Skip to content

Commit 18c0256

Browse files
committed
docs: expand specification for line wrapping
1 parent 0aff7ab commit 18c0256

1 file changed

Lines changed: 303 additions & 0 deletions

File tree

docs/specification_formatter.md

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,306 @@ create_tween() \
8181
```
8282

8383
Anything other than these three continuation layouts is treated as a regular vertical layout and should not be subject to continuation layout rules.
84+
85+
## 1. General Rules
86+
87+
### 1.1. Line width is a loose target
88+
89+
The formatter should keep code at or below `max_line_length` when it can safely add a line break.
90+
91+
The max line length is not a strict limit, though. Strings, node path, identifiers, values, or comments are indivisible content and may cause a line to remain longer than the target.
92+
93+
```gdscript
94+
const DOCUMENTATION_URL = "https://example.com/of/a/url/that/cannot/be/split/even/when/exceeding/max/line/length"
95+
```
96+
97+
All parts of the codebase including the renderer and linter should use the same, consistent width measurement. The measurement should account for the current visual column, indentation, tabs, Unicode width, spaces added by the formatter, commas, backslashes, and more generally any characters inserted by the formatter.
98+
99+
### 1.2. Prefer flat layout when the code fits one line
100+
101+
The formatter should use flat layout when a construct fits and no syntax or comment requires a line break.
102+
103+
Input:
104+
105+
```gdscript
106+
var names = [
107+
"Ana",
108+
"Bao",
109+
"Chidi",
110+
]
111+
```
112+
113+
Output:
114+
115+
```gdscript
116+
var names = ["Ana", "Bao", "Chidi"]
117+
```
118+
119+
Note: This is a change from the previous implementation of the formatter. The previous formatter preserved many source line breaks because it did not wrap lines automatically (it used line breaks as a hint to lay lines vertically). This new formatter should be as hands-off as possible: it should wrap lines when needed and merge them again when the user removes something and the code fits on one line.
120+
121+
The following cases may stay multiline even when their text would fit as a single line:
122+
123+
- Enums, because the official style guide requires one member per line.
124+
- Constructs that contain comments that need to be on separate lines.
125+
- Code that includes an unavoidable hard line break, like a multiline string or a lambda with a multiline body.
126+
- Code inside a `# fmt: off` region.
127+
128+
### 1.3. Existing line breaks do not force broken layout
129+
130+
In the first pass of the formatter, source line breaks are not formatting instructions.
131+
132+
Input:
133+
134+
```gdscript
135+
call(
136+
first,
137+
second,
138+
)
139+
```
140+
141+
Output:
142+
143+
```gdscript
144+
call(first, second)
145+
```
146+
147+
## 2. Indentation
148+
149+
### 2.1. Vertical items use one extra indentation level
150+
151+
Items in vertical layout use one level more than the line that opens the construct.
152+
153+
```gdscript
154+
func move_character():
155+
update_velocity(
156+
input_direction,
157+
maximum_speed,
158+
acceleration,
159+
)
160+
```
161+
162+
This rule applies to arrays, dictionaries, parameters, arguments, annotation arguments, and other comma-separated items inside delimiters.
163+
164+
```gdscript
165+
enum Element {
166+
EARTH,
167+
WATER,
168+
AIR,
169+
FIRE,
170+
}
171+
var party = [
172+
"Godot",
173+
"Godette",
174+
"Steve",
175+
]
176+
var character = {
177+
"name": "Bob",
178+
"job": "Mechanic",
179+
}
180+
181+
182+
func configure_character():
183+
var settings = {
184+
"speed": 300.0,
185+
"acceleration": 1200.0,
186+
}
187+
```
188+
189+
### 2.2. Continuation layouts use two extra indentation levels
190+
191+
Continuation layouts use two extra indentation levels. Conditionals:
192+
193+
```gdscript
194+
if (
195+
position.x > 200 and position.x < 400
196+
and position.y > 300 and position.y < 400
197+
):
198+
pass
199+
```
200+
201+
Backslash continuations:
202+
203+
```gdscript
204+
var total = base_value \
205+
+ equipment_bonus \
206+
+ status_bonus
207+
```
208+
209+
This would also apply to the following case (arguments continuing on hanging lines), although the formatter currently does not produce this kind of packing:
210+
211+
```gdscript
212+
effect.interpolate_property(sprite, "transform/scale",
213+
sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
214+
Tween.TRANS_QUAD, Tween.EASE_OUT)
215+
```
216+
217+
## 3. Delimiters and comma-separated constructs
218+
219+
### 3.1. Break after the opening delimiter
220+
221+
When a construct uses broken layout with delimiters, its first item should start on the next line.
222+
223+
```gdscript
224+
effect.interpolate_property(
225+
sprite,
226+
"transform/scale",
227+
sprite.get_scale(),
228+
Vector2(2.0, 2.0),
229+
0.3,
230+
Tween.TRANS_QUAD,
231+
Tween.EASE_OUT,
232+
)
233+
```
234+
235+
The formatter should normalize hanging input to this vertical layout when the construct must wrap.
236+
237+
### 3.1.1. Keep a fitting call prefix before broken arguments
238+
239+
Method-call arguments own their layout independently from the attribute or method chain that contains the call. When the receiver, method name, and opening parenthesis fit on the current line, the formatter should keep them there even when the arguments require broken layout.
240+
241+
```gdscript
242+
_view_overlay.gui_input.connect(
243+
func(event: InputEvent) -> void:
244+
_panel_gui_input(event)
245+
_view_overlay.mouse_default_cursor_shape = _bubble_container.mouse_default_cursor_shape
246+
)
247+
```
248+
249+
In particular, a multiline lambda argument or ordinary overlong argument list breaks its nearest argument list after the opening parenthesis. It must not by itself break the enclosing attribute chain at every dot.
250+
251+
```gdscript
252+
f.args.map(
253+
func(arg: Dictionary) -> Dictionary:
254+
arg.erase("name")
255+
return arg
256+
)
257+
```
258+
259+
```gdscript
260+
mob.velocity = mob.velocity.move_toward(
261+
desired_velocity,
262+
velocity_distance * acceleration_factor * delta
263+
)
264+
```
265+
266+
### 3.2. Put the closing delimiter on its own line
267+
268+
The closing parenthesis, bracket, or brace of a broken construct should always be on its own line.
269+
270+
```gdscript
271+
var colors = [
272+
Color.RED,
273+
Color.GREEN,
274+
]
275+
```
276+
277+
**Do not** produce this layout:
278+
279+
```gdscript
280+
var colors = [
281+
Color.RED,
282+
Color.GREEN,]
283+
```
284+
285+
### 3.3. Put one item on each line by default
286+
287+
Arrays, dictionaries, function parameters, function arguments, annotation arguments, and similar comma-separated constructs should use one item per line when broken by default. Examples:
288+
289+
```gdscript
290+
var values = [
291+
10,
292+
20,
293+
30,
294+
40,
295+
]
296+
```
297+
298+
```gdscript
299+
func spawn_character(
300+
position: Vector2,
301+
direction: Vector2,
302+
speed: float,
303+
) -> CharacterBody2D:
304+
pass
305+
```
306+
307+
```gdscript
308+
spawn_character(
309+
spawn_position,
310+
Vector2.RIGHT,
311+
300.0,
312+
)
313+
```
314+
315+
```gdscript
316+
@export_custom(
317+
PROPERTY_HINT_RANGE,
318+
"0,100,1",
319+
)
320+
var health := 100
321+
```
322+
323+
```gdscript
324+
# For dictionaries, put one key-value pair per line when broken.
325+
var character = {
326+
"name": "Bob",
327+
"age": 27,
328+
"job": "Mechanic",
329+
}
330+
```
331+
332+
### 3.4. Always format enums vertically
333+
334+
Every enum member should be on its own line, even when the enum would fit on one line.
335+
336+
Input:
337+
338+
```gdscript
339+
enum Element { EARTH, WATER, AIR, FIRE }
340+
```
341+
342+
Output:
343+
344+
```gdscript
345+
enum Element {
346+
EARTH,
347+
WATER,
348+
AIR,
349+
FIRE,
350+
}
351+
```
352+
353+
This rule follows the official GDScript style guide.
354+
355+
### 3.5. Add a trailing comma to multiline constructs
356+
357+
The formatter should add a trailing comma to every multiline comma-separated construct where the GDScript grammar accepts it.
358+
359+
```gdscript
360+
call(
361+
first,
362+
second,
363+
)
364+
```
365+
366+
```gdscript
367+
func combine(
368+
first: String,
369+
second: String,
370+
) -> String:
371+
return first + second
372+
```
373+
374+
The formatter should remove the trailing comma when the construct collapses to one line.
375+
376+
```gdscript
377+
call(first, second)
378+
```
379+
380+
**Exception 1:** The `preload()` function must not have a trailing comma; GDScript does not allow it:
381+
382+
```gdscript
383+
const MUSIC = preload(
384+
"res://audio/a_very_long_music_file_name.ogg"
385+
)
386+
```

0 commit comments

Comments
 (0)