From 7078e8400736dce923be44a4d26f136a22640f93 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Fri, 14 Apr 2023 11:00:15 -0400 Subject: [PATCH] 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 --- helix-core/src/indent.rs | 252 +++++++- helix-term/src/commands.rs | 8 +- 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 | 36 +- 7 files changed, 1130 insertions(+), 31 deletions(-) create mode 100644 helix-term/tests/test/languages/go.rs create mode 100644 helix-term/tests/test/languages/mod.rs create mode 100644 helix-term/tests/test/languages/yaml.rs diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 3aede6b0b..5360a8064 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -237,38 +237,58 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option) -> Vec 0 && added.outdent == 0 { - self.indent += 1; - } else if added.outdent > 0 && added.indent == 0 { - self.outdent += 1; - } + self.indent += added.indent; + self.indent_always += added.indent_always; + self.outdent += added.outdent; + self.outdent_always += added.outdent_always; } + /// Add an indent capture to this indent. /// All the captures that are added in this way should be on the same line. fn add_capture(&mut self, added: IndentCaptureType) { match added { IndentCaptureType::Indent => { - self.indent = 1; + if self.indent_always == 0 { + self.indent = 1; + } + } + IndentCaptureType::IndentAlways => { + // any time we encounter an `indent.always` on the same line, we + // want to cancel out all regular indents + self.indent_always += 1; + self.indent = 0; } IndentCaptureType::Outdent => { - self.outdent = 1; + if self.outdent_always == 0 { + self.outdent = 1; + } + } + IndentCaptureType::OutdentAlways => { + self.outdent_always += 1; + self.outdent = 0; } } } + fn as_string(&self, indent_style: &IndentStyle) -> String { - let indent_level = if self.indent >= self.outdent { - self.indent - self.outdent + let indent = self.indent_always + self.indent; + let outdent = self.outdent_always + self.outdent; + + let indent_level = if indent >= outdent { + indent - outdent } else { log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent); 0 @@ -278,27 +298,32 @@ fn as_string(&self, indent_style: &IndentStyle) -> String { } /// An indent definition which corresponds to a capture from the indent query +#[derive(Debug)] struct IndentCapture { capture_type: IndentCaptureType, scope: IndentScope, } -#[derive(Clone, Copy)] + +#[derive(Debug, Clone, Copy, PartialEq)] enum IndentCaptureType { Indent, + IndentAlways, Outdent, + OutdentAlways, } + impl IndentCaptureType { fn default_scope(&self) -> IndentScope { match self { - IndentCaptureType::Indent => IndentScope::Tail, - IndentCaptureType::Outdent => IndentScope::All, + IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail, + IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All, } } } /// This defines which part of a node an [IndentCapture] applies to. /// Each [IndentCaptureType] has a default scope, but the scope can be changed /// with `#set!` property declarations. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] enum IndentScope { /// The indent applies to the whole node All, @@ -308,6 +333,7 @@ enum IndentScope { /// A capture from the indent query which does not define an indent but extends /// the range of a node. This is used before the indent is calculated. +#[derive(Debug)] enum ExtendCapture { Extend, PreventOnce, @@ -316,6 +342,7 @@ enum ExtendCapture { /// The result of running a tree-sitter indent query. This stores for /// each node (identified by its ID) the relevant captures (already filtered /// by predicates). +#[derive(Debug)] struct IndentQueryResult { indent_captures: HashMap>, extend_captures: HashMap>, @@ -334,6 +361,33 @@ fn query_indents( let mut indent_captures: HashMap> = HashMap::new(); let mut extend_captures: HashMap> = HashMap::new(); cursor.set_byte_range(range); + + let get_node_start_line = |node: Node| { + let mut node_line = node.start_position().row; + + // Adjust for the new line that will be inserted + if let Some((line, byte)) = new_line_break { + if node_line == line && node.start_byte() >= byte { + node_line += 1; + } + } + + node_line + }; + + let get_node_end_line = |node: Node| { + let mut node_line = node.end_position().row; + + // Adjust for the new line that will be inserted + if let Some((line, byte)) = new_line_break { + if node_line == line && node.end_byte() < byte { + node_line += 1; + } + } + + node_line + }; + // Iterate over all captures from the query for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) { // Skip matches where not all custom predicates are fulfilled @@ -360,21 +414,13 @@ fn query_indents( Some(QueryPredicateArg::Capture(capt1)), Some(QueryPredicateArg::Capture(capt2)) ) => { - let get_line_num = |node: Node| { - let mut node_line = node.start_position().row; - // Adjust for the new line that will be inserted - if let Some((line, byte)) = new_line_break { - if node_line==line && node.start_byte()>=byte { - node_line += 1; - } - } - node_line - }; let n1 = m.nodes_for_capture_index(*capt1).next(); let n2 = m.nodes_for_capture_index(*capt2).next(); match (n1, n2) { (Some(n1), Some(n2)) => { - let same_line = get_line_num(n1)==get_line_num(n2); + let n1_line = get_node_start_line(n1); + let n2_line = get_node_start_line(n2); + let same_line = n1_line == n2_line; same_line==(pred.operator.as_ref()=="same-line?") } _ => true, @@ -385,6 +431,23 @@ fn query_indents( } } } + "one-line?" | "not-one-line?" => match pred.args.get(0) { + Some(QueryPredicateArg::Capture(capture_idx)) => { + let node = m.nodes_for_capture_index(*capture_idx).next(); + + match node { + Some(node) => { + let (start_line, end_line) = (get_node_start_line(node), get_node_end_line(node)); + let one_line = end_line == start_line; + one_line != (pred.operator.as_ref() == "not-one-line?") + }, + _ => true, + } + } + _ => { + panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string"); + } + }, _ => { panic!( "Invalid indent query: Unknown predicate (\"{}\")", @@ -399,7 +462,9 @@ fn query_indents( let capture_name = query.capture_names()[capture.index as usize].as_str(); let capture_type = match capture_name { "indent" => IndentCaptureType::Indent, + "indent.always" => IndentCaptureType::IndentAlways, "outdent" => IndentCaptureType::Outdent, + "outdent.always" => IndentCaptureType::OutdentAlways, "extend" => { extend_captures .entry(capture.node.id()) @@ -456,10 +521,15 @@ fn query_indents( .push(indent_capture); } } - IndentQueryResult { + + let result = IndentQueryResult { indent_captures, extend_captures, - } + }; + + log::trace!("indent result = {:?}", result); + + result } /// 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( .tree() .root_node() .descendant_for_byte_range(byte_pos, byte_pos)?; + let (query_result, deepest_preceding) = { // The query range should intersect with all nodes directly preceding // the position of the indent query in case one of them is extended. @@ -642,10 +713,12 @@ pub fn treesitter_indent_for_pos( // even if there are multiple "indent" nodes on the same line let mut indent_for_line = Indentation::default(); let mut indent_for_line_below = Indentation::default(); + loop { // This can safely be unwrapped because `first_in_line` contains // one entry for each ancestor of the node (which is what we iterate over) let is_first = *first_in_line.last().unwrap(); + // Apply all indent definitions for this node if let Some(definitions) = indent_captures.get(&node.id()) { for definition in definitions { @@ -667,6 +740,7 @@ pub fn treesitter_indent_for_pos( if let Some(parent) = node.parent() { let mut node_line = node.start_position().row; let mut parent_line = parent.start_position().row; + if node_line == line && new_line { // Also consider the line that will be inserted if node.start_byte() >= byte_pos { @@ -676,17 +750,20 @@ pub fn treesitter_indent_for_pos( parent_line += 1; } }; + if node_line != parent_line { + // Don't add indent for the line below the line of the query if node_line < line + (new_line as usize) { - // Don't add indent for the line below the line of the query result.add_line(&indent_for_line_below); } + if node_line == parent_line + 1 { indent_for_line_below = indent_for_line; } else { result.add_line(&indent_for_line); indent_for_line_below = Indentation::default(); } + indent_for_line = Indentation::default(); } @@ -700,6 +777,7 @@ pub fn treesitter_indent_for_pos( { result.add_line(&indent_for_line_below); } + result.add_line(&indent_for_line); break; } @@ -810,4 +888,122 @@ fn test_large_indent_level() { 2 ); } + + #[test] + fn add_capture() { + let indent = || Indentation { + indent: 1, + ..Default::default() + }; + let indent_always = || Indentation { + indent_always: 1, + ..Default::default() + }; + let outdent = || Indentation { + outdent: 1, + ..Default::default() + }; + let outdent_always = || Indentation { + outdent_always: 1, + ..Default::default() + }; + + let add_capture = |mut indent: Indentation, capture| { + indent.add_capture(capture); + indent + }; + + // adding an indent to no indent makes an indent + assert_eq!( + indent(), + add_capture(Indentation::default(), IndentCaptureType::Indent) + ); + assert_eq!( + indent_always(), + add_capture(Indentation::default(), IndentCaptureType::IndentAlways) + ); + assert_eq!( + outdent(), + add_capture(Indentation::default(), IndentCaptureType::Outdent) + ); + assert_eq!( + outdent_always(), + add_capture(Indentation::default(), IndentCaptureType::OutdentAlways) + ); + + // adding an indent to an already indented has no effect + assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent)); + assert_eq!( + outdent(), + add_capture(outdent(), IndentCaptureType::Outdent) + ); + + // adding an always to a regular makes it always + assert_eq!( + indent_always(), + add_capture(indent(), IndentCaptureType::IndentAlways) + ); + assert_eq!( + outdent_always(), + add_capture(outdent(), IndentCaptureType::OutdentAlways) + ); + + // adding an always to an always is additive + assert_eq!( + Indentation { + indent_always: 2, + ..Default::default() + }, + add_capture(indent_always(), IndentCaptureType::IndentAlways) + ); + assert_eq!( + Indentation { + outdent_always: 2, + ..Default::default() + }, + add_capture(outdent_always(), IndentCaptureType::OutdentAlways) + ); + + // adding regular to always should be associative + assert_eq!( + Indentation { + indent_always: 1, + ..Default::default() + }, + add_capture( + add_capture(indent(), IndentCaptureType::Indent), + IndentCaptureType::IndentAlways + ) + ); + assert_eq!( + Indentation { + indent_always: 1, + ..Default::default() + }, + add_capture( + add_capture(indent(), IndentCaptureType::IndentAlways), + IndentCaptureType::Indent + ) + ); + assert_eq!( + Indentation { + outdent_always: 1, + ..Default::default() + }, + add_capture( + add_capture(outdent(), IndentCaptureType::Outdent), + IndentCaptureType::OutdentAlways + ) + ); + assert_eq!( + Indentation { + outdent_always: 1, + ..Default::default() + }, + add_capture( + add_capture(outdent(), IndentCaptureType::OutdentAlways), + IndentCaptureType::Outdent + ) + ); + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8be0f83a8..61c647d0b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2999,6 +2999,7 @@ fn open(cx: &mut Context, open: Open) { Open::Below => graphemes::prev_grapheme_boundary(text, range.to()), Open::Above => range.from(), }); + let new_line = match open { // adjust position to the end of the line (next line - 1) Open::Below => cursor_line + 1, @@ -3006,13 +3007,15 @@ fn open(cx: &mut Context, open: Open) { Open::Above => cursor_line, }; + let line_num = new_line.saturating_sub(1); + // Index to insert newlines after, as well as the char width // to use to compensate for those inserted newlines. let (line_end_index, line_end_offset_width) = if new_line == 0 { (0, 0) } else { ( - line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)), + line_end_char_index(&text, line_num), doc.line_ending.len_chars(), ) }; @@ -3023,10 +3026,11 @@ fn open(cx: &mut Context, open: Open) { &doc.indent_style, doc.tab_width(), text, - new_line.saturating_sub(1), + line_num, line_end_index, cursor_line, ); + let indent_len = indent.len(); let mut text = String::with_capacity(1 + indent_len); text.push_str(doc.line_ending.as_str()); diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index a62f00660..9c0e6bbc7 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -18,6 +18,7 @@ async fn hello_world() -> anyhow::Result<()> { mod auto_indent; mod auto_pairs; mod commands; + mod languages; mod movement; mod picker; mod prompt; diff --git a/helix-term/tests/test/languages/go.rs b/helix-term/tests/test/languages/go.rs new file mode 100644 index 000000000..7bb3651e6 --- /dev/null +++ b/helix-term/tests/test/languages/go.rs @@ -0,0 +1,41 @@ +use super::*; + +#[tokio::test(flavor = "multi_thread")] +async fn auto_indent() -> anyhow::Result<()> { + let app = || AppBuilder::new().with_file("foo.go", None); + + let enter_tests = [ + ( + helpers::platform_line(indoc! {r##" + type Test struct {#[}|]# + "##}), + "i", + helpers::platform_line(indoc! {"\ + type Test struct { + \t#[|\n]# + } + "}), + ), + ( + helpers::platform_line(indoc! {"\ + func main() { + \tswitch nil {#[}|]# + } + "}), + "i", + helpers::platform_line(indoc! {"\ + func main() { + \tswitch nil { + \t\t#[|\n]# + \t} + } + "}), + ), + ]; + + for test in enter_tests { + test_with_config(app(), test).await?; + } + + Ok(()) +} diff --git a/helix-term/tests/test/languages/mod.rs b/helix-term/tests/test/languages/mod.rs new file mode 100644 index 000000000..51c23db62 --- /dev/null +++ b/helix-term/tests/test/languages/mod.rs @@ -0,0 +1,4 @@ +use super::*; + +mod go; +mod yaml; diff --git a/helix-term/tests/test/languages/yaml.rs b/helix-term/tests/test/languages/yaml.rs new file mode 100644 index 000000000..7669e8a27 --- /dev/null +++ b/helix-term/tests/test/languages/yaml.rs @@ -0,0 +1,819 @@ +use super::*; + +#[tokio::test(flavor = "multi_thread")] +async fn auto_indent() -> anyhow::Result<()> { + let app = || AppBuilder::new().with_file("foo.yaml", None); + + let below_tests = [ + ( + helpers::platform_line(indoc! {r##" + #[t|]#op: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "o", + helpers::platform_line(indoc! {"\ + top: + #[\n|]# + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + b#[a|]#z: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + #[\n|]# + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi#[:|]# + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + #[\n|]# + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi: + more: #[yes|]# + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + #[\n|]# + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi: + more: yes + why: becaus#[e|]# + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + #[\n|]# + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux:#[\n|]# + - 1 + - 2 + bax: foox + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + #[\n|]# + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1#[\n|]# + - 2 + bax: foox + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + #[\n|]# + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook:#[\n|]# + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + #[\n|]# + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: | + some + multi + line + string#[\n|]# + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: | + some + multi + line + string + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + some + multi + line#[\n|]# + string + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + some + multi + line + #[\n|]# + string + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: >#[\n|]# + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + - top:#[\n|]# + baz: foo + bax: foox + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + - top: + #[\n|]# + baz: foo + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + - top: + baz: foo#[\n|]# + bax: foox + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + - top: + baz: foo + #[\n|]# + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + - top: + baz: foo + bax: foox#[\n|]# + fook: + "}), + "o", + helpers::platform_line(indoc! {"\ + - top: + baz: foo + bax: foox + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: + - one: two#[\n|]# + three: four + - top: + baz: foo + bax: foox + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + baz: + - one: two + #[\n|]# + three: four + - top: + baz: foo + bax: foox + "}), + ), + // yaml map without a key + ( + helpers::platform_line(indoc! {"\ + top:#[\n|]# + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + #[\n|]# + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top#[:|]# + bottom: withvalue + "}), + "o", + helpers::platform_line(indoc! {"\ + top: + #[\n|]# + bottom: withvalue + "}), + ), + ( + helpers::platform_line(indoc! {"\ + bottom: withvalue + top#[:|]# + "}), + "o", + helpers::platform_line(indoc! {"\ + bottom: withvalue + top: + #[\n|]# + "}), + ), + ]; + + for test in below_tests { + test_with_config(app(), test).await?; + } + + let above_tests = [ + ( + helpers::platform_line(indoc! {r##" + #[t|]#op: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "O", + helpers::platform_line(indoc! {"\ + #[\n|]# + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + b#[a|]#z: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "O", + helpers::platform_line(indoc! {"\ + top: + #[\n|]# + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi#[:|]# + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + #[\n|]# + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi: + more: #[yes|]# + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + #[\n|]# + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {r##" + top: + baz: foo + bazi: + more: yes + why: becaus#[e|]# + quux: + - 1 + - 2 + bax: foox + fook: + "##}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + #[\n|]# + why: because + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux:#[\n|]# + - 1 + - 2 + bax: foox + fook: + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + #[\n|]# + quux: + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1#[\n|]# + - 2 + bax: foox + fook: + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + #[\n|]# + - 1 + - 2 + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + fook:#[\n|]# + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bazi: + more: yes + why: because + quux: + - 1 + - 2 + bax: foox + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: | + some + multi + line + string#[\n|]# + fook: + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: | + some + multi + line + #[\n|]# + string + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + some#[\n|]# + multi + line + string + fook: + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + #[\n|]# + some + multi + line + string + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + fook:#[\n|]# + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: foo + bax: > + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + - top: + baz: foo#[\n|]# + bax: foox + fook: + "}), + "O", + helpers::platform_line(indoc! {"\ + - top: + #[\n|]# + baz: foo + bax: foox + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + - top: + baz: foo + bax: foox + fook:#[\n|]# + "}), + "O", + helpers::platform_line(indoc! {"\ + - top: + baz: foo + bax: foox + #[\n|]# + fook: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + baz: + - one: two#[\n|]# + three: four + - top: + baz: foo + bax: foox + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + baz: + #[\n|]# + - one: two + three: four + - top: + baz: foo + bax: foox + "}), + ), + // yaml map without a key + ( + helpers::platform_line(indoc! {"\ + top:#[\n|]# + "}), + "O", + helpers::platform_line(indoc! {"\ + #[\n|]# + top: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + bottom: withvalue + top#[:|]# + "}), + "O", + helpers::platform_line(indoc! {"\ + bottom: withvalue + #[\n|]# + top: + "}), + ), + ( + helpers::platform_line(indoc! {"\ + top: + bottom:#[ |]#withvalue + "}), + "O", + helpers::platform_line(indoc! {"\ + top: + #[\n|]# + bottom: withvalue + "}), + ), + ]; + + for test in above_tests { + test_with_config(app(), test).await?; + } + + let enter_tests = [ + ( + helpers::platform_line(indoc! {r##" + foo: #[b|]#ar + "##}), + "i", + helpers::platform_line(indoc! {"\ + foo: + #[|b]#ar + "}), + ), + ( + helpers::platform_line(indoc! {"\ + foo:#[\n|]# + "}), + "i", + helpers::platform_line(indoc! {"\ + foo: + #[|\n]# + "}), + ), + ]; + + for test in enter_tests { + test_with_config(app(), test).await?; + } + + Ok(()) +} diff --git a/runtime/queries/yaml/indents.scm b/runtime/queries/yaml/indents.scm index 70a00b695..87853eb6b 100644 --- a/runtime/queries/yaml/indents.scm +++ b/runtime/queries/yaml/indents.scm @@ -1,2 +1,36 @@ -(block_mapping_pair) @indent +(block_scalar) @indent @extend +; indent sequence items only if they span more than one line, e.g. +; +; - foo: +; bar: baz +; - quux: +; bar: baz +; +; but not +; +; - foo +; - bar +; - baz +((block_sequence_item) @item @indent.always @extend + (#not-one-line? @item)) + +; map pair where without a key +; +; foo: +((block_mapping_pair + key: (_) @key + !value + ) @indent.always @extend +) + +; map pair where the key and value are on different lines +; +; foo: +; bar: baz +((block_mapping_pair + key: (_) @key + value: (_) @val + (#not-same-line? @key @val) + ) @indent.always @extend +) \ No newline at end of file