Skip to content

Commit 2ca7a8d

Browse files
committed
Implement better line wrapping for chains of properties and method calls
with long argument lists or nested lambdas
1 parent 1c46750 commit 2ca7a8d

10 files changed

Lines changed: 319 additions & 63 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This file documents the changes made to the formatter with each release.
1111
### Changed
1212

1313
- Implemented new balanced line wrapping algorithm for long chains of expressions, including operations and boolean expressions
14+
- In chains of properties and method calls, the formatter will now try to preserve chains like a.b.c with long argument lists or nested lambdas. It will favor breaking the arguments in parentheses over breaking the chain into multiple lines
1415
- Specified the definition for continuation lines and changed function calls and other statements and expressions to use a single extra indent instead of two by default
1516
- Always force lambda functions to have a line return after the function declaration, like regular functions
1617

src/formatter.rs

Lines changed: 138 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use crate::QuoteStyle;
1414
use crate::node_kind::GDScriptNodeKind;
1515
use crate::parser::{ParseInput, RegionWithDisabledFormatting};
16-
use crate::renderer::{RangeRenderElement, RangeSourceBytes, RenderElement};
16+
use crate::renderer::{GroupParentFit, RangeRenderElement, RangeSourceBytes, RenderElement};
1717
use crate::reorder::{self, DeclarationKind};
1818

1919
fn begin_indent(render_elements: &mut Vec<RenderElement>, level: u16) -> usize {
@@ -40,6 +40,7 @@ fn begin_group(render_elements: &mut Vec<RenderElement>) -> usize {
4040
let index = render_elements.len();
4141
render_elements.push(RenderElement::Group {
4242
children: RangeRenderElement { start: 0, end: 0 },
43+
parent_fit: GroupParentFit::Full,
4344
});
4445
index
4546
}
@@ -55,6 +56,14 @@ fn finish_group(render_elements: &mut [RenderElement], group_index: usize) {
5556
}
5657
}
5758

59+
fn begin_group_until_first_line_break(render_elements: &mut Vec<RenderElement>) -> usize {
60+
let group_index = begin_group(render_elements);
61+
if let RenderElement::Group { parent_fit, .. } = &mut render_elements[group_index] {
62+
*parent_fit = GroupParentFit::UntilFirstLineBreak;
63+
}
64+
group_index
65+
}
66+
5867
/// Returns the number of blank lines to output before a declaration of the given
5968
/// kind, based on the user's config. Returns 0 for declarations that don't need
6069
/// extra blank lines (normal spacing applies).
@@ -2031,7 +2040,6 @@ fn process_attribute(
20312040
} else {
20322041
input.continuation_indent_level
20332042
};
2034-
let continuation_indent_index = begin_indent(render_elements, chain_indent_level);
20352043
let mut attribute_index: u32 = 1;
20362044
while attribute_index < child_count as u32 {
20372045
let child = node.child(attribute_index);
@@ -2061,18 +2069,28 @@ fn process_attribute(
20612069
if GDScriptNodeKind::get_kind_from_ast_node(call_node)
20622070
== GDScriptNodeKind::AttributeCall
20632071
{
2064-
process_method_call_flat(input, call_node, render_elements);
2072+
process_method_call_name(input, call_node, render_elements);
2073+
finish_indent(render_elements, continuation_indent_index);
2074+
process_method_call_arguments(
2075+
input,
2076+
call_node,
2077+
attribute_index + 2 >= child_count as u32,
2078+
render_elements,
2079+
);
20652080
} else {
20662081
process_node(input, call_node, render_elements);
2082+
finish_indent(render_elements, continuation_indent_index);
20672083
}
2084+
} else {
2085+
finish_indent(render_elements, continuation_indent_index);
20682086
}
2069-
finish_indent(render_elements, continuation_indent_index);
20702087
}
20712088
attribute_index += 2;
20722089
continue;
20732090
}
20742091

