on master help sign in
Fix YAML auto indent

YAML indents queries are tweaked to fix auto indent behavior. A new capture type `indent.always` is introduced to address use cases where combining indent captures on a single line is desired. Fixes #6661

commit7078e8400736dce923be44a4d26f136a22640f93 authorSkyler Hawthorne <skyler@dead10ck.com> dateFri 14 Apr 2023, 11:00 (4 years ago) parent57f093d
7 files changed+1130−31
helix-core/src/indent.rs+224−28 helix-term/src/commands.rs+6−2 helix-term/tests/integration.rs+1 helix-term/tests/test/languages/go.rs+41 helix-term/tests/test/languages/mod.rs+4 helix-term/tests/test/languages/yaml.rs+819 runtime/queries/yaml/indents.scm+35−1
helix-core/src/indent.rs+224−28
@@ -237,38 +237,58 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
237237/// This is usually constructed in one of 2 ways:
238238/// - Successively add indent captures to get the (added) indent from a single line
239239/// - Successively add the indent results for each line
240−#[derive(Default)]
240+#[derive(Default, Debug, PartialEq, Eq, Clone)]
241241pub struct Indentation {
242242 /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
243243 /// The string that this results in depends on the indent style (spaces or tabs, etc.)
244244 indent: usize,
245+ indent_always: usize,
245246 outdent: usize,
247+ outdent_always: usize,
246248}
247249impl Indentation {
248250 /// Add some other [Indentation] to this.
249251 /// The added indent should be the total added indent from one line
250252 fn add_line(&mut self, added: &Indentation) {
251− if added.indent > 0 && added.outdent == 0 {
252− self.indent += 1;
253− } else if added.outdent > 0 && added.indent == 0 {
254− self.outdent += 1;
255− }
253+ self.indent += added.indent;
254+ self.indent_always += added.indent_always;
255+ self.outdent += added.outdent;
256+ self.outdent_always += added.outdent_always;
256257 }
258+
257259 /// Add an indent capture to this indent.
258260 /// All the captures that are added in this way should be on the same line.
259261 fn add_capture(&mut self, added: IndentCaptureType) {
260262 match added {
261263 IndentCaptureType::Indent => {
262− self.indent = 1;
264+ if self.indent_always == 0 {
265+ self.indent = 1;
266+ }
267+ }
268+ IndentCaptureType::IndentAlways => {
269+ // any time we encounter an `indent.always` on the same line, we
270+ // want to cancel out all regular indents
271+ self.indent_always += 1;
272+ self.indent = 0;
263273 }
264274 IndentCaptureType::Outdent => {
265− self.outdent = 1;
275+ if self.outdent_always == 0 {
276+ self.outdent = 1;
277+ }
278+ }
279+ IndentCaptureType::OutdentAlways => {
280+ self.outdent_always += 1;
281+ self.outdent = 0;
266282 }
267283 }
268284 }
285+
269286 fn as_string(&self, indent_style: &IndentStyle) -> String {
270− let indent_level = if self.indent >= self.outdent {
271− self.indent - self.outdent
287+ let indent = self.indent_always + self.indent;
288+ let outdent = self.outdent_always + self.outdent;
289+
290+ let indent_level = if indent >= outdent {
291+ indent - outdent
272292 } else {
273293 log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
274294 0
@@ -278,27 +298,32 @@ impl Indentation {
278298}
279299
280300/// An indent definition which corresponds to a capture from the indent query
301+#[derive(Debug)]
281302struct IndentCapture {
282303 capture_type: IndentCaptureType,
283304 scope: IndentScope,
284305}
285−#[derive(Clone, Copy)]
306+
307+#[derive(Debug, Clone, Copy, PartialEq)]
286308enum IndentCaptureType {
287309 Indent,
310+ IndentAlways,
288311 Outdent,
312+ OutdentAlways,
289313}
314+
290315impl IndentCaptureType {
291316 fn default_scope(&self) -> IndentScope {
292317 match self {
293− IndentCaptureType::Indent => IndentScope::Tail,
294− IndentCaptureType::Outdent => IndentScope::All,
318+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
319+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
295320 }
296321 }
297322}
298323/// This defines which part of a node an [IndentCapture] applies to.
299324/// Each [IndentCaptureType] has a default scope, but the scope can be changed
300325/// with `#set!` property declarations.
301−#[derive(Clone, Copy)]
326+#[derive(Debug, Clone, Copy)]
302327enum IndentScope {
303328 /// The indent applies to the whole node
304329 All,
@@ -308,6 +333,7 @@ enum IndentScope {
308333
309334/// A capture from the indent query which does not define an indent but extends
310335/// the range of a node. This is used before the indent is calculated.
336+#[derive(Debug)]
311337enum ExtendCapture {
312338 Extend,
313339 PreventOnce,
@@ -316,6 +342,7 @@ enum ExtendCapture {
316342/// The result of running a tree-sitter indent query. This stores for
317343/// each node (identified by its ID) the relevant captures (already filtered
318344/// by predicates).
345+#[derive(Debug)]
319346struct IndentQueryResult {
320347 indent_captures: HashMap<usize, Vec<IndentCapture>>,
321348 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
@@ -334,6 +361,33 @@ fn query_indents(
334361 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
335362 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
336363 cursor.set_byte_range(range);
364+
365+ let get_node_start_line = |node: Node| {
366+ let mut node_line = node.start_position().row;
367+
368+ // Adjust for the new line that will be inserted
369+ if let Some((line, byte)) = new_line_break {
370+ if node_line == line && node.start_byte() >= byte {
371+ node_line += 1;
372+ }
373+ }
374+
375+ node_line
376+ };
377+
378+ let get_node_end_line = |node: Node| {
379+ let mut node_line = node.end_position().row;
380+
381+ // Adjust for the new line that will be inserted
382+ if let Some((line, byte)) = new_line_break {
383+ if node_line == line && node.end_byte() < byte {
384+ node_line += 1;
385+ }
386+ }
387+
388+ node_line
389+ };
390+
337391 // Iterate over all captures from the query
338392 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
339393 // Skip matches where not all custom predicates are fulfilled
@@ -360,21 +414,13 @@ fn query_indents(
360414 Some(QueryPredicateArg::Capture(capt1)),
361415 Some(QueryPredicateArg::Capture(capt2))
362416 ) => {
363− let get_line_num = |node: Node| {
364− let mut node_line = node.start_position().row;
365− // Adjust for the new line that will be inserted
366− if let Some((line, byte)) = new_line_break {
367− if node_line==line && node.start_byte()>=byte {
368− node_line += 1;
369− }
370− }
371− node_line
372− };
373417 let n1 = m.nodes_for_capture_index(*capt1).next();
374418 let n2 = m.nodes_for_capture_index(*capt2).next();
375419 match (n1, n2) {
376420 (Some(n1), Some(n2)) => {
377− let same_line = get_line_num(n1)==get_line_num(n2);
421+ let n1_line = get_node_start_line(n1);
422+ let n2_line = get_node_start_line(n2);
423+ let same_line = n1_line == n2_line;
378424 same_line==(pred.operator.as_ref()=="same-line?")
379425 }
380426 _ => true,
@@ -385,6 +431,23 @@ fn query_indents(
385431 }
386432 }
387433 }
434+ "one-line?" | "not-one-line?" => match pred.args.get(0) {
435+ Some(QueryPredicateArg::Capture(capture_idx)) => {
436+ let node = m.nodes_for_capture_index(*capture_idx).next();
437+
438+ match node {
439+ Some(node) => {
440+ let (start_line, end_line) = (get_node_start_line(node), get_node_end_line(node));
441+ let one_line = end_line == start_line;
442+ one_line != (pred.operator.as_ref() == "not-one-line?")
443+ },
444+ _ => true,
445+ }
446+ }
447+ _ => {
448+ panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
449+ }
450+ },
388451 _ => {
389452 panic!(
390453 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -399,7 +462,9 @@ fn query_indents(
399462 let capture_name = query.capture_names()[capture.index as usize].as_str();
400463 let capture_type = match capture_name {
401464 "indent" => IndentCaptureType::Indent,
465+ "indent.always" => IndentCaptureType::IndentAlways,
402466 "outdent" => IndentCaptureType::Outdent,
467+ "outdent.always" => IndentCaptureType::OutdentAlways,
403468 "extend" => {
404469 extend_captures
405470 .entry(capture.node.id())
@@ -456,10 +521,15 @@ fn query_indents(
456521 .push(indent_capture);
457522 }
458523 }
459− IndentQueryResult {
524+
525+ let result = IndentQueryResult {
460526 indent_captures,
461527 extend_captures,
462− }
528+ };
529+
530+ log::trace!("indent result = {:?}", result);
531+
532+ result
463533}
464534
465535/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -584,6 +654,7 @@ pub fn treesitter_indent_for_pos(
584654 .tree()
585655 .root_node()
586656 .descendant_for_byte_range(byte_pos, byte_pos)?;
657+
587658 let (query_result, deepest_preceding) = {
588659 // The query range should intersect with all nodes directly preceding
589660 // the position of the indent query in case one of them is extended.
@@ -642,10 +713,12 @@ pub fn treesitter_indent_for_pos(
642713 // even if there are multiple "indent" nodes on the same line
643714 let mut indent_for_line = Indentation::default();
644715 let mut indent_for_line_below = Indentation::default();
716+
645717 loop {
646718 // This can safely be unwrapped because `first_in_line` contains
647719 // one entry for each ancestor of the node (which is what we iterate over)
648720 let is_first = *first_in_line.last().unwrap();
721+
649722 // Apply all indent definitions for this node
650723 if let Some(definitions) = indent_captures.get(&node.id()) {
651724 for definition in definitions {
@@ -667,6 +740,7 @@ pub fn treesitter_indent_for_pos(
667740 if let Some(parent) = node.parent() {
668741 let mut node_line = node.start_position().row;
669742 let mut parent_line = parent.start_position().row;
743+
670744 if node_line == line && new_line {
671745 // Also consider the line that will be inserted
672746 if node.start_byte() >= byte_pos {
@@ -676,17 +750,20 @@ pub fn treesitter_indent_for_pos(
676750 parent_line += 1;
677751 }
678752 };
753+
679754 if node_line != parent_line {
755+ // Don't add indent for the line below the line of the query
680756 if node_line < line + (new_line as usize) {
681− // Don't add indent for the line below the line of the query
682757 result.add_line(&indent_for_line_below);
683758 }
759+
684760 if node_line == parent_line + 1 {
685761 indent_for_line_below = indent_for_line;
686762 } else {
687763 result.add_line(&indent_for_line);
688764 indent_for_line_below = Indentation::default();
689765 }
766+
690767 indent_for_line = Indentation::default();
691768 }
692769
@@ -700,6 +777,7 @@ pub fn treesitter_indent_for_pos(
700777 {
701778 result.add_line(&indent_for_line_below);
702779 }
780+
703781 result.add_line(&indent_for_line);
704782 break;
705783 }
@@ -810,4 +888,122 @@ mod test {
810888 2
811889 );
812890 }
891+
892+ #[test]
893+ fn add_capture() {
894+ let indent = || Indentation {
895+ indent: 1,
896+ ..Default::default()
897+ };
898+ let indent_always = || Indentation {
899+ indent_always: 1,
900+ ..Default::default()
901+ };
902+ let outdent = || Indentation {
903+ outdent: 1,
904+ ..Default::default()
905+ };
906+ let outdent_always = || Indentation {
907+ outdent_always: 1,
908+ ..Default::default()
909+ };
910+
911+ let add_capture = |mut indent: Indentation, capture| {
912+ indent.add_capture(capture);
913+ indent
914+ };
915+
916+ // adding an indent to no indent makes an indent
917+ assert_eq!(
918+ indent(),
919+ add_capture(Indentation::default(), IndentCaptureType::Indent)
920+ );
921+ assert_eq!(
922+ indent_always(),
923+ add_capture(Indentation::default(), IndentCaptureType::IndentAlways)
924+ );
925+ assert_eq!(
926+ outdent(),
927+ add_capture(Indentation::default(), IndentCaptureType::Outdent)
928+ );
929+ assert_eq!(
930+ outdent_always(),
931+ add_capture(Indentation::default(), IndentCaptureType::OutdentAlways)
932+ );
933+
934+ // adding an indent to an already indented has no effect
935+ assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent));
936+ assert_eq!(
937+ outdent(),
938+ add_capture(outdent(), IndentCaptureType::Outdent)
939+ );
940+
941+ // adding an always to a regular makes it always
942+ assert_eq!(
943+ indent_always(),
944+ add_capture(indent(), IndentCaptureType::IndentAlways)
945+ );
946+ assert_eq!(
947+ outdent_always(),
948+ add_capture(outdent(), IndentCaptureType::OutdentAlways)
949+ );
950+
951+ // adding an always to an always is additive
952+ assert_eq!(
953+ Indentation {
954+ indent_always: 2,
955+ ..Default::default()
956+ },
957+ add_capture(indent_always(), IndentCaptureType::IndentAlways)
958+ );
959+ assert_eq!(
960+ Indentation {
961+ outdent_always: 2,
962+ ..Default::default()
963+ },
964+ add_capture(outdent_always(), IndentCaptureType::OutdentAlways)
965+ );
966+
967+ // adding regular to always should be associative
968+ assert_eq!(
969+ Indentation {
970+ indent_always: 1,
971+ ..Default::default()
972+ },
973+ add_capture(
974+ add_capture(indent(), IndentCaptureType::Indent),
975+ IndentCaptureType::IndentAlways
976+ )
977+ );
978+ assert_eq!(
979+ Indentation {
980+ indent_always: 1,
981+ ..Default::default()
982+ },
983+ add_capture(
984+ add_capture(indent(), IndentCaptureType::IndentAlways),
985+ IndentCaptureType::Indent
986+ )
987+ );
988+ assert_eq!(
989+ Indentation {
990+ outdent_always: 1,
991+ ..Default::default()
992+ },
993+ add_capture(
994+ add_capture(outdent(), IndentCaptureType::Outdent),
995+ IndentCaptureType::OutdentAlways
996+ )
997+ );
998+ assert_eq!(
999+ Indentation {
1000+ outdent_always: 1,
1001+ ..Default::default()
1002+ },
1003+ add_capture(
1004+ add_capture(outdent(), IndentCaptureType::OutdentAlways),
1005+ IndentCaptureType::Outdent
1006+ )
1007+ );
1008+ }
8131009}
helix-term/src/commands.rs+6−2
@@ -2999,6 +2999,7 @@ fn open(cx: &mut Context, open: Open) {
29992999 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
30003000 Open::Above => range.from(),
30013001 });
3002+
30023003 let new_line = match open {
30033004 // adjust position to the end of the line (next line - 1)
30043005 Open::Below => cursor_line + 1,
@@ -3006,13 +3007,15 @@ fn open(cx: &mut Context, open: Open) {
30063007 Open::Above => cursor_line,
30073008 };
30083009
3010+ let line_num = new_line.saturating_sub(1);
3011+
30093012 // Index to insert newlines after, as well as the char width
30103013 // to use to compensate for those inserted newlines.
30113014 let (line_end_index, line_end_offset_width) = if new_line == 0 {
30123015 (0, 0)
30133016 } else {
30143017 (
3015− line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3018+ line_end_char_index(&text, line_num),
30163019 doc.line_ending.len_chars(),
30173020 )
30183021 };
@@ -3023,10 +3026,11 @@ fn open(cx: &mut Context, open: Open) {
30233026 &doc.indent_style,
30243027 doc.tab_width(),
30253028 text,
3026− new_line.saturating_sub(1),
3029+ line_num,
30273030 line_end_index,
30283031 cursor_line,
30293032 );
3033+
30303034 let indent_len = indent.len();
30313035 let mut text = String::with_capacity(1 + indent_len);
30323036 text.push_str(doc.line_ending.as_str());
helix-term/tests/integration.rs+1
@@ -18,6 +18,7 @@ mod test {
1818 mod auto_indent;
1919 mod auto_pairs;
2020 mod commands;
21+ mod languages;
2122 mod movement;
2223 mod picker;
2324 mod prompt;
helix-term/tests/test/languages/go.rsnew file+41
@@ -0,0 +1,41 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn auto_indent() -> anyhow::Result<()> {
5+ let app = || AppBuilder::new().with_file("foo.go", None);
6+
7+ let enter_tests = [
8+ (
9+ helpers::platform_line(indoc! {r##"
10+ type Test struct {#[}|]#
11+ "##}),
12+ "i<ret>",
13+ helpers::platform_line(indoc! {"\
14+ type Test struct {
15+ \t#[|\n]#
16+ }
17+ "}),
18+ ),
19+ (
20+ helpers::platform_line(indoc! {"\
21+ func main() {
22+ \tswitch nil {#[}|]#
23+ }
24+ "}),
25+ "i<ret>",
26+ helpers::platform_line(indoc! {"\
27+ func main() {
28+ \tswitch nil {
29+ \t\t#[|\n]#
30+ \t}
31+ }
32+ "}),
33+ ),
34+ ];
35+
36+ for test in enter_tests {
37+ test_with_config(app(), test).await?;
38+ }
39+
40+ Ok(())
41+}
helix-term/tests/test/languages/mod.rsnew file+4
@@ -0,0 +1,4 @@
1+use super::*;
2+
3+mod go;
4+mod yaml;
helix-term/tests/test/languages/yaml.rsnew file+819
@@ -0,0 +1,819 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn auto_indent() -> anyhow::Result<()> {
5+ let app = || AppBuilder::new().with_file("foo.yaml", None);
6+
7+ let below_tests = [
8+ (
9+ helpers::platform_line(indoc! {r##"
10+ #[t|]#op:
11+ baz: foo
12+ bazi:
13+ more: yes
14+ why: because
15+ quux:
16+ - 1
17+ - 2
18+ bax: foox
19+ fook:
20+ "##}),
21+ "o",
22+ helpers::platform_line(indoc! {"\
23+ top:
24+ #[\n|]#
25+ baz: foo
26+ bazi:
27+ more: yes
28+ why: because
29+ quux:
30+ - 1
31+ - 2
32+ bax: foox
33+ fook:
34+ "}),
35+ ),
36+ (
37+ helpers::platform_line(indoc! {r##"
38+ top:
39+ b#[a|]#z: foo
40+ bazi:
41+ more: yes
42+ why: because
43+ quux:
44+ - 1
45+ - 2
46+ bax: foox
47+ fook:
48+ "##}),
49+ "o",
50+ helpers::platform_line(indoc! {"\
51+ top:
52+ baz: foo
53+ #[\n|]#
54+ bazi:
55+ more: yes
56+ why: because
57+ quux:
58+ - 1
59+ - 2
60+ bax: foox
61+ fook:
62+ "}),
63+ ),
64+ (
65+ helpers::platform_line(indoc! {r##"
66+ top:
67+ baz: foo
68+ bazi#[:|]#
69+ more: yes
70+ why: because
71+ quux:
72+ - 1
73+ - 2
74+ bax: foox
75+ fook:
76+ "##}),
77+ "o",
78+ helpers::platform_line(indoc! {"\
79+ top:
80+ baz: foo
81+ bazi:
82+ #[\n|]#
83+ more: yes
84+ why: because
85+ quux:
86+ - 1
87+ - 2
88+ bax: foox
89+ fook:
90+ "}),
91+ ),
92+ (
93+ helpers::platform_line(indoc! {r##"
94+ top:
95+ baz: foo
96+ bazi:
97+ more: #[yes|]#
98+ why: because
99+ quux:
100+ - 1
101+ - 2
102+ bax: foox
103+ fook:
104+ "##}),
105+ "o",
106+ helpers::platform_line(indoc! {"\
107+ top:
108+ baz: foo
109+ bazi:
110+ more: yes
111+ #[\n|]#
112+ why: because
113+ quux:
114+ - 1
115+ - 2
116+ bax: foox
117+ fook:
118+ "}),
119+ ),
120+ (
121+ helpers::platform_line(indoc! {r##"
122+ top:
123+ baz: foo
124+ bazi:
125+ more: yes
126+ why: becaus#[e|]#
127+ quux:
128+ - 1
129+ - 2
130+ bax: foox
131+ fook:
132+ "##}),
133+ "o",
134+ helpers::platform_line(indoc! {"\
135+ top:
136+ baz: foo
137+ bazi:
138+ more: yes
139+ why: because
140+ #[\n|]#
141+ quux:
142+ - 1
143+ - 2
144+ bax: foox
145+ fook:
146+ "}),
147+ ),
148+ (
149+ helpers::platform_line(indoc! {"\
150+ top:
151+ baz: foo
152+ bazi:
153+ more: yes
154+ why: because
155+ quux:#[\n|]#
156+ - 1
157+ - 2
158+ bax: foox
159+ fook:
160+ "}),
161+ "o",
162+ helpers::platform_line(indoc! {"\
163+ top:
164+ baz: foo
165+ bazi:
166+ more: yes
167+ why: because
168+ quux:
169+ #[\n|]#
170+ - 1
171+ - 2
172+ bax: foox
173+ fook:
174+ "}),
175+ ),
176+ (
177+ helpers::platform_line(indoc! {"\
178+ top:
179+ baz: foo
180+ bazi:
181+ more: yes
182+ why: because
183+ quux:
184+ - 1#[\n|]#
185+ - 2
186+ bax: foox
187+ fook:
188+ "}),
189+ "o",
190+ helpers::platform_line(indoc! {"\
191+ top:
192+ baz: foo
193+ bazi:
194+ more: yes
195+ why: because
196+ quux:
197+ - 1
198+ #[\n|]#
199+ - 2
200+ bax: foox
201+ fook:
202+ "}),
203+ ),
204+ (
205+ helpers::platform_line(indoc! {"\
206+ top:
207+ baz: foo
208+ bazi:
209+ more: yes
210+ why: because
211+ quux:
212+ - 1
213+ - 2
214+ bax: foox
215+ fook:#[\n|]#
216+ "}),
217+ "o",
218+ helpers::platform_line(indoc! {"\
219+ top:
220+ baz: foo
221+ bazi:
222+ more: yes
223+ why: because
224+ quux:
225+ - 1
226+ - 2
227+ bax: foox
228+ fook:
229+ #[\n|]#
230+ "}),
231+ ),
232+ (
233+ helpers::platform_line(indoc! {"\
234+ top:
235+ baz: foo
236+ bax: |
237+ some
238+ multi
239+ line
240+ string#[\n|]#
241+ fook:
242+ "}),
243+ "o",
244+ helpers::platform_line(indoc! {"\
245+ top:
246+ baz: foo
247+ bax: |
248+ some
249+ multi
250+ line
251+ string
252+ #[\n|]#
253+ fook:
254+ "}),
255+ ),
256+ (
257+ helpers::platform_line(indoc! {"\
258+ top:
259+ baz: foo
260+ bax: >
261+ some
262+ multi
263+ line#[\n|]#
264+ string
265+ fook:
266+ "}),
267+ "o",
268+ helpers::platform_line(indoc! {"\
269+ top:
270+ baz: foo
271+ bax: >
272+ some
273+ multi
274+ line
275+ #[\n|]#
276+ string
277+ fook:
278+ "}),
279+ ),
280+ (
281+ helpers::platform_line(indoc! {"\
282+ top:
283+ baz: foo
284+ bax: >#[\n|]#
285+ fook:
286+ "}),
287+ "o",
288+ helpers::platform_line(indoc! {"\
289+ top:
290+ baz: foo
291+ bax: >
292+ #[\n|]#
293+ fook:
294+ "}),
295+ ),
296+ (
297+ helpers::platform_line(indoc! {"\
298+ - top:#[\n|]#
299+ baz: foo
300+ bax: foox
301+ fook:
302+ "}),
303+ "o",
304+ helpers::platform_line(indoc! {"\
305+ - top:
306+ #[\n|]#
307+ baz: foo
308+ bax: foox
309+ fook:
310+ "}),
311+ ),
312+ (
313+ helpers::platform_line(indoc! {"\
314+ - top:
315+ baz: foo#[\n|]#
316+ bax: foox
317+ fook:
318+ "}),
319+ "o",
320+ helpers::platform_line(indoc! {"\
321+ - top:
322+ baz: foo
323+ #[\n|]#
324+ bax: foox
325+ fook:
326+ "}),
327+ ),
328+ (
329+ helpers::platform_line(indoc! {"\
330+ - top:
331+ baz: foo
332+ bax: foox#[\n|]#
333+ fook:
334+ "}),
335+ "o",
336+ helpers::platform_line(indoc! {"\
337+ - top:
338+ baz: foo
339+ bax: foox
340+ #[\n|]#
341+ fook:
342+ "}),
343+ ),
344+ (
345+ helpers::platform_line(indoc! {"\
346+ top:
347+ baz:
348+ - one: two#[\n|]#
349+ three: four
350+ - top:
351+ baz: foo
352+ bax: foox
353+ "}),
354+ "o",
355+ helpers::platform_line(indoc! {"\
356+ top:
357+ baz:
358+ - one: two
359+ #[\n|]#
360+ three: four
361+ - top:
362+ baz: foo
363+ bax: foox
364+ "}),
365+ ),
366+ // yaml map without a key
367+ (
368+ helpers::platform_line(indoc! {"\
369+ top:#[\n|]#
370+ "}),
371+ "o",
372+ helpers::platform_line(indoc! {"\
373+ top:
374+ #[\n|]#
375+ "}),
376+ ),
377+ (
378+ helpers::platform_line(indoc! {"\
379+ top#[:|]#
380+ bottom: withvalue
381+ "}),
382+ "o",
383+ helpers::platform_line(indoc! {"\
384+ top:
385+ #[\n|]#
386+ bottom: withvalue
387+ "}),
388+ ),
389+ (
390+ helpers::platform_line(indoc! {"\
391+ bottom: withvalue
392+ top#[:|]#
393+ "}),
394+ "o",
395+ helpers::platform_line(indoc! {"\
396+ bottom: withvalue
397+ top:
398+ #[\n|]#
399+ "}),
400+ ),
401+ ];
402+
403+ for test in below_tests {
404+ test_with_config(app(), test).await?;
405+ }
406+
407+ let above_tests = [
408+ (
409+ helpers::platform_line(indoc! {r##"
410+ #[t|]#op:
411+ baz: foo
412+ bazi:
413+ more: yes
414+ why: because
415+ quux:
416+ - 1
417+ - 2
418+ bax: foox
419+ fook:
420+ "##}),
421+ "O",
422+ helpers::platform_line(indoc! {"\
423+ #[\n|]#
424+ top:
425+ baz: foo
426+ bazi:
427+ more: yes
428+ why: because
429+ quux:
430+ - 1
431+ - 2
432+ bax: foox
433+ fook:
434+ "}),
435+ ),
436+ (
437+ helpers::platform_line(indoc! {r##"
438+ top:
439+ b#[a|]#z: foo
440+ bazi:
441+ more: yes
442+ why: because
443+ quux:
444+ - 1
445+ - 2
446+ bax: foox
447+ fook:
448+ "##}),
449+ "O",
450+ helpers::platform_line(indoc! {"\
451+ top:
452+ #[\n|]#
453+ baz: foo
454+ bazi:
455+ more: yes
456+ why: because
457+ quux:
458+ - 1
459+ - 2
460+ bax: foox
461+ fook:
462+ "}),
463+ ),
464+ (
465+ helpers::platform_line(indoc! {r##"
466+ top:
467+ baz: foo
468+ bazi#[:|]#
469+ more: yes
470+ why: because
471+ quux:
472+ - 1
473+ - 2
474+ bax: foox
475+ fook:
476+ "##}),
477+ "O",
478+ helpers::platform_line(indoc! {"\
479+ top:
480+ baz: foo
481+ #[\n|]#
482+ bazi:
483+ more: yes
484+ why: because
485+ quux:
486+ - 1
487+ - 2
488+ bax: foox
489+ fook:
490+ "}),
491+ ),
492+ (
493+ helpers::platform_line(indoc! {r##"
494+ top:
495+ baz: foo
496+ bazi:
497+ more: #[yes|]#
498+ why: because
499+ quux:
500+ - 1
501+ - 2
502+ bax: foox
503+ fook:
504+ "##}),
505+ "O",
506+ helpers::platform_line(indoc! {"\
507+ top:
508+ baz: foo
509+ bazi:
510+ #[\n|]#
511+ more: yes
512+ why: because
513+ quux:
514+ - 1
515+ - 2
516+ bax: foox
517+ fook:
518+ "}),
519+ ),
520+ (
521+ helpers::platform_line(indoc! {r##"
522+ top:
523+ baz: foo
524+ bazi:
525+ more: yes
526+ why: becaus#[e|]#
527+ quux:
528+ - 1
529+ - 2
530+ bax: foox
531+ fook:
532+ "##}),
533+ "O",
534+ helpers::platform_line(indoc! {"\
535+ top:
536+ baz: foo
537+ bazi:
538+ more: yes
539+ #[\n|]#
540+ why: because
541+ quux:
542+ - 1
543+ - 2
544+ bax: foox
545+ fook:
546+ "}),
547+ ),
548+ (
549+ helpers::platform_line(indoc! {"\
550+ top:
551+ baz: foo
552+ bazi:
553+ more: yes
554+ why: because
555+ quux:#[\n|]#
556+ - 1
557+ - 2
558+ bax: foox
559+ fook:
560+ "}),
561+ "O",
562+ helpers::platform_line(indoc! {"\
563+ top:
564+ baz: foo
565+ bazi:
566+ more: yes
567+ why: because
568+ #[\n|]#
569+ quux:
570+ - 1
571+ - 2
572+ bax: foox
573+ fook:
574+ "}),
575+ ),
576+ (
577+ helpers::platform_line(indoc! {"\
578+ top:
579+ baz: foo
580+ bazi:
581+ more: yes
582+ why: because
583+ quux:
584+ - 1#[\n|]#
585+ - 2
586+ bax: foox
587+ fook:
588+ "}),
589+ "O",
590+ helpers::platform_line(indoc! {"\
591+ top:
592+ baz: foo
593+ bazi:
594+ more: yes
595+ why: because
596+ quux:
597+ #[\n|]#
598+ - 1
599+ - 2
600+ bax: foox
601+ fook:
602+ "}),
603+ ),
604+ (
605+ helpers::platform_line(indoc! {"\
606+ top:
607+ baz: foo
608+ bazi:
609+ more: yes
610+ why: because
611+ quux:
612+ - 1
613+ - 2
614+ bax: foox
615+ fook:#[\n|]#
616+ "}),
617+ "O",
618+ helpers::platform_line(indoc! {"\
619+ top:
620+ baz: foo
621+ bazi:
622+ more: yes
623+ why: because
624+ quux:
625+ - 1
626+ - 2
627+ bax: foox
628+ #[\n|]#
629+ fook:
630+ "}),
631+ ),
632+ (
633+ helpers::platform_line(indoc! {"\
634+ top:
635+ baz: foo
636+ bax: |
637+ some
638+ multi
639+ line
640+ string#[\n|]#
641+ fook:
642+ "}),
643+ "O",
644+ helpers::platform_line(indoc! {"\
645+ top:
646+ baz: foo
647+ bax: |
648+ some
649+ multi
650+ line
651+ #[\n|]#
652+ string
653+ fook:
654+ "}),
655+ ),
656+ (
657+ helpers::platform_line(indoc! {"\
658+ top:
659+ baz: foo
660+ bax: >
661+ some#[\n|]#
662+ multi
663+ line
664+ string
665+ fook:
666+ "}),
667+ "O",
668+ helpers::platform_line(indoc! {"\
669+ top:
670+ baz: foo
671+ bax: >
672+ #[\n|]#
673+ some
674+ multi
675+ line
676+ string
677+ fook:
678+ "}),
679+ ),
680+ (
681+ helpers::platform_line(indoc! {"\
682+ top:
683+ baz: foo
684+ bax: >
685+ fook:#[\n|]#
686+ "}),
687+ "O",
688+ helpers::platform_line(indoc! {"\
689+ top:
690+ baz: foo
691+ bax: >
692+ #[\n|]#
693+ fook:
694+ "}),
695+ ),
696+ (
697+ helpers::platform_line(indoc! {"\
698+ - top:
699+ baz: foo#[\n|]#
700+ bax: foox
701+ fook:
702+ "}),
703+ "O",
704+ helpers::platform_line(indoc! {"\
705+ - top:
706+ #[\n|]#
707+ baz: foo
708+ bax: foox
709+ fook:
710+ "}),
711+ ),
712+ (
713+ helpers::platform_line(indoc! {"\
714+ - top:
715+ baz: foo
716+ bax: foox
717+ fook:#[\n|]#
718+ "}),
719+ "O",
720+ helpers::platform_line(indoc! {"\
721+ - top:
722+ baz: foo
723+ bax: foox
724+ #[\n|]#
725+ fook:
726+ "}),
727+ ),
728+ (
729+ helpers::platform_line(indoc! {"\
730+ top:
731+ baz:
732+ - one: two#[\n|]#
733+ three: four
734+ - top:
735+ baz: foo
736+ bax: foox
737+ "}),
738+ "O",
739+ helpers::platform_line(indoc! {"\
740+ top:
741+ baz:
742+ #[\n|]#
743+ - one: two
744+ three: four
745+ - top:
746+ baz: foo
747+ bax: foox
748+ "}),
749+ ),
750+ // yaml map without a key
751+ (
752+ helpers::platform_line(indoc! {"\
753+ top:#[\n|]#
754+ "}),
755+ "O",
756+ helpers::platform_line(indoc! {"\
757+ #[\n|]#
758+ top:
759+ "}),
760+ ),
761+ (
762+ helpers::platform_line(indoc! {"\
763+ bottom: withvalue
764+ top#[:|]#
765+ "}),
766+ "O",
767+ helpers::platform_line(indoc! {"\
768+ bottom: withvalue
769+ #[\n|]#
770+ top:
771+ "}),
772+ ),
773+ (
774+ helpers::platform_line(indoc! {"\
775+ top:
776+ bottom:#[ |]#withvalue
777+ "}),
778+ "O",
779+ helpers::platform_line(indoc! {"\
780+ top:
781+ #[\n|]#
782+ bottom: withvalue
783+ "}),
784+ ),
785+ ];
786+
787+ for test in above_tests {
788+ test_with_config(app(), test).await?;
789+ }
790+
791+ let enter_tests = [
792+ (
793+ helpers::platform_line(indoc! {r##"
794+ foo: #[b|]#ar
795+ "##}),
796+ "i<ret>",
797+ helpers::platform_line(indoc! {"\
798+ foo:
799+ #[|b]#ar
800+ "}),
801+ ),
802+ (
803+ helpers::platform_line(indoc! {"\
804+ foo:#[\n|]#
805+ "}),
806+ "i<ret>",
807+ helpers::platform_line(indoc! {"\
808+ foo:
809+ #[|\n]#
810+ "}),
811+ ),
812+ ];
813+
814+ for test in enter_tests {
815+ test_with_config(app(), test).await?;
816+ }
817+
818+ Ok(())
819+}
runtime/queries/yaml/indents.scm+35−1
@@ -1,2 +1,36 @@
1−(block_mapping_pair) @indent
1+(block_scalar) @indent @extend
22
3+; indent sequence items only if they span more than one line, e.g.
4+;
5+; - foo:
6+; bar: baz
7+; - quux:
8+; bar: baz
9+;
10+; but not
11+;
12+; - foo
13+; - bar
14+; - baz
15+((block_sequence_item) @item @indent.always @extend
16+ (#not-one-line? @item))
17+
18+; map pair where without a key
19+;
20+; foo:
21+((block_mapping_pair
22+ key: (_) @key
23+ !value
24+ ) @indent.always @extend
25+)
26+
27+; map pair where the key and value are on different lines
28+;
29+; foo:
30+; bar: baz
31+((block_mapping_pair
32+ key: (_) @key
33+ value: (_) @val
34+ (#not-same-line? @key @val)
35+ ) @indent.always @extend
36+)