aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSkyler Hawthorne2023-04-14 15:00:15 +0000
committerBlaž Hrastnik2023-08-10 21:22:22 +0000
commit7078e8400736dce923be44a4d26f136a22640f93 (patch)
treee1f9d821b605c5809d3210a79b91922c6edeb06b
parent57f093d83641642ad5d4ba42ae59f03272efcfcc (diff)
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
-rw-r--r--helix-core/src/indent.rs252
-rw-r--r--helix-term/src/commands.rs8
-rw-r--r--helix-term/tests/integration.rs1
-rw-r--r--helix-term/tests/test/languages/go.rs41
-rw-r--r--helix-term/tests/test/languages/mod.rs4
-rw-r--r--helix-term/tests/test/languages/yaml.rs819
-rw-r--r--runtime/queries/yaml/indents.scm36
7 files changed, 1130 insertions, 31 deletions
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index 3aede6b0..5360a806 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<usize>) -> Vec<bo
/// This is usually constructed in one of 2 ways:
/// - Successively add indent captures to get the (added) indent from a single line
/// - Successively add the indent results for each line
-#[derive(Default)]
+#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Indentation {
/// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
/// The string that this results in depends on the indent style (spaces or tabs, etc.)
indent: usize,
+ indent_always: usize,
outdent: usize,
+ outdent_always: usize,
}
impl Indentation {
/// Add some other [Indentation] to this.
/// The added indent should be the total added indent from one line
fn add_line(&mut self, added: &Indentation) {
- if added.indent > 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 @@ impl Indentation {
}
/// 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<usize, Vec<IndentCapture>>,
extend_captures: HashMap<usize, Vec<ExtendCapture>>,
@@ -334,6 +361,33 @@ fn query_indents(
let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = 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 @@ mod test {
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 8be0f83a..61c647d0 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 a62f0066..9c0e6bbc 100644
--- a/helix-term/tests/integration.rs
+++ b/helix-term/tests/integration.rs
@@ -18,6 +18,7 @@ mod test {
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 00000000..7bb3651e
--- /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<ret>",
+ helpers::platform_line(indoc! {"\
+ type Test struct {
+ \t#[|\n]#
+ }
+ "}),
+ ),
+ (
+ helpers::platform_line(indoc! {"\
+ func main() {
+ \tswitch nil {#[}|]#
+ }
+ "}),
+ "i<ret>",
+ 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 00000000..51c23db6
--- /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 00000000..7669e8a2
--- /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<ret>",
+ helpers::platform_line(indoc! {"\
+ foo:
+ #[|b]#ar
+ "}),
+ ),
+ (
+ helpers::platform_line(indoc! {"\
+ foo:#[\n|]#
+ "}),
+ "i<ret>",
+ 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 70a00b69..87853eb6 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