20752092
if !allows_implicit_continuation && !has_explicit_line_continuation {
2093+
let continuation_indent_index = begin_indent(render_elements, chain_indent_level);
20762094
let continuation_index = render_elements.len() + 1;
20772095
render_elements.push(RenderElement::Branch {
20782096
if_single_line: None,
@@ -2083,6 +2101,31 @@ fn process_attribute(
20832101
});
20842102
render_elements.push(RenderElement::Space);
20852103
render_elements.push(RenderElement::TextStatic("\\"));
2104+
render_elements.push(RenderElement::SoftLine);
2105+
if let Some(dot_node) = child {
2106+
process_node(input, dot_node, render_elements);
2107+
}
2108+
if let Some(call_node) = next {
2109+
if GDScriptNodeKind::get_kind_from_ast_node(call_node)
2110+
== GDScriptNodeKind::AttributeCall
2111+
{
2112+
process_method_call_name(input, call_node, render_elements);
2113+
finish_indent(render_elements, continuation_indent_index);
2114+
process_method_call_arguments(
2115+
input,
2116+
call_node,
2117+
attribute_index + 2 >= child_count as u32,
2118+
render_elements,
2119+
);
2120+
} else {
2121+
process_node(input, call_node, render_elements);
2122+
finish_indent(render_elements, continuation_indent_index);
2123+
}
2124+
} else {
2125+
finish_indent(render_elements, continuation_indent_index);
2126+
}
2127+
attribute_index += 2;
2128+
continue;
20862129
}
20872130
if !has_explicit_line_continuation {
20882131
render_elements.push(RenderElement::SoftLine);
@@ -2095,82 +2138,118 @@ fn process_attribute(
20952138
if GDScriptNodeKind::get_kind_from_ast_node(call_node)
20962139
== GDScriptNodeKind::AttributeCall
20972140
{
2098-
process_method_call_flat(input, call_node, render_elements);
2141+
process_method_call_flat(
2142+
input,
2143+
call_node,
2144+
attribute_index + 2 >= child_count as u32,
2145+
render_elements,
2146+
);
20992147
} else {
21002148
process_node(input, call_node, render_elements);
21012149
}
21022150
}
21032151

21042152
attribute_index += 2;
21052153
}
2106-
finish_indent(render_elements, continuation_indent_index);
21072154
finish_group(render_elements, group_index);
21082155
}
21092156

