From 58758fee610a3808dfaeafddd1b4b4242a7e42cd Mon Sep 17 00:00:00 2001 From: Triton171 Date: Wed, 30 Mar 2022 17:08:07 +0200 Subject: [PATCH] Indentation rework (#1562) * WIP: Rework indentation system * Add ComplexNode for context-aware indentation (including a proof of concept for assignment statements in rust) * Add switch statements to Go indents.toml (fixes the second half of issue #1523) Remove commented-out code * Migrate all existing indentation queries. Add more options to ComplexNode and use them to improve C/C++ indentation. * Add comments & replace Option> with Vec<_> * Add more detailed documentation for tree-sitter indentation * Improve code style in indent.rs * Use tree-sitter queries for indentation instead of TOML config. Migrate existing indent queries. * Add documentation for the new indent queries. Change xtask docgen to look for indents.scm instead of indents.toml * Improve code style in indent.rs. Fix an issue with the rust indent query. * Move indentation test sources to separate files. Add `#not-kind-eq?`, `#same-line?` and `#not-same-line` custom predicates. Improve the rust and c indent queries. * Fix indent test. Improve rust indent queries. * Move indentation tests to integration test folder. * Improve code style in indent.rs. Reuse tree-sitter cursors for indentation queries. * Migrate HCL indent query * Replace custom loading in indent tests with a designated languages.toml * Update indent query file name for --health command. * Fix single-space formatting in indent queries. * Add explanation for unwrapping. Co-authored-by: Triton171 --- book/src/SUMMARY.md | 1 + book/src/guides/indent.md | 79 +++ helix-core/src/indent.rs | 594 ++++++++++++-------- helix-core/src/syntax.rs | 31 +- helix-core/tests/data/indent/indent.rs | 1 + helix-core/tests/data/indent/languages.toml | 13 + helix-core/tests/data/indent/rust.rs | 105 ++++ helix-core/tests/indent.rs | 68 +++ helix-term/src/commands.rs | 28 +- helix-term/src/health.rs | 2 +- runtime/queries/c/indents.scm | 33 ++ runtime/queries/c/indents.toml | 16 - runtime/queries/cmake/indents.scm | 10 + runtime/queries/cmake/indents.toml | 12 - runtime/queries/cpp/indents.scm | 3 + runtime/queries/cpp/indents.toml | 17 - runtime/queries/dart/indents.scm | 20 + runtime/queries/dart/indents.toml | 20 - runtime/queries/fish/indents.scm | 12 + runtime/queries/fish/indents.toml | 12 - runtime/queries/glsl/indents.scm | 19 + runtime/queries/glsl/indents.toml | 19 - runtime/queries/go/indents.scm | 26 + runtime/queries/go/indents.toml | 30 - runtime/queries/hcl/indents.scm | 13 + runtime/queries/hcl/indents.toml | 13 - runtime/queries/javascript/indents.scm | 22 + runtime/queries/javascript/indents.toml | 28 - runtime/queries/json/indents.scm | 9 + runtime/queries/json/indents.toml | 9 - runtime/queries/llvm-mir-yaml/indents.scm | 2 + runtime/queries/llvm-mir-yaml/indents.toml | 3 - runtime/queries/llvm-mir/indents.scm | 3 + runtime/queries/llvm-mir/indents.toml | 7 - runtime/queries/llvm/indents.scm | 6 + runtime/queries/llvm/indents.toml | 8 - runtime/queries/lua/indents.scm | 24 + runtime/queries/lua/indents.toml | 24 - runtime/queries/nix/indents.scm | 18 + runtime/queries/nix/indents.toml | 18 - runtime/queries/ocaml/indents.scm | 12 + runtime/queries/ocaml/indents.toml | 13 - runtime/queries/perl/indents.scm | 15 + runtime/queries/perl/indents.toml | 17 - runtime/queries/php/indents.scm | 17 + runtime/queries/php/indents.toml | 17 - runtime/queries/protobuf/indents.scm | 11 + runtime/queries/protobuf/indents.toml | 12 - runtime/queries/python/indents.scm | 38 ++ runtime/queries/python/indents.toml | 39 -- runtime/queries/ruby/indents.scm | 25 + runtime/queries/ruby/indents.toml | 25 - runtime/queries/rust/indents.scm | 80 +++ runtime/queries/rust/indents.toml | 28 - runtime/queries/scala/indents.scm | 22 + runtime/queries/scala/indents.toml | 23 - runtime/queries/svelte/indents.scm | 17 + runtime/queries/svelte/indents.toml | 18 - runtime/queries/tablegen/indents.scm | 3 + runtime/queries/tablegen/indents.toml | 7 - runtime/queries/typescript/indents.scm | 7 + runtime/queries/typescript/indents.toml | 1 - runtime/queries/yaml/indents.scm | 2 + runtime/queries/yaml/indents.toml | 3 - runtime/queries/zig/indents.scm | 16 + runtime/queries/zig/indents.toml | 16 - 66 files changed, 1149 insertions(+), 713 deletions(-) create mode 100644 book/src/guides/indent.md create mode 120000 helix-core/tests/data/indent/indent.rs create mode 100644 helix-core/tests/data/indent/languages.toml create mode 100644 helix-core/tests/data/indent/rust.rs create mode 100644 helix-core/tests/indent.rs create mode 100644 runtime/queries/c/indents.scm delete mode 100644 runtime/queries/c/indents.toml create mode 100644 runtime/queries/cmake/indents.scm delete mode 100644 runtime/queries/cmake/indents.toml create mode 100644 runtime/queries/cpp/indents.scm delete mode 100644 runtime/queries/cpp/indents.toml create mode 100644 runtime/queries/dart/indents.scm delete mode 100644 runtime/queries/dart/indents.toml create mode 100644 runtime/queries/fish/indents.scm delete mode 100644 runtime/queries/fish/indents.toml create mode 100644 runtime/queries/glsl/indents.scm delete mode 100644 runtime/queries/glsl/indents.toml create mode 100644 runtime/queries/go/indents.scm delete mode 100644 runtime/queries/go/indents.toml create mode 100644 runtime/queries/hcl/indents.scm delete mode 100644 runtime/queries/hcl/indents.toml create mode 100644 runtime/queries/javascript/indents.scm delete mode 100644 runtime/queries/javascript/indents.toml create mode 100644 runtime/queries/json/indents.scm delete mode 100644 runtime/queries/json/indents.toml create mode 100644 runtime/queries/llvm-mir-yaml/indents.scm delete mode 100644 runtime/queries/llvm-mir-yaml/indents.toml create mode 100644 runtime/queries/llvm-mir/indents.scm delete mode 100644 runtime/queries/llvm-mir/indents.toml create mode 100644 runtime/queries/llvm/indents.scm delete mode 100644 runtime/queries/llvm/indents.toml create mode 100644 runtime/queries/lua/indents.scm delete mode 100644 runtime/queries/lua/indents.toml create mode 100644 runtime/queries/nix/indents.scm delete mode 100644 runtime/queries/nix/indents.toml create mode 100644 runtime/queries/ocaml/indents.scm delete mode 100644 runtime/queries/ocaml/indents.toml create mode 100644 runtime/queries/perl/indents.scm delete mode 100644 runtime/queries/perl/indents.toml create mode 100644 runtime/queries/php/indents.scm delete mode 100644 runtime/queries/php/indents.toml create mode 100644 runtime/queries/protobuf/indents.scm delete mode 100644 runtime/queries/protobuf/indents.toml create mode 100644 runtime/queries/python/indents.scm delete mode 100644 runtime/queries/python/indents.toml create mode 100644 runtime/queries/ruby/indents.scm delete mode 100644 runtime/queries/ruby/indents.toml create mode 100644 runtime/queries/rust/indents.scm delete mode 100644 runtime/queries/rust/indents.toml create mode 100644 runtime/queries/scala/indents.scm delete mode 100644 runtime/queries/scala/indents.toml create mode 100644 runtime/queries/svelte/indents.scm delete mode 100644 runtime/queries/svelte/indents.toml create mode 100644 runtime/queries/tablegen/indents.scm delete mode 100644 runtime/queries/tablegen/indents.toml create mode 100644 runtime/queries/typescript/indents.scm delete mode 120000 runtime/queries/typescript/indents.toml create mode 100644 runtime/queries/yaml/indents.scm delete mode 100644 runtime/queries/yaml/indents.toml create mode 100644 runtime/queries/zig/indents.scm delete mode 100644 runtime/queries/zig/indents.toml diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 9e15eee33d0d..ef214b12a765 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -16,3 +16,4 @@ - [Guides](./guides/README.md) - [Adding Languages](./guides/adding_languages.md) - [Adding Textobject Queries](./guides/textobject.md) + - [Adding Indent Queries](./guides/indent.md) diff --git a/book/src/guides/indent.md b/book/src/guides/indent.md new file mode 100644 index 000000000000..235a30c442ea --- /dev/null +++ b/book/src/guides/indent.md @@ -0,0 +1,79 @@ +# Adding Indent Queries + +Helix uses tree-sitter to correctly indent new lines. This requires +a tree-sitter grammar and an `indent.scm` query file placed in +`runtime/queries/{language}/indents.scm`. The indentation for a line +is calculated by traversing the syntax tree from the lowest node at the +beginning of the new line. Each of these nodes contributes to the total +indent when it is captured by the query (in what way depends on the name +of the capture). + +Note that it matters where these added indents begin. For example, +multiple indent level increases that start on the same line only increase +the total indent level by 1. + +## Scopes + +Added indents don't always apply to the whole node. For example, in most +cases when a node should be indented, we actually only want everything +except for its first line to be indented. For this, there are several +scopes (more scopes may be added in the future if required): + +- `all`: +This scope applies to the whole captured node. This is only different from +`tail` when the captured node is the first node on its line. + +- `tail`: +This scope applies to everything except for the first line of the +captured node. + +Every capture type has a default scope which should do the right thing +in most situations. When a different scope is required, this can be +changed by using a `#set!` declaration anywhere in the pattern: +```scm +(assignment_expression + right: (_) @indent + (#set! "scope" "all")) +``` + +## Capture Types + +- `@indent` (default scope `tail`): +Increase the indent level by 1. Multiple occurences in the same line +don't stack. If there is at least one `@indent` and one `@outdent` +capture on the same line, the indent level isn't changed at all. + +- `@outdent` (default scope `all`): +Decrease the indent level by 1. The same rules as for `@indent` apply. + +## Predicates + +In some cases, an S-expression cannot express exactly what pattern should be matched. +For that, tree-sitter allows for predicates to appear anywhere within a pattern, +similar to how `#set!` declarations work: +```scm +(some_kind + (child_kind) @indent + (#predicate? arg1 arg2 ...) +) +``` +The number of arguments depends on the predicate that's used. +Each argument is either a capture (`@name`) or a string (`"some string"`). +The following predicates are supported by tree-sitter: + +- `#eq?`/`#not-eq?`: +The first argument (a capture) must/must not be equal to the second argument +(a capture or a string). + +- `#match?`/`#not-match?`: +The first argument (a capture) must/must not match the regex given in the +second argument (a string). + +Additionally, we support some custom predicates for indent queries: + +- `#not-kind-eq?`: +The kind of the first argument (a capture) must not be equal to the second +argument (a string). + +- `#same-line?`/`#not-same-line?`: +The captures given by the 2 arguments must/must not start on the same line. diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 30f4a3405d9c..529139b81e25 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -1,6 +1,10 @@ +use std::collections::HashMap; + +use tree_sitter::{Query, QueryCursor, QueryPredicateArg}; + use crate::{ chars::{char_is_line_ending, char_is_whitespace}, - syntax::{IndentQuery, LanguageConfiguration, Syntax}, + syntax::{LanguageConfiguration, RopeProvider, Syntax}, tree_sitter::Node, Rope, RopeSlice, }; @@ -186,103 +190,405 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize { len / tab_width } -/// Find the highest syntax node at position. -/// This is to identify the column where this node (e.g., an HTML closing tag) ends. -fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option { - let tree = syntax.tree(); - - // named_descendant - let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?; - - while let Some(parent) = node.parent() { - if parent.start_byte() == node.start_byte() { - node = parent +/// Computes for node and all ancestors whether they are the first node on their line. +/// The first entry in the return value represents the root node, the last one the node itself +fn get_first_in_line(mut node: Node, byte_pos: usize, new_line: bool) -> Vec { + let mut first_in_line = Vec::new(); + loop { + if let Some(prev) = node.prev_sibling() { + // If we insert a new line, the first node at/after the cursor is considered to be the first in its line + let first = prev.end_position().row != node.start_position().row + || (new_line && node.start_byte() >= byte_pos && prev.start_byte() < byte_pos); + first_in_line.push(Some(first)); + } else { + // Nodes that have no previous siblings are first in their line if and only if their parent is + // (which we don't know yet) + first_in_line.push(None); + } + if let Some(parent) = node.parent() { + node = parent; } else { break; } } - Some(node) + let mut result = Vec::with_capacity(first_in_line.len()); + let mut parent_is_first = true; // The root node is by definition the first node in its line + for first in first_in_line.into_iter().rev() { + if let Some(first) = first { + result.push(first); + parent_is_first = first; + } else { + result.push(parent_is_first); + } + } + result } -/// Calculate the indentation at a given treesitter node. -/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies). -/// This is because the indentation is only increased starting at the second line of the node. -fn calculate_indentation( - query: &IndentQuery, - node: Option, - line: usize, - newline: bool, -) -> usize { - let mut increment: isize = 0; - - let mut node = match node { - Some(node) => node, - None => return 0, - }; +/// The total indent for some line of code. +/// 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)] +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, + outdent: usize, +} +impl Indentation { + /// Add some other [IndentResult] 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; + } + } + /// 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; + } + IndentCaptureType::Outdent => { + self.outdent = 1; + } + } + } + fn as_string(&self, indent_style: &IndentStyle) -> String { + let indent_level = if self.indent >= self.outdent { + self.indent - self.outdent + } else { + log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent); + 0 + }; + indent_style.as_str().repeat(indent_level) + } +} - let mut current_line = line; - let mut consider_indent = newline; - let mut increment_from_line: isize = 0; +/// An indent definition which corresponds to a capture from the indent query +struct IndentCapture { + capture_type: IndentCaptureType, + scope: IndentScope, +} +#[derive(Clone, Copy)] +enum IndentCaptureType { + Indent, + Outdent, +} +impl IndentCaptureType { + fn default_scope(&self) -> IndentScope { + match self { + IndentCaptureType::Indent => IndentScope::Tail, + IndentCaptureType::Outdent => 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)] +enum IndentScope { + /// The indent applies to the whole node + All, + /// The indent applies to everything except for the first line of the node + Tail, +} - loop { - let node_kind = node.kind(); - let start = node.start_position().row; - if current_line != start { - // Indent/dedent by at most one per line: - // .map(|a| { <-- ({ is two scopes - // let len = 1; <-- indents one level - // }) <-- }) is two scopes - if consider_indent || increment_from_line < 0 { - increment += increment_from_line.signum(); +/// Execute the indent query. +/// Returns for each node (identified by its id) a list of indent captures for that node. +fn query_indents( + query: &Query, + syntax: &Syntax, + cursor: &mut QueryCursor, + text: RopeSlice, + range: std::ops::Range, + // Position of the (optional) newly inserted line break. + // Given as (line, byte_pos) + new_line_break: Option<(usize, usize)>, +) -> HashMap> { + let mut indent_captures: HashMap> = HashMap::new(); + cursor.set_byte_range(range); + // 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 + if !query.general_predicates(m.pattern_index).iter().all(|pred| { + match pred.operator.as_ref() { + "not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) { + ( + Some(QueryPredicateArg::Capture(capture_idx)), + Some(QueryPredicateArg::String(kind)), + ) => { + let node = m.nodes_for_capture_index(*capture_idx).next(); + match node { + Some(node) => node.kind()!=kind.as_ref(), + _ => true, + } + } + _ => { + panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string"); + } + }, + "same-line?" | "not-same-line?" => { + match (pred.args.get(0), pred.args.get(1)) { + ( + 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); + same_line==(pred.operator.as_ref()=="same-line?") + } + _ => true, + } + } + _ => { + panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator); + } + } + } + _ => { + panic!( + "Invalid indent query: Unknown predicate (\"{}\")", + pred.operator + ); + } } - increment_from_line = 0; - current_line = start; - consider_indent = true; + }) { + continue; } - - if query.outdent.contains(node_kind) { - increment_from_line -= 1; + for capture in m.captures { + let capture_type = query.capture_names()[capture.index as usize].as_str(); + let capture_type = match capture_type { + "indent" => IndentCaptureType::Indent, + "outdent" => IndentCaptureType::Outdent, + _ => { + // Ignore any unknown captures (these may be needed for predicates such as #match?) + continue; + } + }; + let scope = capture_type.default_scope(); + let mut indent_capture = IndentCapture { + capture_type, + scope, + }; + // Apply additional settings for this capture + for property in query.property_settings(m.pattern_index) { + match property.key.as_ref() { + "scope" => { + indent_capture.scope = match property.value.as_deref() { + Some("all") => IndentScope::All, + Some("tail") => IndentScope::Tail, + Some(s) => { + panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s); + } + None => { + panic!( + "Invalid indent query: Missing value for \"scope\" property" + ); + } + } + } + _ => { + panic!( + "Invalid indent query: Unknown property \"{}\"", + property.key + ); + } + } + } + indent_captures + .entry(capture.node.id()) + // Most entries only need to contain a single IndentCapture + .or_insert_with(|| Vec::with_capacity(1)) + .push(indent_capture); } - if query.indent.contains(node_kind) { - increment_from_line += 1; + } + indent_captures +} + +/// Use the syntax tree to determine the indentation for a given position. +/// This can be used in 2 ways: +/// +/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation. +/// - In this case, pos should be inside the first tree-sitter node on that line. +/// In most cases, this can just be the first non-whitespace on that line. +/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line +/// after pos were moved to a new line. +/// +/// The indentation is determined by traversing all the tree-sitter nodes containing the position. +/// Each of these nodes produces some [AddedIndent] for: +/// +/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line. +/// - The line after the node. This is defined by: +/// - The scope `tail`. +/// - The scope `all` if this node is not the first node on its line. +/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node. +/// The indents from different nodes for the same line are then combined. +/// The [IndentResult] is simply the sum of the [AddedIndent] for all lines. +/// +/// Specifying which line exactly an [AddedIndent] applies to is important because indents on the same line combine differently than indents on different lines: +/// ```ignore +/// some_function(|| { +/// // Both the function parameters as well as the contained block should be indented. +/// // Because they are on the same line, this only yields one indent level +/// }); +/// ``` +/// +/// ```ignore +/// some_function( +/// parm1, +/// || { +/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines +/// }, +/// ); +/// ``` +pub fn treesitter_indent_for_pos( + query: &Query, + syntax: &Syntax, + indent_style: &IndentStyle, + text: RopeSlice, + line: usize, + pos: usize, + new_line: bool, +) -> Option { + let byte_pos = text.char_to_byte(pos); + let mut node = syntax + .tree() + .root_node() + .descendant_for_byte_range(byte_pos, byte_pos)?; + let mut first_in_line = get_first_in_line(node, byte_pos, new_line); + let new_line_break = if new_line { + Some((line, byte_pos)) + } else { + None + }; + let query_result = crate::syntax::PARSER.with(|ts_parser| { + let mut ts_parser = ts_parser.borrow_mut(); + let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new); + let query_result = query_indents( + query, + syntax, + &mut cursor, + text, + byte_pos..byte_pos + 1, + new_line_break, + ); + ts_parser.cursors.push(cursor); + query_result + }); + + let mut result = Indentation::default(); + // We always keep track of all the indent changes on one line, in order to only indent once + // 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) = query_result.get(&node.id()) { + for definition in definitions { + match definition.scope { + IndentScope::All => { + if is_first { + indent_for_line.add_capture(definition.capture_type); + } else { + indent_for_line_below.add_capture(definition.capture_type); + } + } + IndentScope::Tail => { + indent_for_line_below.add_capture(definition.capture_type); + } + } + } } 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 { + node_line += 1; + } + if parent.start_byte() >= byte_pos { + parent_line += 1; + } + }; + if node_line != parent_line { + 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(); + } + node = parent; + first_in_line.pop(); } else { + result.add_line(&indent_for_line_below); + result.add_line(&indent_for_line); break; } } - if consider_indent || increment_from_line < 0 { - increment += increment_from_line.signum(); - } - increment.max(0) as usize + Some(result.as_string(indent_style)) } -// TODO: two usecases: if we are triggering this for a new, blank line: -// - it should return 0 when mass indenting stuff -// - it should look up the wrapper node and count it too when we press o/O -pub fn suggested_indent_for_pos( +/// Returns the indentation for a new line. +/// This is done either using treesitter, or if that's not available by copying the indentation from the current line +#[allow(clippy::too_many_arguments)] +pub fn indent_for_newline( language_config: Option<&LanguageConfiguration>, syntax: Option<&Syntax>, + indent_style: &IndentStyle, + tab_width: usize, text: RopeSlice, - pos: usize, - line: usize, - new_line: bool, -) -> Option { + line_before: usize, + line_before_end_pos: usize, + current_line: usize, +) -> String { if let (Some(query), Some(syntax)) = ( language_config.and_then(|config| config.indent_query()), syntax, ) { - let byte_start = text.char_to_byte(pos); - let node = get_highest_syntax_node_at_bytepos(syntax, byte_start); - // TODO: special case for comments - // TODO: if preserve_leading_whitespace - Some(calculate_indentation(query, node, line, new_line)) - } else { - None + if let Some(indent) = treesitter_indent_for_pos( + query, + syntax, + indent_style, + text, + line_before, + line_before_end_pos, + true, + ) { + return indent; + }; } + let indent_level = indent_level_for_line(text.line(current_line), tab_width); + indent_style.as_str().repeat(indent_level) } pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> { @@ -326,156 +632,4 @@ mod test { let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3); } - - #[test] - fn test_suggested_indent_for_line() { - let doc = Rope::from( - " -use std::{ - io::{self, stdout, Stdout, Write}, - path::PathBuf, - sync::Arc, - time::Duration, -} -mod test { - fn hello_world() { - 1 + 1; - - let does_indentation_work = 1; - - let test_function = function_with_param(this_param, - that_param - ); - - let test_function = function_with_param( - this_param, - that_param - ); - - let test_function = function_with_proper_indent(param1, - param2, - ); - - let selection = Selection::new( - changes - .clone() - .map(|(start, end, text): (usize, usize, Option)| { - let len = text.map(|text| text.len()).unwrap() - 1; // minus newline - let pos = start + len; - Range::new(pos, pos) - }) - .collect(), - 0, - ); - - return; - } -} - -impl MyTrait for YourType -where - A: TraitB + TraitC, - D: TraitE + TraitF, -{ - -} -#[test] -// -match test { - Some(a) => 1, - None => { - unimplemented!() - } -} -std::panic::set_hook(Box::new(move |info| { - hook(info); -})); - -{ { { - 1 -}}} - -pub fn change(document: &Document, changes: I) -> Self -where - I: IntoIterator + ExactSizeIterator, -{ - [ - 1, - 2, - 3, - ]; - ( - 1, - 2 - ); - true -} -", - ); - - let doc = doc; - use crate::diagnostic::Severity; - use crate::syntax::{ - Configuration, IndentationConfiguration, LanguageConfiguration, Loader, - }; - use once_cell::sync::OnceCell; - let loader = Loader::new(Configuration { - language: vec![LanguageConfiguration { - scope: "source.rust".to_string(), - file_types: vec!["rs".to_string()], - shebangs: vec![], - language_id: "Rust".to_string(), - highlight_config: OnceCell::new(), - config: None, - // - injection_regex: None, - roots: vec![], - comment_token: None, - auto_format: false, - diagnostic_severity: Severity::Warning, - grammar: None, - language_server: None, - indent: Some(IndentationConfiguration { - tab_width: 4, - unit: String::from(" "), - }), - indent_query: OnceCell::new(), - textobject_query: OnceCell::new(), - debugger: None, - auto_pairs: None, - }], - }); - - // set runtime path so we can find the queries - let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - runtime.push("../runtime"); - std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap()); - - let language_config = loader.language_config_for_scope("source.rust").unwrap(); - let highlight_config = language_config.highlight_config(&[]).unwrap(); - let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader)); - let text = doc.slice(..); - let tab_width = 4; - - for i in 0..doc.len_lines() { - let line = text.line(i); - if let Some(pos) = crate::find_first_non_whitespace_char(line) { - let indent = indent_level_for_line(line, tab_width); - assert_eq!( - suggested_indent_for_pos( - Some(&language_config), - Some(&syntax), - text, - text.line_to_char(i) + pos, - i, - false - ), - Some(indent), - "line {}: \"{}\"", - i, - line - ); - } - } - } } diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index d3750e757c6c..dde7e90cde32 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -92,7 +92,7 @@ pub struct LanguageConfiguration { pub indent: Option, #[serde(skip)] - pub(crate) indent_query: OnceCell>, + pub(crate) indent_query: OnceCell>, #[serde(skip)] pub(crate) textobject_query: OnceCell>, #[serde(skip_serializing_if = "Option::is_none")] @@ -220,17 +220,6 @@ impl FromStr for AutoPairConfig { } } -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct IndentQuery { - #[serde(default)] - #[serde(skip_serializing_if = "HashSet::is_empty")] - pub indent: HashSet, - #[serde(default)] - #[serde(skip_serializing_if = "HashSet::is_empty")] - pub outdent: HashSet, -} - #[derive(Debug)] pub struct TextObjectQuery { pub query: Query, @@ -404,13 +393,13 @@ impl LanguageConfiguration { self.highlight_config.get().is_some() } - pub fn indent_query(&self) -> Option<&IndentQuery> { + pub fn indent_query(&self) -> Option<&Query> { self.indent_query .get_or_init(|| { - let language = self.language_id.to_ascii_lowercase(); - - let toml = load_runtime_file(&language, "indents.toml").ok()?; - toml::from_slice(toml.as_bytes()).ok() + let lang_name = self.language_id.to_ascii_lowercase(); + let query_text = read_query(&lang_name, "indents.scm"); + let lang = self.highlight_config.get()?.as_ref()?.language; + Query::new(lang, &query_text).ok() }) .as_ref() } @@ -557,7 +546,7 @@ impl Loader { pub struct TsParser { parser: tree_sitter::Parser, - cursors: Vec, + pub cursors: Vec, } // could also just use a pool, or a single instance? @@ -1180,7 +1169,7 @@ struct HighlightIter<'a> { } // Adapter to convert rope chunks to bytes -struct ChunksBytes<'a> { +pub struct ChunksBytes<'a> { chunks: ropey::iter::Chunks<'a>, } impl<'a> Iterator for ChunksBytes<'a> { @@ -1190,7 +1179,7 @@ impl<'a> Iterator for ChunksBytes<'a> { } } -struct RopeProvider<'a>(RopeSlice<'a>); +pub struct RopeProvider<'a>(pub RopeSlice<'a>); impl<'a> TextProvider<'a> for RopeProvider<'a> { type I = ChunksBytes<'a>; @@ -2126,7 +2115,7 @@ mod test { #[test] fn test_load_runtime_file() { // Test to make sure we can load some data from the runtime directory. - let contents = load_runtime_file("rust", "indents.toml").unwrap(); + let contents = load_runtime_file("rust", "indents.scm").unwrap(); assert!(!contents.is_empty()); let results = load_runtime_file("rust", "does-not-exist"); diff --git a/helix-core/tests/data/indent/indent.rs b/helix-core/tests/data/indent/indent.rs new file mode 120000 index 000000000000..2ac16cf96357 --- /dev/null +++ b/helix-core/tests/data/indent/indent.rs @@ -0,0 +1 @@ +../../../src/indent.rs \ No newline at end of file diff --git a/helix-core/tests/data/indent/languages.toml b/helix-core/tests/data/indent/languages.toml new file mode 100644 index 000000000000..f9cef4942824 --- /dev/null +++ b/helix-core/tests/data/indent/languages.toml @@ -0,0 +1,13 @@ +# This languages.toml should contain definitions for all languages for which we have indent tests +[[language]] +name = "rust" +scope = "source.rust" +injection-regex = "rust" +file-types = ["rs"] +comment-token = "//" +roots = ["Cargo.toml", "Cargo.lock"] +indent = { tab-width = 4, unit = " " } + +[[grammar]] +name = "rust" +source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a360da0a29a19c281d08295a35ecd0544d2da211" } diff --git a/helix-core/tests/data/indent/rust.rs b/helix-core/tests/data/indent/rust.rs new file mode 100644 index 000000000000..010745e0d708 --- /dev/null +++ b/helix-core/tests/data/indent/rust.rs @@ -0,0 +1,105 @@ +use std::{ + io::{self, stdout, Stdout, Write}, + path::PathBuf, + sync::Arc, + time::Duration, +}; +mod test { + fn hello_world() { + 1 + 1; + + let does_indentation_work = 1; + + let mut really_long_variable_name_using_up_the_line = + really_long_fn_that_should_definitely_go_on_the_next_line(); + really_long_variable_name_using_up_the_line = + really_long_fn_that_should_definitely_go_on_the_next_line(); + really_long_variable_name_using_up_the_line |= + really_long_fn_that_should_definitely_go_on_the_next_line(); + + let ( + a_long_variable_name_in_this_tuple, + b_long_variable_name_in_this_tuple, + c_long_variable_name_in_this_tuple, + d_long_variable_name_in_this_tuple, + e_long_variable_name_in_this_tuple, + ): (usize, usize, usize, usize, usize) = + if really_long_fn_that_should_definitely_go_on_the_next_line() { + ( + 03294239434, + 1213412342314, + 21231234134, + 834534234549898789, + 9879234234543853457, + ) + } else { + (0, 1, 2, 3, 4) + }; + + let test_function = function_with_param(this_param, + that_param + ); + + let test_function = function_with_param( + this_param, + that_param + ); + + let test_function = function_with_proper_indent(param1, + param2, + ); + + let selection = Selection::new( + changes + .clone() + .map(|(start, end, text): (usize, usize, Option)| { + let len = text.map(|text| text.len()).unwrap() - 1; // minus newline + let pos = start + len; + Range::new(pos, pos) + }) + .collect(), + 0, + ); + + return; + } +} + +impl MyTrait for YourType +where + A: TraitB + TraitC, + D: TraitE + TraitF, +{ + +} +#[test] +// +match test { + Some(a) => 1, + None => { + unimplemented!() + } +} +std::panic::set_hook(Box::new(move |info| { + hook(info); +})); + +{ { { + 1 +}}} + +pub fn change(document: &Document, changes: I) -> Self +where + I: IntoIterator + ExactSizeIterator, +{ + [ + 1, + 2, + 3, + ]; + ( + 1, + 2 + ); + true +} diff --git a/helix-core/tests/indent.rs b/helix-core/tests/indent.rs new file mode 100644 index 000000000000..ff04d05f5bbe --- /dev/null +++ b/helix-core/tests/indent.rs @@ -0,0 +1,68 @@ +use helix_core::{ + indent::{treesitter_indent_for_pos, IndentStyle}, + syntax::Loader, + Syntax, +}; +use std::path::PathBuf; + +#[test] +fn test_treesitter_indent_rust() { + test_treesitter_indent("rust.rs", "source.rust"); +} +#[test] +fn test_treesitter_indent_rust_2() { + test_treesitter_indent("indent.rs", "source.rust"); + // TODO Use commands.rs as indentation test. + // Currently this fails because we can't align the parameters of a closure yet + // test_treesitter_indent("commands.rs", "source.rust"); +} + +fn test_treesitter_indent(file_name: &str, lang_scope: &str) { + let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_dir.push("tests/data/indent"); + + let mut test_file = test_dir.clone(); + test_file.push(file_name); + let test_file = std::fs::File::open(test_file).unwrap(); + let doc = ropey::Rope::from_reader(test_file).unwrap(); + + let mut config_file = test_dir; + config_file.push("languages.toml"); + let config = std::fs::read(config_file).unwrap(); + let config = toml::from_slice(&config).unwrap(); + let loader = Loader::new(config); + + // set runtime path so we can find the queries + let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + runtime.push("../runtime"); + std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap()); + + let language_config = loader.language_config_for_scope(lang_scope).unwrap(); + let highlight_config = language_config.highlight_config(&[]).unwrap(); + let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader)); + let indent_query = language_config.indent_query().unwrap(); + let text = doc.slice(..); + + for i in 0..doc.len_lines() { + let line = text.line(i); + if let Some(pos) = helix_core::find_first_non_whitespace_char(line) { + let suggested_indent = treesitter_indent_for_pos( + indent_query, + &syntax, + &IndentStyle::Spaces(4), + text, + i, + text.line_to_char(i) + pos, + false, + ) + .unwrap(); + assert!( + line.get_slice(..pos).map_or(false, |s| s == suggested_indent), + "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n", + i+1, + line.slice(..line.len_chars()-1), + suggested_indent, + ); + } + } +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4b4e834a5636..bd66f26a20d5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2240,17 +2240,16 @@ fn open(cx: &mut Context, open: Open) { ) }; - // TODO: share logic with insert_newline for indentation - let indent_level = indent::suggested_indent_for_pos( + let indent = indent::indent_for_newline( doc.language_config(), doc.syntax(), + &doc.indent_style, + doc.tab_width(), text, - line_end_index, new_line.saturating_sub(1), - true, - ) - .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width())); - let indent = doc.indent_unit().repeat(indent_level); + 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()); @@ -2703,19 +2702,16 @@ pub mod insert { let curr = contents.get_char(pos).unwrap_or(' '); let current_line = text.char_to_line(pos); - let indent_level = indent::suggested_indent_for_pos( + let indent = indent::indent_for_newline( doc.language_config(), doc.syntax(), + &doc.indent_style, + doc.tab_width(), text, + current_line, pos, current_line, - true, - ) - .unwrap_or_else(|| { - indent::indent_level_for_line(text.line(current_line), doc.tab_width()) - }); - - let indent = doc.indent_unit().repeat(indent_level); + ); let mut text = String::new(); // If we are between pairs (such as brackets), we want to // insert an additional line which is indented one level @@ -2727,7 +2723,7 @@ pub mod insert { .is_some(); let new_head_pos = if on_auto_pair { - let inner_indent = doc.indent_unit().repeat(indent_level + 1); + let inner_indent = indent.clone() + doc.indent_style.as_str(); text.reserve_exact(2 + indent.len() + inner_indent.len()); text.push_str(doc.line_ending.as_str()); text.push_str(&inner_indent); diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 80f596808686..f73139fc3e21 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -19,7 +19,7 @@ impl TsFeature { match *self { Self::Highlight => "highlights.scm", Self::TextObject => "textobjects.scm", - Self::AutoIndent => "indents.toml", + Self::AutoIndent => "indents.scm", } } diff --git a/runtime/queries/c/indents.scm b/runtime/queries/c/indents.scm new file mode 100644 index 000000000000..353ea81b9a2e --- /dev/null +++ b/runtime/queries/c/indents.scm @@ -0,0 +1,33 @@ +[ + (compound_statement) + (field_declaration_list) + (enumerator_list) + (parameter_list) + (init_declarator) + (case_statement) + (expression_statement) +] @indent + +[ + "case" + "}" + "]" +] @outdent + +(if_statement + consequence: (_) @indent + (#not-kind-eq? @indent "compound_statement") + (#set! "scope" "all")) +(while_statement + body: (_) @indent + (#not-kind-eq? @indent "compound_statement") + (#set! "scope" "all")) +(do_statement + body: (_) @indent + (#not-kind-eq? @indent "compound_statement") + (#set! "scope" "all")) +(for_statement + ")" + (_) @indent + (#not-kind-eq? @indent "compound_statement") + (#set! "scope" "all")) diff --git a/runtime/queries/c/indents.toml b/runtime/queries/c/indents.toml deleted file mode 100644 index f4076e171c10..000000000000 --- a/runtime/queries/c/indents.toml +++ /dev/null @@ -1,16 +0,0 @@ -indent = [ - "compound_statement", - "field_declaration_list", - "enumerator_list", - "parameter_list", - "init_declarator", - "case_statement", - "condition_clause", - "expression_statement", -] - -outdent = [ - "case", - "}", - "]", -] diff --git a/runtime/queries/cmake/indents.scm b/runtime/queries/cmake/indents.scm new file mode 100644 index 000000000000..199b1031e5c6 --- /dev/null +++ b/runtime/queries/cmake/indents.scm @@ -0,0 +1,10 @@ +[ + (if_condition) + (foreach_loop) + (while_loop) + (function_def) + (macro_def) + (normal_command) +] @indent + +")" @outdent diff --git a/runtime/queries/cmake/indents.toml b/runtime/queries/cmake/indents.toml deleted file mode 100644 index 8b886a4fb3dc..000000000000 --- a/runtime/queries/cmake/indents.toml +++ /dev/null @@ -1,12 +0,0 @@ -indent = [ - "if_condition", - "foreach_loop", - "while_loop", - "function_def", - "macro_def", - "normal_command", -] - -outdent = [ - ")" -] diff --git a/runtime/queries/cpp/indents.scm b/runtime/queries/cpp/indents.scm new file mode 100644 index 000000000000..36876f9490d7 --- /dev/null +++ b/runtime/queries/cpp/indents.scm @@ -0,0 +1,3 @@ +; inherits: c + +(access_specifier) @outdent diff --git a/runtime/queries/cpp/indents.toml b/runtime/queries/cpp/indents.toml deleted file mode 100644 index 0ca2ed8bf7f7..000000000000 --- a/runtime/queries/cpp/indents.toml +++ /dev/null @@ -1,17 +0,0 @@ -indent = [ - "compound_statement", - "field_declaration_list", - "enumerator_list", - "parameter_list", - "init_declarator", - "case_statement", - "condition_clause", - "expression_statement", -] - -outdent = [ - "case", - "access_specifier", - "}", - "]", -] diff --git a/runtime/queries/dart/indents.scm b/runtime/queries/dart/indents.scm new file mode 100644 index 000000000000..14c6a375889a --- /dev/null +++ b/runtime/queries/dart/indents.scm @@ -0,0 +1,20 @@ +[ + (class_body) + (function_body) + (function_expression_body) + (declaration) + (initializers) + (switch_block) + (if_statement) + (formal_parameter_list) + (formal_parameter) + (list_literal) + (return_statement) + (arguments) +] @indent + +[ + "}" + "]" + ")" +] @outdent diff --git a/runtime/queries/dart/indents.toml b/runtime/queries/dart/indents.toml deleted file mode 100644 index 5c11e05dd859..000000000000 --- a/runtime/queries/dart/indents.toml +++ /dev/null @@ -1,20 +0,0 @@ -indent = [ - "class_body", - "function_body", - "function_expression_body", - "declaration", - "initializers", - "switch_block", - "if_statement", - "formal_parameter_list", - "formal_parameter", - "list_literal", - "return_statement", - "arguments" -] - -outdent = [ - "}", - "]", - ")" -] diff --git a/runtime/queries/fish/indents.scm b/runtime/queries/fish/indents.scm new file mode 100644 index 000000000000..ba7c65eade46 --- /dev/null +++ b/runtime/queries/fish/indents.scm @@ -0,0 +1,12 @@ +[ + (function_definition) + (while_statement) + (for_statement) + (if_statement) + (begin_statement) + (switch_statement) +] @indent + +[ + "end" +] @outdent diff --git a/runtime/queries/fish/indents.toml b/runtime/queries/fish/indents.toml deleted file mode 100644 index 6f1e563ae069..000000000000 --- a/runtime/queries/fish/indents.toml +++ /dev/null @@ -1,12 +0,0 @@ -indent = [ - "function_definition", - "while_statement", - "for_statement", - "if_statement", - "begin_statement", - "switch_statement", -] - -outdent = [ - "end" -] diff --git a/runtime/queries/glsl/indents.scm b/runtime/queries/glsl/indents.scm new file mode 100644 index 000000000000..a8b55a424734 --- /dev/null +++ b/runtime/queries/glsl/indents.scm @@ -0,0 +1,19 @@ +[ + (init_declarator) + (compound_statement) + (preproc_arg) + (field_declaration_list) + (case_statement) + (conditional_expression) + (enumerator_list) + (struct_specifier) + (compound_literal_expression) +] @indent + +[ + "#define" + "#ifdef" + "#endif" + "{" + "}" +] @outdent diff --git a/runtime/queries/glsl/indents.toml b/runtime/queries/glsl/indents.toml deleted file mode 100644 index a7fd499a9239..000000000000 --- a/runtime/queries/glsl/indents.toml +++ /dev/null @@ -1,19 +0,0 @@ -indent = [ - "init_declarator", - "compound_statement", - "preproc_arg", - "field_declaration_list", - "case_statement", - "conditional_expression", - "enumerator_list", - "struct_specifier", - "compound_literal_expression" -] - -outdent = [ - "#define", - "#ifdef", - "#endif", - "{", - "}" -] diff --git a/runtime/queries/go/indents.scm b/runtime/queries/go/indents.scm new file mode 100644 index 000000000000..d75417d97a39 --- /dev/null +++ b/runtime/queries/go/indents.scm @@ -0,0 +1,26 @@ +[ + (import_declaration) + (const_declaration) + (type_declaration) + (type_spec) + (func_literal) + (literal_value) + (element) + (keyed_element) + (expression_case) + (default_case) + (type_case) + (communication_case) + (argument_list) + (field_declaration_list) + (block) + (type_switch_statement) + (expression_switch_statement) +] @indent + +[ + "case" + "}" + "]" + ")" +] @outdent diff --git a/runtime/queries/go/indents.toml b/runtime/queries/go/indents.toml deleted file mode 100644 index 7929ff50d37b..000000000000 --- a/runtime/queries/go/indents.toml +++ /dev/null @@ -1,30 +0,0 @@ -indent = [ - "import_declaration", - "const_declaration", - #"var_declaration", - #"short_var_declaration", - "type_declaration", - "type_spec", - # simply block should be enough - # "function_declaration", - # "method_declaration", - # "composite_literal", - "func_literal", - "literal_value", - "element", - "keyed_element", - "expression_case", - "default_case", - "type_case", - "communication_case", - "argument_list", - "field_declaration_list", - "block", -] - -outdent = [ - "case", - "}", - "]", - ")" -] diff --git a/runtime/queries/hcl/indents.scm b/runtime/queries/hcl/indents.scm new file mode 100644 index 000000000000..3625641b28e0 --- /dev/null +++ b/runtime/queries/hcl/indents.scm @@ -0,0 +1,13 @@ +[ + (object) + (block) + (tuple) + (for_tuple_expr) + (for_object_expr) +] @indent + +[ + (object_end) + (block_end) + (tuple_end) +] @outdent diff --git a/runtime/queries/hcl/indents.toml b/runtime/queries/hcl/indents.toml deleted file mode 100644 index b0d4a3f0d877..000000000000 --- a/runtime/queries/hcl/indents.toml +++ /dev/null @@ -1,13 +0,0 @@ -indent = [ - "object", - "block", - "tuple", - "for_tuple_expr", - "for_object_expr" -] - -outdent = [ - "object_end", - "block_end", - "tuple_end" -] diff --git a/runtime/queries/javascript/indents.scm b/runtime/queries/javascript/indents.scm new file mode 100644 index 000000000000..a4237e59982c --- /dev/null +++ b/runtime/queries/javascript/indents.scm @@ -0,0 +1,22 @@ +[ + (array) + (object) + (arguments) + (formal_parameters) + + (statement_block) + (object_pattern) + (class_body) + (named_imports) + + (binary_expression) + (return_statement) + (template_substitution) + (export_clause) +] @indent + +[ + "}" + "]" + ")" +] @outdent diff --git a/runtime/queries/javascript/indents.toml b/runtime/queries/javascript/indents.toml deleted file mode 100644 index 9d711ab23d4c..000000000000 --- a/runtime/queries/javascript/indents.toml +++ /dev/null @@ -1,28 +0,0 @@ -indent = [ - "array", - "object", - "arguments", - "formal_parameters", - - "statement_block", - "object_pattern", - "class_body", - "named_imports", - - "binary_expression", - "return_statement", - "template_substitution", - # (expression_statement (call_expression)) - "export_clause", - - # typescript - "enum_declaration", - "interface_declaration", - "object_type", -] - -outdent = [ - "}", - "]", - ")" -] diff --git a/runtime/queries/json/indents.scm b/runtime/queries/json/indents.scm new file mode 100644 index 000000000000..f756e609acfe --- /dev/null +++ b/runtime/queries/json/indents.scm @@ -0,0 +1,9 @@ +[ + (object) + (array) +] @indent + +[ + "]" + "}" +] @outdent diff --git a/runtime/queries/json/indents.toml b/runtime/queries/json/indents.toml deleted file mode 100644 index 64a8d1757f09..000000000000 --- a/runtime/queries/json/indents.toml +++ /dev/null @@ -1,9 +0,0 @@ -indent = [ - "object", - "array" -] - -outdent = [ - "]", - "}" -] diff --git a/runtime/queries/llvm-mir-yaml/indents.scm b/runtime/queries/llvm-mir-yaml/indents.scm new file mode 100644 index 000000000000..70a00b695389 --- /dev/null +++ b/runtime/queries/llvm-mir-yaml/indents.scm @@ -0,0 +1,2 @@ +(block_mapping_pair) @indent + diff --git a/runtime/queries/llvm-mir-yaml/indents.toml b/runtime/queries/llvm-mir-yaml/indents.toml deleted file mode 100644 index ddc3578b1420..000000000000 --- a/runtime/queries/llvm-mir-yaml/indents.toml +++ /dev/null @@ -1,3 +0,0 @@ -indent = [ - "block_mapping_pair", -] diff --git a/runtime/queries/llvm-mir/indents.scm b/runtime/queries/llvm-mir/indents.scm new file mode 100644 index 000000000000..12c86268cb00 --- /dev/null +++ b/runtime/queries/llvm-mir/indents.scm @@ -0,0 +1,3 @@ +(basic_block) @indent + +(label) @outdent diff --git a/runtime/queries/llvm-mir/indents.toml b/runtime/queries/llvm-mir/indents.toml deleted file mode 100644 index 6a70e5adc2ab..000000000000 --- a/runtime/queries/llvm-mir/indents.toml +++ /dev/null @@ -1,7 +0,0 @@ -indent = [ - "basic_block", -] - -outdent = [ - "label", -] diff --git a/runtime/queries/llvm/indents.scm b/runtime/queries/llvm/indents.scm new file mode 100644 index 000000000000..293eeebf4133 --- /dev/null +++ b/runtime/queries/llvm/indents.scm @@ -0,0 +1,6 @@ +[ + (function_body) + (instruction) +] @indent + +"}" @outdent diff --git a/runtime/queries/llvm/indents.toml b/runtime/queries/llvm/indents.toml deleted file mode 100644 index 8cd603c8e668..000000000000 --- a/runtime/queries/llvm/indents.toml +++ /dev/null @@ -1,8 +0,0 @@ -indent = [ - "function_body", - "instruction", -] - -outdent = [ - "}", -] diff --git a/runtime/queries/lua/indents.scm b/runtime/queries/lua/indents.scm new file mode 100644 index 000000000000..55a812c51ba8 --- /dev/null +++ b/runtime/queries/lua/indents.scm @@ -0,0 +1,24 @@ +[ + (function_definition) + (variable_declaration) + (local_variable_declaration) + (field) + (local_function) + (function) + (if_statement) + (for_statement) + (for_in_statement) + (repeat_statement) + (return_statement) + (while_statement) + (table) + (arguments) + (do_statement) +] @indent + +[ + "end" + "until" + "}" + ")" +] @outdent diff --git a/runtime/queries/lua/indents.toml b/runtime/queries/lua/indents.toml deleted file mode 100644 index df1a9752a043..000000000000 --- a/runtime/queries/lua/indents.toml +++ /dev/null @@ -1,24 +0,0 @@ -indent = [ - "function_definition", - "variable_declaration", - "local_variable_declaration", - "field", - "local_function", - "function", - "if_statement", - "for_statement", - "for_in_statement", - "repeat_statement", - "return_statement", - "while_statement", - "table", - "arguments", - "do_statement", -] - -oudent = [ - "end", - "until", - "}", - ")", -] diff --git a/runtime/queries/nix/indents.scm b/runtime/queries/nix/indents.scm new file mode 100644 index 000000000000..0790ce2919ff --- /dev/null +++ b/runtime/queries/nix/indents.scm @@ -0,0 +1,18 @@ +[ + ; "function", + (bind) + (assert) + (with) + (let) + (if) + + (attrset) + (list) + (indented_string) + (parenthesized) +] @indent + +[ + "}" + "]" +] @outdent diff --git a/runtime/queries/nix/indents.toml b/runtime/queries/nix/indents.toml deleted file mode 100644 index b92ab752df99..000000000000 --- a/runtime/queries/nix/indents.toml +++ /dev/null @@ -1,18 +0,0 @@ -indent = [ - # "function", - "bind", - "assert", - "with", - "let", - "if", - - "attrset", - "list", - "indented_string", - "parenthesized", -] - -outdent = [ - "}", - "]", -] diff --git a/runtime/queries/ocaml/indents.scm b/runtime/queries/ocaml/indents.scm new file mode 100644 index 000000000000..dc4d591a5456 --- /dev/null +++ b/runtime/queries/ocaml/indents.scm @@ -0,0 +1,12 @@ +[ + (let_binding) + (type_binding) + (structure) + (signature) + (record_declaration) + (function_expression) + (match_case) +] @indent + +"}" @outdent + diff --git a/runtime/queries/ocaml/indents.toml b/runtime/queries/ocaml/indents.toml deleted file mode 100644 index 7586b83a0f42..000000000000 --- a/runtime/queries/ocaml/indents.toml +++ /dev/null @@ -1,13 +0,0 @@ -indent = [ - "let_binding", - "type_binding", - "structure", - "signature", - "record_declaration", - "function_expression", - "match_case", -] - -outdent = [ - "}", -] diff --git a/runtime/queries/perl/indents.scm b/runtime/queries/perl/indents.scm new file mode 100644 index 000000000000..5ae34f5e9842 --- /dev/null +++ b/runtime/queries/perl/indents.scm @@ -0,0 +1,15 @@ +[ + (function) + (identifier) + (method_invocation) + (if_statement) + (unless_statement) + (if_simple_statement) + (unless_simple_statement) + (variable_declaration) + (block) + (list_item) + (word_list_qw) +] @indent + +"}" @outdent diff --git a/runtime/queries/perl/indents.toml b/runtime/queries/perl/indents.toml deleted file mode 100644 index 365e0663601d..000000000000 --- a/runtime/queries/perl/indents.toml +++ /dev/null @@ -1,17 +0,0 @@ -indent = [ - "function", - "identifier", - "method_invocation", - "if_statement", - "unless_statement", - "if_simple_statement", - "unless_simple_statement", - "variable_declaration", - "block", - "list_item", - "word_list_qw" -] - -outdent = [ - "}" -] diff --git a/runtime/queries/php/indents.scm b/runtime/queries/php/indents.scm new file mode 100644 index 000000000000..b22393ed18ef --- /dev/null +++ b/runtime/queries/php/indents.scm @@ -0,0 +1,17 @@ +[ + (array_creation_expression) + (arguments) + (formal_parameters) + (compound_statement) + (declaration_list) + (binary_expression) + (return_statement) + (expression_statement) + (switch_block) + (anonymous_function_use_clause) +] @indent + +[ + "}" + ")" +] @outdent diff --git a/runtime/queries/php/indents.toml b/runtime/queries/php/indents.toml deleted file mode 100644 index 85c104db55cd..000000000000 --- a/runtime/queries/php/indents.toml +++ /dev/null @@ -1,17 +0,0 @@ -indent = [ - "array_creation_expression", - "arguments", - "formal_parameters", - "compound_statement", - "declaration_list", - "binary_expression", - "return_statement", - "expression_statement", - "switch_block", - "anonymous_function_use_clause", -] - -oudent = [ - "}", - ")", -] diff --git a/runtime/queries/protobuf/indents.scm b/runtime/queries/protobuf/indents.scm new file mode 100644 index 000000000000..d457d75f1998 --- /dev/null +++ b/runtime/queries/protobuf/indents.scm @@ -0,0 +1,11 @@ +[ + (messageBody) + (enumBody) + (oneofBody) + (serviceBody) + (rpcBody) + (msgLit) +] @indent + +"}" @outdent + diff --git a/runtime/queries/protobuf/indents.toml b/runtime/queries/protobuf/indents.toml deleted file mode 100644 index e655f8db6463..000000000000 --- a/runtime/queries/protobuf/indents.toml +++ /dev/null @@ -1,12 +0,0 @@ -indent = [ - "messageBody", - "enumBody", - "oneofBody", - "serviceBody", - "rpcBody", - "msgLit", -] - -outdent = [ - "}", -] diff --git a/runtime/queries/python/indents.scm b/runtime/queries/python/indents.scm new file mode 100644 index 000000000000..810ff52f5e8f --- /dev/null +++ b/runtime/queries/python/indents.scm @@ -0,0 +1,38 @@ +[ + (list) + (tuple) + (dictionary) + (set) + + (if_statement) + (for_statement) + (while_statement) + (with_statement) + (try_statement) + (import_from_statement) + + (parenthesized_expression) + (generator_expression) + (list_comprehension) + (set_comprehension) + (dictionary_comprehension) + + (tuple_pattern) + (list_pattern) + (argument_list) + (parameters) + (binary_operator) + + (function_definition) + (class_definition) +] @indent + +[ + ")" + "]" + "}" + (return_statement) + (pass_statement) + (raise_statement) +] @outdent + diff --git a/runtime/queries/python/indents.toml b/runtime/queries/python/indents.toml deleted file mode 100644 index 6bc684864bbe..000000000000 --- a/runtime/queries/python/indents.toml +++ /dev/null @@ -1,39 +0,0 @@ -indent = [ - "list", - "tuple", - "dictionary", - "set", - - "if_statement", - "for_statement", - "while_statement", - "with_statement", - "try_statement", - "import_from_statement", - - "parenthesized_expression", - "generator_expression", - "list_comprehension", - "set_comprehension", - "dictionary_comprehension", - - "tuple_pattern", - "list_pattern", - "argument_list", - "parameters", - "binary_operator", - - "function_definition", - "class_definition", -] - -outdent = [ - ")", - "]", - "}", - "return_statement", - "pass_statement", - "raise_statement", -] - -ignore = ["string"] diff --git a/runtime/queries/ruby/indents.scm b/runtime/queries/ruby/indents.scm new file mode 100644 index 000000000000..f5a6d19b5e03 --- /dev/null +++ b/runtime/queries/ruby/indents.scm @@ -0,0 +1,25 @@ +[ + (argument_list) + (array) + (begin) + (block) + (call) + (class) + (case) + (do_block) + (elsif) + (if) + (hash) + (method) + (module) + (singleton_class) + (singleton_method) +] @indent + +[ + ")" + "}" + "]" + "end" + "when" +] @outdent diff --git a/runtime/queries/ruby/indents.toml b/runtime/queries/ruby/indents.toml deleted file mode 100644 index b417751fc80c..000000000000 --- a/runtime/queries/ruby/indents.toml +++ /dev/null @@ -1,25 +0,0 @@ -indent = [ - "argument_list", - "array", - "begin", - "block", - "call", - "class", - "case", - "do_block", - "elsif", - "if", - "hash", - "method", - "module", - "singleton_class", - "singleton_method", -] - -outdent = [ - ")", - "}", - "]", - "end", - "when", -] diff --git a/runtime/queries/rust/indents.scm b/runtime/queries/rust/indents.scm new file mode 100644 index 000000000000..600c77a32cd3 --- /dev/null +++ b/runtime/queries/rust/indents.scm @@ -0,0 +1,80 @@ +[ + (use_list) + (block) + (match_block) + (arguments) + (parameters) + (declaration_list) + (field_declaration_list) + (field_initializer_list) + (struct_pattern) + (tuple_pattern) + (unit_expression) + (enum_variant_list) + (call_expression) + (binary_expression) + (field_expression) + (tuple_expression) + (array_expression) + (where_clause) + + (token_tree) + (macro_definition) + (token_tree_pattern) + (token_repetition) +] @indent + +[ + "}" + "]" + ")" +] @outdent + +; Indent the right side of assignments. +; The #not-same-line? predicate is required to prevent an extra indent for e.g. +; an else-clause where the previous if-clause starts on the same line as the assignment. +(assignment_expression + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(compound_assignment_expr + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(let_declaration + . + (_) @expr-start + value: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(if_let_expression + . + (_) @expr-start + value: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(static_item + . + (_) @expr-start + value: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) + +; Some field expressions where the left part is a multiline expression are not +; indented by cargo fmt. +; Because this multiline expression might be nested in an arbitrary number of +; field expressions, this can only be matched using a Regex. +(field_expression + value: (_) @val + "." @outdent + (#match? @val "(\\A[^\\n\\r]+\\([\\t ]*(\\n|\\r).*)|(\\A[^\\n\\r]*\\{[\\t ]*(\\n|\\r))") +) diff --git a/runtime/queries/rust/indents.toml b/runtime/queries/rust/indents.toml deleted file mode 100644 index 51a0ceeaf014..000000000000 --- a/runtime/queries/rust/indents.toml +++ /dev/null @@ -1,28 +0,0 @@ -indent = [ - "use_list", - "block", - "match_block", - "arguments", - "parameters", - "declaration_list", - "field_declaration_list", - "field_initializer_list", - "struct_pattern", - "tuple_pattern", - "unit_expression", - "enum_variant_list", - "call_expression", - "binary_expression", - "field_expression", - "tuple_expression", - "array_expression", - "where_clause", - "macro_invocation" -] - -outdent = [ - "where", - "}", - "]", - ")" -] diff --git a/runtime/queries/scala/indents.scm b/runtime/queries/scala/indents.scm new file mode 100644 index 000000000000..3449cfa7a965 --- /dev/null +++ b/runtime/queries/scala/indents.scm @@ -0,0 +1,22 @@ +[ + (block) + (arguments) + (parameter) + (class_definition) + (trait_definition) + (object_definition) + (function_definition) + (val_definition) + (import_declaration) + (while_expression) + (do_while_expression) + (for_expression) + (try_expression) + (match_expression) +] @indent + +[ + "}" + "]" + ")" +] @outdent diff --git a/runtime/queries/scala/indents.toml b/runtime/queries/scala/indents.toml deleted file mode 100644 index 6de548442ab2..000000000000 --- a/runtime/queries/scala/indents.toml +++ /dev/null @@ -1,23 +0,0 @@ - -indent = [ - "block", - "arguments", - "parameter", - "class_definition", - "trait_definition", - "object_definition", - "function_definition", - "val_definition", - "import_declaration", - "while_expression", - "do_while_expression", - "for_expression", - "try_expression", - "match_expression" -] - -outdent = [ - "}", - "]", - ")" -] diff --git a/runtime/queries/svelte/indents.scm b/runtime/queries/svelte/indents.scm new file mode 100644 index 000000000000..02aaaa58de33 --- /dev/null +++ b/runtime/queries/svelte/indents.scm @@ -0,0 +1,17 @@ +[ + (element) + (if_statement) + (each_statement) + (await_statement) +] @indent + +[ + (end_tag) + (else_statement) + (if_end_expr) + (each_end_expr) + (await_end_expr) + ">" + "/>" +] @outdent + diff --git a/runtime/queries/svelte/indents.toml b/runtime/queries/svelte/indents.toml deleted file mode 100644 index 693db8e3dab2..000000000000 --- a/runtime/queries/svelte/indents.toml +++ /dev/null @@ -1,18 +0,0 @@ -indent = [ - "element" - "if_statement" - "each_statement" - "await_statement" -] - -outdent = [ - "end_tag" - "else_statement" - "if_end_expr" - "each_end_expr" - "await_end_expr" - ">" - "/>" -] - -ignore = "comment" diff --git a/runtime/queries/tablegen/indents.scm b/runtime/queries/tablegen/indents.scm new file mode 100644 index 000000000000..1c15d7dbd32b --- /dev/null +++ b/runtime/queries/tablegen/indents.scm @@ -0,0 +1,3 @@ +(statement) @indent + +"}" @outdent diff --git a/runtime/queries/tablegen/indents.toml b/runtime/queries/tablegen/indents.toml deleted file mode 100644 index 43532f4d4ad6..000000000000 --- a/runtime/queries/tablegen/indents.toml +++ /dev/null @@ -1,7 +0,0 @@ -indent = [ - "statement", -] - -outdent = [ - "}", -] diff --git a/runtime/queries/typescript/indents.scm b/runtime/queries/typescript/indents.scm new file mode 100644 index 000000000000..055e170b82f2 --- /dev/null +++ b/runtime/queries/typescript/indents.scm @@ -0,0 +1,7 @@ +; inherits: javascript + +[ + (enum_declaration) + (interface_declaration) + (object_type) +] @indent diff --git a/runtime/queries/typescript/indents.toml b/runtime/queries/typescript/indents.toml deleted file mode 120000 index 3a17f2586608..000000000000 --- a/runtime/queries/typescript/indents.toml +++ /dev/null @@ -1 +0,0 @@ -../javascript/indents.toml \ No newline at end of file diff --git a/runtime/queries/yaml/indents.scm b/runtime/queries/yaml/indents.scm new file mode 100644 index 000000000000..70a00b695389 --- /dev/null +++ b/runtime/queries/yaml/indents.scm @@ -0,0 +1,2 @@ +(block_mapping_pair) @indent + diff --git a/runtime/queries/yaml/indents.toml b/runtime/queries/yaml/indents.toml deleted file mode 100644 index ddc3578b1420..000000000000 --- a/runtime/queries/yaml/indents.toml +++ /dev/null @@ -1,3 +0,0 @@ -indent = [ - "block_mapping_pair", -] diff --git a/runtime/queries/zig/indents.scm b/runtime/queries/zig/indents.scm new file mode 100644 index 000000000000..af25a9c3c567 --- /dev/null +++ b/runtime/queries/zig/indents.scm @@ -0,0 +1,16 @@ +[ + (Block) + (BlockExpr) + (ContainerDecl) + (SwitchExpr) + (AssignExpr) + (ErrorUnionExpr) + (Statement) + (InitList) +] @indent + +[ + "}" + "]" + ")" +] @outdent diff --git a/runtime/queries/zig/indents.toml b/runtime/queries/zig/indents.toml deleted file mode 100644 index 36ba8e55895c..000000000000 --- a/runtime/queries/zig/indents.toml +++ /dev/null @@ -1,16 +0,0 @@ -indent = [ - "Block", - "BlockExpr", - "ContainerDecl", - "SwitchExpr", - "AssignExpr", - "ErrorUnionExpr", - "Statement", - "InitList" -] - -outdent = [ - "}", - "]", - ")" -]