2110-
/// Builds a method call (name + arguments) inline without extra indentation.
2111-
/// Used inside a dot-access chain. Uses emit_inter_child_separator between
2112-
/// arguments and between the last argument and the closing parenthesis.
2157+
/// Builds a method call inside a dot-access chain. Its argument container is
2158+
/// isolated so that long arguments do not force the whole chain to break.
21132159
fn process_method_call_flat(
2160+
input: &ParseInput,
2161+
attribute_call: tree_sitter::Node,
2162+
is_last_chain_call: bool,
2163+
render_elements: &mut Vec<RenderElement>,
2164+
) {
2165+
process_method_call_name(input, attribute_call, render_elements);
2166+
process_method_call_arguments(input, attribute_call, is_last_chain_call, render_elements);
2167+
}
2168+
2169+
fn process_method_call_name(
21142170
input: &ParseInput,
21152171
attribute_call: tree_sitter::Node,
21162172
render_elements: &mut Vec<RenderElement>,
21172173
) {
21182174
if let Some(method_name) = attribute_call.child(0) {
21192175
process_node(input, method_name, render_elements);
21202176
}
2177+
}
2178+
2179+
fn process_method_call_arguments(
2180+
input: &ParseInput,
2181+
attribute_call: tree_sitter::Node,
2182+
is_last_chain_call: bool,
2183+
render_elements: &mut Vec<RenderElement>,
2184+
) {
21212185
if let Some(args) = attribute_call.child(1) {
2122-
let argument_child_count = args.child_count();
2123-
if argument_child_count >= 2 {
2124-
if let Some(open) = args.child(0) {
2125-
process_node(input, open, render_elements);
2126-
}
2127-
let close_parenthesis_index = (argument_child_count - 1) as u32;
2128-
let args_kind = GDScriptNodeKind::get_kind_from_ast_node(args);
2129-
let has_trailing_comma = if close_parenthesis_index >= 2 {
2130-
if let Some(node_before_close_paren) = args.child(close_parenthesis_index - 1) {
2131-
GDScriptNodeKind::get_kind_from_ast_node(node_before_close_paren)
2132-
== GDScriptNodeKind::TokenComma
2133-
} else {
2134-
false
2135-
}
2136-
} else {
2137-
false
2138-
};
2139-
let body_end = if has_trailing_comma {
2140-
close_parenthesis_index - 1
2141-
} else {
2142-
close_parenthesis_index
2143-
};
2144-
let mut previous_child: Option<tree_sitter::Node> =
2145-
Some(args.child(0).expect("argument_child_count>=2"));
2146-
let mut argument_index: u32 = 1;
2147-
while argument_index < body_end {
2148-
if let Some(child_argument) = args.child(argument_index) {
2149-
if let Some(ref previous_node) = previous_child {
2150-
process_separator_between_sibling_nodes(
2151-
args_kind,
2152-
previous_node,
2153-
&child_argument,
2154-
render_elements,
2155-
);
2156-
}
2157-
process_node(input, child_argument, render_elements);
2158-
previous_child = Some(child_argument);
2159-
}
2160-
argument_index += 1;
2161-
}
2162-
if let Some(close) = args.child(close_parenthesis_index) {
2163-
if let Some(ref previous_node) = previous_child {
2164-
process_separator_between_sibling_nodes(
2165-
args_kind,
2166-
previous_node,
2167-
&close,
2168-
render_elements,
2169-
);
2170-
}
2171-
process_node(input, close, render_elements);
2186+
if is_last_chain_call {
2187+
let group_index = begin_group_until_first_line_break(render_elements);
2188+
process_node(input, args, render_elements);
2189+
finish_group(render_elements, group_index);
2190+
} else {
2191+
process_method_arguments_flat(input, args, render_elements);
2192+
}
2193+
}
2194+
}
2195+
2196+
fn process_method_arguments_flat(
2197+
input: &ParseInput,
2198+
args: tree_sitter::Node,
2199+
render_elements: &mut Vec<RenderElement>,
2200+
) {
2201+
let argument_child_count = args.child_count();
2202+
if argument_child_count < 2 {
2203+
return;
2204+
}
2205+
if let Some(open) = args.child(0) {
2206+
process_node(input, open, render_elements);
2207+
}
2208+
let close_parenthesis_index = (argument_child_count - 1) as u32;
2209+
let args_kind = GDScriptNodeKind::get_kind_from_ast_node(args);
2210+
let has_trailing_comma = if close_parenthesis_index >= 2 {
2211+
if let Some(node_before_close_paren) = args.child(close_parenthesis_index - 1) {
2212+
GDScriptNodeKind::get_kind_from_ast_node(node_before_close_paren)
2213+
== GDScriptNodeKind::TokenComma
2214+
} else {
2215+
false
2216+
}
2217+
} else {
2218+
false
2219+
};
2220+
let body_end = if has_trailing_comma {
2221+
close_parenthesis_index - 1
2222+
} else {
2223+
close_parenthesis_index
2224+
};
2225+
let mut previous_child: Option<tree_sitter::Node> =
2226+
Some(args.child(0).expect("argument_child_count >= 2"));
2227+
let mut argument_index: u32 = 1;
2228+
while argument_index < body_end {
2229+
if let Some(child_argument) = args.child(argument_index) {
2230+
if let Some(ref previous_node) = previous_child {
2231+
process_separator_between_sibling_nodes(
2232+
args_kind,
2233+
previous_node,
2234+
&child_argument,
2235+
render_elements,
2236+
);
21722237
}
2238+
process_node(input, child_argument, render_elements);
2239+
previous_child = Some(child_argument);
21732240
}
2241+
argument_index += 1;
2242+
}
2243+
if let Some(close) = args.child(close_parenthesis_index) {
2244+
if let Some(ref previous_node) = previous_child {
2245+
process_separator_between_sibling_nodes(
2246+
args_kind,
2247+
previous_node,
2248+
&close,
2249+
render_elements,
2250+
);
2251+
}
2252+
process_node(input, close, render_elements);
21742253
}
21752254
}
21762255

0 commit comments

Comments
 (0)