on master help sign in
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<Vec<_>> 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 <triton0171@gmail.com>

commit58758fee610a3808dfaeafddd1b4b4242a7e42cd authorTriton171 <daniel.ebert@protonmail.com> dateWed 30 Mar 2022, 17:08 (5 years ago) parentc18de0e
66 files changed+1149−713
book/src/SUMMARY.md+1 book/src/guides/indent.md+79 helix-core/src/indent.rs+374−220 helix-core/src/syntax.rs+10−21 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+12−16 helix-term/src/health.rs+1−1 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
book/src/SUMMARY.md+1
@@ -16,3 +16,4 @@
1616- [Guides](./guides/README.md)
1717 - [Adding Languages](./guides/adding_languages.md)
1818 - [Adding Textobject Queries](./guides/textobject.md)
19+ - [Adding Indent Queries](./guides/indent.md)
book/src/guides/indent.mdnew file+79
@@ -0,0 +1,79 @@
1+# Adding Indent Queries
2+
3+Helix uses tree-sitter to correctly indent new lines. This requires
4+a tree-sitter grammar and an `indent.scm` query file placed in
5+`runtime/queries/{language}/indents.scm`. The indentation for a line
6+is calculated by traversing the syntax tree from the lowest node at the
7+beginning of the new line. Each of these nodes contributes to the total
8+indent when it is captured by the query (in what way depends on the name
9+of the capture).
10+
11+Note that it matters where these added indents begin. For example,
12+multiple indent level increases that start on the same line only increase
13+the total indent level by 1.
14+
15+## Scopes
16+
17+Added indents don't always apply to the whole node. For example, in most
18+cases when a node should be indented, we actually only want everything
19+except for its first line to be indented. For this, there are several
20+scopes (more scopes may be added in the future if required):
21+
22+- `all`:
23+This scope applies to the whole captured node. This is only different from
24+`tail` when the captured node is the first node on its line.
25+
26+- `tail`:
27+This scope applies to everything except for the first line of the
28+captured node.
29+
30+Every capture type has a default scope which should do the right thing
31+in most situations. When a different scope is required, this can be
32+changed by using a `#set!` declaration anywhere in the pattern:
33+```scm
34+(assignment_expression
35+ right: (_) @indent
36+ (#set! "scope" "all"))
37+```
38+
39+## Capture Types
40+
41+- `@indent` (default scope `tail`):
42+Increase the indent level by 1. Multiple occurences in the same line
43+don't stack. If there is at least one `@indent` and one `@outdent`
44+capture on the same line, the indent level isn't changed at all.
45+
46+- `@outdent` (default scope `all`):
47+Decrease the indent level by 1. The same rules as for `@indent` apply.
48+
49+## Predicates
50+
51+In some cases, an S-expression cannot express exactly what pattern should be matched.
52+For that, tree-sitter allows for predicates to appear anywhere within a pattern,
53+similar to how `#set!` declarations work:
54+```scm
55+(some_kind
56+ (child_kind) @indent
57+ (#predicate? arg1 arg2 ...)
58+)
59+```
60+The number of arguments depends on the predicate that's used.
61+Each argument is either a capture (`@name`) or a string (`"some string"`).
62+The following predicates are supported by tree-sitter:
63+
64+- `#eq?`/`#not-eq?`:
65+The first argument (a capture) must/must not be equal to the second argument
66+(a capture or a string).
67+
68+- `#match?`/`#not-match?`:
69+The first argument (a capture) must/must not match the regex given in the
70+second argument (a string).
71+
72+Additionally, we support some custom predicates for indent queries:
73+
74+- `#not-kind-eq?`:
75+The kind of the first argument (a capture) must not be equal to the second
76+argument (a string).
77+
78+- `#same-line?`/`#not-same-line?`:
79+The captures given by the 2 arguments must/must not start on the same line.
helix-core/src/indent.rs+374−220
@@ -1,6 +1,10 @@
1+use std::collections::HashMap;
2+
3+use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
4+
15use crate::{
26 chars::{char_is_line_ending, char_is_whitespace},
3− syntax::{IndentQuery, LanguageConfiguration, Syntax},
7+ syntax::{LanguageConfiguration, RopeProvider, Syntax},
48 tree_sitter::Node,
59 Rope, RopeSlice,
610};
@@ -186,103 +190,405 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
186190 len / tab_width
187191}
188192
189−/// Find the highest syntax node at position.
190−/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
191−fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
192− let tree = syntax.tree();
193−
194− // named_descendant
195− let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
196−
197− while let Some(parent) = node.parent() {
198− if parent.start_byte() == node.start_byte() {
199− node = parent
193+/// Computes for node and all ancestors whether they are the first node on their line.
194+/// The first entry in the return value represents the root node, the last one the node itself
195+fn get_first_in_line(mut node: Node, byte_pos: usize, new_line: bool) -> Vec<bool> {
196+ let mut first_in_line = Vec::new();
197+ loop {
198+ if let Some(prev) = node.prev_sibling() {
199+ // If we insert a new line, the first node at/after the cursor is considered to be the first in its line
200+ let first = prev.end_position().row != node.start_position().row
201+ || (new_line && node.start_byte() >= byte_pos && prev.start_byte() < byte_pos);
202+ first_in_line.push(Some(first));
203+ } else {
204+ // Nodes that have no previous siblings are first in their line if and only if their parent is
205+ // (which we don't know yet)
206+ first_in_line.push(None);
207+ }
208+ if let Some(parent) = node.parent() {
209+ node = parent;
200210 } else {
201211 break;
202212 }
203213 }
204214
205− Some(node)
215+ let mut result = Vec::with_capacity(first_in_line.len());
216+ let mut parent_is_first = true; // The root node is by definition the first node in its line
217+ for first in first_in_line.into_iter().rev() {
218+ if let Some(first) = first {
219+ result.push(first);
220+ parent_is_first = first;
221+ } else {
222+ result.push(parent_is_first);
223+ }
224+ }
225+ result
206226}
207227
208−/// Calculate the indentation at a given treesitter node.
209−/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
210−/// This is because the indentation is only increased starting at the second line of the node.
211−fn calculate_indentation(
212− query: &IndentQuery,
213− node: Option<Node>,
214− line: usize,
215− newline: bool,
216−) -> usize {
217− let mut increment: isize = 0;
218−
219− let mut node = match node {
220− Some(node) => node,
221− None => return 0,
222− };
228+/// The total indent for some line of code.
229+/// This is usually constructed in one of 2 ways:
230+/// - Successively add indent captures to get the (added) indent from a single line
231+/// - Successively add the indent results for each line
232+#[derive(Default)]
233+struct Indentation {
234+ /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
235+ /// The string that this results in depends on the indent style (spaces or tabs, etc.)
236+ indent: usize,
237+ outdent: usize,
238+}
239+impl Indentation {
240+ /// Add some other [IndentResult] to this.
241+ /// The added indent should be the total added indent from one line
242+ fn add_line(&mut self, added: &Indentation) {
243+ if added.indent > 0 && added.outdent == 0 {
244+ self.indent += 1;
245+ } else if added.outdent > 0 && added.indent == 0 {
246+ self.outdent += 1;
247+ }
248+ }
249+ /// Add an indent capture to this indent.
250+ /// All the captures that are added in this way should be on the same line.
251+ fn add_capture(&mut self, added: IndentCaptureType) {
252+ match added {
253+ IndentCaptureType::Indent => {
254+ self.indent = 1;
255+ }
256+ IndentCaptureType::Outdent => {
257+ self.outdent = 1;
258+ }
259+ }
260+ }
261+ fn as_string(&self, indent_style: &IndentStyle) -> String {
262+ let indent_level = if self.indent >= self.outdent {
263+ self.indent - self.outdent
264+ } else {
265+ log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
266+ 0
267+ };
268+ indent_style.as_str().repeat(indent_level)
269+ }
270+}
223271
224− let mut current_line = line;
225− let mut consider_indent = newline;
226− let mut increment_from_line: isize = 0;
272+/// An indent definition which corresponds to a capture from the indent query
273+struct IndentCapture {
274+ capture_type: IndentCaptureType,
275+ scope: IndentScope,
276+}
277+#[derive(Clone, Copy)]
278+enum IndentCaptureType {
279+ Indent,
280+ Outdent,
281+}
282+impl IndentCaptureType {
283+ fn default_scope(&self) -> IndentScope {
284+ match self {
285+ IndentCaptureType::Indent => IndentScope::Tail,
286+ IndentCaptureType::Outdent => IndentScope::All,
287+ }
288+ }
289+}
290+/// This defines which part of a node an [IndentCapture] applies to.
291+/// Each [IndentCaptureType] has a default scope, but the scope can be changed
292+/// with `#set!` property declarations.
293+#[derive(Clone, Copy)]
294+enum IndentScope {
295+ /// The indent applies to the whole node
296+ All,
297+ /// The indent applies to everything except for the first line of the node
298+ Tail,
299+}
227300
228− loop {
229− let node_kind = node.kind();
230− let start = node.start_position().row;
231− if current_line != start {
232− // Indent/dedent by at most one per line:
233− // .map(|a| { <-- ({ is two scopes
234− // let len = 1; <-- indents one level
235− // }) <-- }) is two scopes
236− if consider_indent || increment_from_line < 0 {
237− increment += increment_from_line.signum();
301+/// Execute the indent query.
302+/// Returns for each node (identified by its id) a list of indent captures for that node.
303+fn query_indents(
304+ query: &Query,
305+ syntax: &Syntax,
306+ cursor: &mut QueryCursor,
307+ text: RopeSlice,
308+ range: std::ops::Range<usize>,
309+ // Position of the (optional) newly inserted line break.
310+ // Given as (line, byte_pos)
311+ new_line_break: Option<(usize, usize)>,
312+) -> HashMap<usize, Vec<IndentCapture>> {
313+ let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
314+ cursor.set_byte_range(range);
315+ // Iterate over all captures from the query
316+ for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
317+ // Skip matches where not all custom predicates are fulfilled
318+ if !query.general_predicates(m.pattern_index).iter().all(|pred| {
319+ match pred.operator.as_ref() {
320+ "not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) {
321+ (
322+ Some(QueryPredicateArg::Capture(capture_idx)),
323+ Some(QueryPredicateArg::String(kind)),
324+ ) => {
325+ let node = m.nodes_for_capture_index(*capture_idx).next();
326+ match node {
327+ Some(node) => node.kind()!=kind.as_ref(),
328+ _ => true,
329+ }
330+ }
331+ _ => {
332+ panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
333+ }
334+ },
335+ "same-line?" | "not-same-line?" => {
336+ match (pred.args.get(0), pred.args.get(1)) {
337+ (
338+ Some(QueryPredicateArg::Capture(capt1)),
339+ Some(QueryPredicateArg::Capture(capt2))
340+ ) => {
341+ let get_line_num = |node: Node| {
342+ let mut node_line = node.start_position().row;
343+ // Adjust for the new line that will be inserted
344+ if let Some((line, byte)) = new_line_break {
345+ if node_line==line && node.start_byte()>=byte {
346+ node_line += 1;
347+ }
348+ }
349+ node_line
350+ };
351+ let n1 = m.nodes_for_capture_index(*capt1).next();
352+ let n2 = m.nodes_for_capture_index(*capt2).next();
353+ match (n1, n2) {
354+ (Some(n1), Some(n2)) => {
355+ let same_line = get_line_num(n1)==get_line_num(n2);
356+ same_line==(pred.operator.as_ref()=="same-line?")
357+ }
358+ _ => true,
359+ }
360+ }
361+ _ => {
362+ panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator);
363+ }
364+ }
365+ }
366+ _ => {
367+ panic!(
368+ "Invalid indent query: Unknown predicate (\"{}\")",
369+ pred.operator
370+ );
371+ }
238372 }
239− increment_from_line = 0;
240− current_line = start;
241− consider_indent = true;
373+ }) {
374+ continue;
242375 }
243−
244− if query.outdent.contains(node_kind) {
245− increment_from_line -= 1;
376+ for capture in m.captures {
377+ let capture_type = query.capture_names()[capture.index as usize].as_str();
378+ let capture_type = match capture_type {
379+ "indent" => IndentCaptureType::Indent,
380+ "outdent" => IndentCaptureType::Outdent,
381+ _ => {
382+ // Ignore any unknown captures (these may be needed for predicates such as #match?)
383+ continue;
384+ }
385+ };
386+ let scope = capture_type.default_scope();
387+ let mut indent_capture = IndentCapture {
388+ capture_type,
389+ scope,
390+ };
391+ // Apply additional settings for this capture
392+ for property in query.property_settings(m.pattern_index) {
393+ match property.key.as_ref() {
394+ "scope" => {
395+ indent_capture.scope = match property.value.as_deref() {
396+ Some("all") => IndentScope::All,
397+ Some("tail") => IndentScope::Tail,
398+ Some(s) => {
399+ panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s);
400+ }
401+ None => {
402+ panic!(
403+ "Invalid indent query: Missing value for \"scope\" property"
404+ );
405+ }
406+ }
407+ }
408+ _ => {
409+ panic!(
410+ "Invalid indent query: Unknown property \"{}\"",
411+ property.key
412+ );
413+ }
414+ }
415+ }
416+ indent_captures
417+ .entry(capture.node.id())
418+ // Most entries only need to contain a single IndentCapture
419+ .or_insert_with(|| Vec::with_capacity(1))
420+ .push(indent_capture);
246421 }
247− if query.indent.contains(node_kind) {
248− increment_from_line += 1;
422+ }
423+ indent_captures
424+}
425+
426+/// Use the syntax tree to determine the indentation for a given position.
427+/// This can be used in 2 ways:
428+///
429+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
430+/// - In this case, pos should be inside the first tree-sitter node on that line.
431+/// In most cases, this can just be the first non-whitespace on that line.
432+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
433+/// after pos were moved to a new line.
434+///
435+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
436+/// Each of these nodes produces some [AddedIndent] for:
437+///
438+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
439+/// - The line after the node. This is defined by:
440+/// - The scope `tail`.
441+/// - The scope `all` if this node is not the first node on its line.
442+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
443+/// The indents from different nodes for the same line are then combined.
444+/// The [IndentResult] is simply the sum of the [AddedIndent] for all lines.
445+///
446+/// Specifying which line exactly an [AddedIndent] applies to is important because indents on the same line combine differently than indents on different lines:
447+/// ```ignore
448+/// some_function(|| {
449+/// // Both the function parameters as well as the contained block should be indented.
450+/// // Because they are on the same line, this only yields one indent level
451+/// });
452+/// ```
453+///
454+/// ```ignore
455+/// some_function(
456+/// parm1,
457+/// || {
458+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
459+/// },
460+/// );
461+/// ```
462+pub fn treesitter_indent_for_pos(
463+ query: &Query,
464+ syntax: &Syntax,
465+ indent_style: &IndentStyle,
466+ text: RopeSlice,
467+ line: usize,
468+ pos: usize,
469+ new_line: bool,
470+) -> Option<String> {
471+ let byte_pos = text.char_to_byte(pos);
472+ let mut node = syntax
473+ .tree()
474+ .root_node()
475+ .descendant_for_byte_range(byte_pos, byte_pos)?;
476+ let mut first_in_line = get_first_in_line(node, byte_pos, new_line);
477+ let new_line_break = if new_line {
478+ Some((line, byte_pos))
479+ } else {
480+ None
481+ };
482+ let query_result = crate::syntax::PARSER.with(|ts_parser| {
483+ let mut ts_parser = ts_parser.borrow_mut();
484+ let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
485+ let query_result = query_indents(
486+ query,
487+ syntax,
488+ &mut cursor,
489+ text,
490+ byte_pos..byte_pos + 1,
491+ new_line_break,
492+ );
493+ ts_parser.cursors.push(cursor);
494+ query_result
495+ });
496+
497+ let mut result = Indentation::default();
498+ // We always keep track of all the indent changes on one line, in order to only indent once
499+ // even if there are multiple "indent" nodes on the same line
500+ let mut indent_for_line = Indentation::default();
501+ let mut indent_for_line_below = Indentation::default();
502+ loop {
503+ // This can safely be unwrapped because `first_in_line` contains
504+ // one entry for each ancestor of the node (which is what we iterate over)
505+ let is_first = *first_in_line.last().unwrap();
506+ // Apply all indent definitions for this node
507+ if let Some(definitions) = query_result.get(&node.id()) {
508+ for definition in definitions {
509+ match definition.scope {
510+ IndentScope::All => {
511+ if is_first {
512+ indent_for_line.add_capture(definition.capture_type);
513+ } else {
514+ indent_for_line_below.add_capture(definition.capture_type);
515+ }
516+ }
517+ IndentScope::Tail => {
518+ indent_for_line_below.add_capture(definition.capture_type);
519+ }
520+ }
521+ }
249522 }
250523
251524 if let Some(parent) = node.parent() {
525+ let mut node_line = node.start_position().row;
526+ let mut parent_line = parent.start_position().row;
527+ if node_line == line && new_line {
528+ // Also consider the line that will be inserted
529+ if node.start_byte() >= byte_pos {
530+ node_line += 1;
531+ }
532+ if parent.start_byte() >= byte_pos {
533+ parent_line += 1;
534+ }
535+ };
536+ if node_line != parent_line {
537+ if node_line < line + (new_line as usize) {
538+ // Don't add indent for the line below the line of the query
539+ result.add_line(&indent_for_line_below);
540+ }
541+ if node_line == parent_line + 1 {
542+ indent_for_line_below = indent_for_line;
543+ } else {
544+ result.add_line(&indent_for_line);
545+ indent_for_line_below = Indentation::default();
546+ }
547+ indent_for_line = Indentation::default();
548+ }
549+
252550 node = parent;
551+ first_in_line.pop();
253552 } else {
553+ result.add_line(&indent_for_line_below);
554+ result.add_line(&indent_for_line);
254555 break;
255556 }
256557 }
257− if consider_indent || increment_from_line < 0 {
258− increment += increment_from_line.signum();
259− }
260− increment.max(0) as usize
558+ Some(result.as_string(indent_style))
261559}
262560
263−// TODO: two usecases: if we are triggering this for a new, blank line:
264−// - it should return 0 when mass indenting stuff
265−// - it should look up the wrapper node and count it too when we press o/O
266−pub fn suggested_indent_for_pos(
561+/// Returns the indentation for a new line.
562+/// This is done either using treesitter, or if that's not available by copying the indentation from the current line
563+#[allow(clippy::too_many_arguments)]
564+pub fn indent_for_newline(
267565 language_config: Option<&LanguageConfiguration>,
268566 syntax: Option<&Syntax>,
567+ indent_style: &IndentStyle,
568+ tab_width: usize,
269569 text: RopeSlice,
270− pos: usize,
271− line: usize,
272− new_line: bool,
273−) -> Option<usize> {
570+ line_before: usize,
571+ line_before_end_pos: usize,
572+ current_line: usize,
573+) -> String {
274574 if let (Some(query), Some(syntax)) = (
275575 language_config.and_then(|config| config.indent_query()),
276576 syntax,
277577 ) {
278− let byte_start = text.char_to_byte(pos);
279− let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
280− // TODO: special case for comments
281− // TODO: if preserve_leading_whitespace
282− Some(calculate_indentation(query, node, line, new_line))
283− } else {
284− None
578+ if let Some(indent) = treesitter_indent_for_pos(
579+ query,
580+ syntax,
581+ indent_style,
582+ text,
583+ line_before,
584+ line_before_end_pos,
585+ true,
586+ ) {
587+ return indent;
588+ };
285589 }
590+ let indent_level = indent_level_for_line(text.line(current_line), tab_width);
591+ indent_style.as_str().repeat(indent_level)
286592}
287593
288594pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
@@ -326,156 +632,4 @@ mod test {
326632 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
327633 assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
328634 }
329−
330− #[test]
331− fn test_suggested_indent_for_line() {
332− let doc = Rope::from(
333− "
334−use std::{
335− io::{self, stdout, Stdout, Write},
336− path::PathBuf,
337− sync::Arc,
338− time::Duration,
339−}
340−mod test {
341− fn hello_world() {
342− 1 + 1;
343−
344− let does_indentation_work = 1;
345−
346− let test_function = function_with_param(this_param,
347− that_param
348− );
349−
350− let test_function = function_with_param(
351− this_param,
352− that_param
353− );
354−
355− let test_function = function_with_proper_indent(param1,
356− param2,
357− );
358−
359− let selection = Selection::new(
360− changes
361− .clone()
362− .map(|(start, end, text): (usize, usize, Option<Tendril>)| {
363− let len = text.map(|text| text.len()).unwrap() - 1; // minus newline
364− let pos = start + len;
365− Range::new(pos, pos)
366− })
367− .collect(),
368− 0,
369− );
370−
371− return;
372− }
373−}
374−
375−impl<A, D> MyTrait<A, D> for YourType
376−where
377− A: TraitB + TraitC,
378− D: TraitE + TraitF,
379−{
380−
381−}
382−#[test]
383−//
384−match test {
385− Some(a) => 1,
386− None => {
387− unimplemented!()
388− }
389−}
390−std::panic::set_hook(Box::new(move |info| {
391− hook(info);
392−}));
393−
394−{ { {
395− 1
396−}}}
397−
398−pub fn change<I>(document: &Document, changes: I) -> Self
399−where
400− I: IntoIterator<Item = Change> + ExactSizeIterator,
401−{
402− [
403− 1,
404− 2,
405− 3,
406− ];
407− (
408− 1,
409− 2
410− );
411− true
412−}
413−",
414− );
415−
416− let doc = doc;
417− use crate::diagnostic::Severity;
418− use crate::syntax::{
419− Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
420− };
421− use once_cell::sync::OnceCell;
422− let loader = Loader::new(Configuration {
423− language: vec![LanguageConfiguration {
424− scope: "source.rust".to_string(),
425− file_types: vec!["rs".to_string()],
426− shebangs: vec![],
427− language_id: "Rust".to_string(),
428− highlight_config: OnceCell::new(),
429− config: None,
430− //
431− injection_regex: None,
432− roots: vec![],
433− comment_token: None,
434− auto_format: false,
435− diagnostic_severity: Severity::Warning,
436− grammar: None,
437− language_server: None,
438− indent: Some(IndentationConfiguration {
439− tab_width: 4,
440− unit: String::from(" "),
441− }),
442− indent_query: OnceCell::new(),
443− textobject_query: OnceCell::new(),
444− debugger: None,
445− auto_pairs: None,
446− }],
447− });
448−
449− // set runtime path so we can find the queries
450− let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
451− runtime.push("../runtime");
452− std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
453−
454− let language_config = loader.language_config_for_scope("source.rust").unwrap();
455− let highlight_config = language_config.highlight_config(&[]).unwrap();
456− let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader));
457− let text = doc.slice(..);
458− let tab_width = 4;
459−
460− for i in 0..doc.len_lines() {
461− let line = text.line(i);
462− if let Some(pos) = crate::find_first_non_whitespace_char(line) {
463− let indent = indent_level_for_line(line, tab_width);
464− assert_eq!(
465− suggested_indent_for_pos(
466− Some(&language_config),
467− Some(&syntax),
468− text,
469− text.line_to_char(i) + pos,
470− i,
471− false
472− ),
473− Some(indent),
474− "line {}: \"{}\"",
475− i,
476− line
477− );
478− }
479− }
480− }
481635}
helix-core/src/syntax.rs+10−21
@@ -92,7 +92,7 @@ pub struct LanguageConfiguration {
9292 pub indent: Option<IndentationConfiguration>,
9393
9494 #[serde(skip)]
95− pub(crate) indent_query: OnceCell<Option<IndentQuery>>,
95+ pub(crate) indent_query: OnceCell<Option<Query>>,
9696 #[serde(skip)]
9797 pub(crate) textobject_query: OnceCell<Option<TextObjectQuery>>,
9898 #[serde(skip_serializing_if = "Option::is_none")]
@@ -220,17 +220,6 @@ impl FromStr for AutoPairConfig {
220220 }
221221}
222222
223−#[derive(Debug, Serialize, Deserialize)]
224−#[serde(rename_all = "kebab-case")]
225−pub struct IndentQuery {
226− #[serde(default)]
227− #[serde(skip_serializing_if = "HashSet::is_empty")]
228− pub indent: HashSet<String>,
229− #[serde(default)]
230− #[serde(skip_serializing_if = "HashSet::is_empty")]
231− pub outdent: HashSet<String>,
232−}
233−
234223#[derive(Debug)]
235224pub struct TextObjectQuery {
236225 pub query: Query,
@@ -404,13 +393,13 @@ impl LanguageConfiguration {
404393 self.highlight_config.get().is_some()
405394 }
406395
407− pub fn indent_query(&self) -> Option<&IndentQuery> {
396+ pub fn indent_query(&self) -> Option<&Query> {
408397 self.indent_query
409398 .get_or_init(|| {
410− let language = self.language_id.to_ascii_lowercase();
411−
412− let toml = load_runtime_file(&language, "indents.toml").ok()?;
413− toml::from_slice(toml.as_bytes()).ok()
399+ let lang_name = self.language_id.to_ascii_lowercase();
400+ let query_text = read_query(&lang_name, "indents.scm");
401+ let lang = self.highlight_config.get()?.as_ref()?.language;
402+ Query::new(lang, &query_text).ok()
414403 })
415404 .as_ref()
416405 }
@@ -557,7 +546,7 @@ impl Loader {
557546
558547pub struct TsParser {
559548 parser: tree_sitter::Parser,
560− cursors: Vec<QueryCursor>,
549+ pub cursors: Vec<QueryCursor>,
561550}
562551
563552// could also just use a pool, or a single instance?
@@ -1180,7 +1169,7 @@ struct HighlightIter<'a> {
11801169}
11811170
11821171// Adapter to convert rope chunks to bytes
1183−struct ChunksBytes<'a> {
1172+pub struct ChunksBytes<'a> {
11841173 chunks: ropey::iter::Chunks<'a>,
11851174}
11861175impl<'a> Iterator for ChunksBytes<'a> {
@@ -1190,7 +1179,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
11901179 }
11911180}
11921181
1193−struct RopeProvider<'a>(RopeSlice<'a>);
1182+pub struct RopeProvider<'a>(pub RopeSlice<'a>);
11941183impl<'a> TextProvider<'a> for RopeProvider<'a> {
11951184 type I = ChunksBytes<'a>;
11961185
@@ -2126,7 +2115,7 @@ mod test {
21262115 #[test]
21272116 fn test_load_runtime_file() {
21282117 // Test to make sure we can load some data from the runtime directory.
2129− let contents = load_runtime_file("rust", "indents.toml").unwrap();
2118+ let contents = load_runtime_file("rust", "indents.scm").unwrap();
21302119 assert!(!contents.is_empty());
21312120
21322121 let results = load_runtime_file("rust", "does-not-exist");
helix-core/tests/data/indent/indent.rsnew file+1
@@ -0,0 +1 @@
1+../../../src/indent.rs
helix-core/tests/data/indent/languages.tomlnew file+13
@@ -0,0 +1,13 @@
1+# This languages.toml should contain definitions for all languages for which we have indent tests
2+[[language]]
3+name = "rust"
4+scope = "source.rust"
5+injection-regex = "rust"
6+file-types = ["rs"]
7+comment-token = "//"
8+roots = ["Cargo.toml", "Cargo.lock"]
9+indent = { tab-width = 4, unit = " " }
10+
11+[[grammar]]
12+name = "rust"
13+source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a360da0a29a19c281d08295a35ecd0544d2da211" }
helix-core/tests/data/indent/rust.rsnew file+105
@@ -0,0 +1,105 @@
1+use std::{
2+ io::{self, stdout, Stdout, Write},
3+ path::PathBuf,
4+ sync::Arc,
5+ time::Duration,
6+};
7+mod test {
8+ fn hello_world() {
9+ 1 + 1;
10+
11+ let does_indentation_work = 1;
12+
13+ let mut really_long_variable_name_using_up_the_line =
14+ really_long_fn_that_should_definitely_go_on_the_next_line();
15+ really_long_variable_name_using_up_the_line =
16+ really_long_fn_that_should_definitely_go_on_the_next_line();
17+ really_long_variable_name_using_up_the_line |=
18+ really_long_fn_that_should_definitely_go_on_the_next_line();
19+
20+ let (
21+ a_long_variable_name_in_this_tuple,
22+ b_long_variable_name_in_this_tuple,
23+ c_long_variable_name_in_this_tuple,
24+ d_long_variable_name_in_this_tuple,
25+ e_long_variable_name_in_this_tuple,
26+ ): (usize, usize, usize, usize, usize) =
27+ if really_long_fn_that_should_definitely_go_on_the_next_line() {
28+ (
29+ 03294239434,
30+ 1213412342314,
31+ 21231234134,
32+ 834534234549898789,
33+ 9879234234543853457,
34+ )
35+ } else {
36+ (0, 1, 2, 3, 4)
37+ };
38+
39+ let test_function = function_with_param(this_param,
40+ that_param
41+ );
42+
43+ let test_function = function_with_param(
44+ this_param,
45+ that_param
46+ );
47+
48+ let test_function = function_with_proper_indent(param1,
49+ param2,
50+ );
51+
52+ let selection = Selection::new(
53+ changes
54+ .clone()
55+ .map(|(start, end, text): (usize, usize, Option<Tendril>)| {
56+ let len = text.map(|text| text.len()).unwrap() - 1; // minus newline
57+ let pos = start + len;
58+ Range::new(pos, pos)
59+ })
60+ .collect(),
61+ 0,
62+ );
63+
64+ return;
65+ }
66+}
67+
68+impl<A, D> MyTrait<A, D> for YourType
69+where
70+ A: TraitB + TraitC,
71+ D: TraitE + TraitF,
72+{
73+
74+}
75+#[test]
76+//
77+match test {
78+ Some(a) => 1,
79+ None => {
80+ unimplemented!()
81+ }
82+}
83+std::panic::set_hook(Box::new(move |info| {
84+ hook(info);
85+}));
86+
87+{ { {
88+ 1
89+}}}
90+
91+pub fn change<I>(document: &Document, changes: I) -> Self
92+where
93+ I: IntoIterator<Item = Change> + ExactSizeIterator,
94+{
95+ [
96+ 1,
97+ 2,
98+ 3,
99+ ];
100+ (
101+ 1,
102+ 2
103+ );
104+ true
105+}
helix-core/tests/indent.rsnew file+68
@@ -0,0 +1,68 @@
1+use helix_core::{
2+ indent::{treesitter_indent_for_pos, IndentStyle},
3+ syntax::Loader,
4+ Syntax,
5+};
6+use std::path::PathBuf;
7+
8+#[test]
9+fn test_treesitter_indent_rust() {
10+ test_treesitter_indent("rust.rs", "source.rust");
11+}
12+#[test]
13+fn test_treesitter_indent_rust_2() {
14+ test_treesitter_indent("indent.rs", "source.rust");
15+ // TODO Use commands.rs as indentation test.
16+ // Currently this fails because we can't align the parameters of a closure yet
17+ // test_treesitter_indent("commands.rs", "source.rust");
18+}
19+
20+fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
21+ let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
22+ test_dir.push("tests/data/indent");
23+
24+ let mut test_file = test_dir.clone();
25+ test_file.push(file_name);
26+ let test_file = std::fs::File::open(test_file).unwrap();
27+ let doc = ropey::Rope::from_reader(test_file).unwrap();
28+
29+ let mut config_file = test_dir;
30+ config_file.push("languages.toml");
31+ let config = std::fs::read(config_file).unwrap();
32+ let config = toml::from_slice(&config).unwrap();
33+ let loader = Loader::new(config);
34+
35+ // set runtime path so we can find the queries
36+ let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
37+ runtime.push("../runtime");
38+ std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
39+
40+ let language_config = loader.language_config_for_scope(lang_scope).unwrap();
41+ let highlight_config = language_config.highlight_config(&[]).unwrap();
42+ let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader));
43+ let indent_query = language_config.indent_query().unwrap();
44+ let text = doc.slice(..);
45+
46+ for i in 0..doc.len_lines() {
47+ let line = text.line(i);
48+ if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
49+ let suggested_indent = treesitter_indent_for_pos(
50+ indent_query,
51+ &syntax,
52+ &IndentStyle::Spaces(4),
53+ text,
54+ i,
55+ text.line_to_char(i) + pos,
56+ false,
57+ )
58+ .unwrap();
59+ assert!(
60+ line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
61+ "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
62+ i+1,
63+ line.slice(..line.len_chars()-1),
64+ suggested_indent,
65+ );
66+ }
67+ }
68+}
helix-term/src/commands.rs+12−16
@@ -2240,17 +2240,16 @@ fn open(cx: &mut Context, open: Open) {
22402240 )
22412241 };
22422242
2243− // TODO: share logic with insert_newline for indentation
2244− let indent_level = indent::suggested_indent_for_pos(
2243+ let indent = indent::indent_for_newline(
22452244 doc.language_config(),
22462245 doc.syntax(),
2246+ &doc.indent_style,
2247+ doc.tab_width(),
22472248 text,
2248− line_end_index,
22492249 new_line.saturating_sub(1),
2250− true,
2251− )
2252− .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
2253− let indent = doc.indent_unit().repeat(indent_level);
2250+ line_end_index,
2251+ cursor_line,
2252+ );
22542253 let indent_len = indent.len();
22552254 let mut text = String::with_capacity(1 + indent_len);
22562255 text.push_str(doc.line_ending.as_str());
@@ -2703,19 +2702,16 @@ pub mod insert {
27032702 let curr = contents.get_char(pos).unwrap_or(' ');
27042703
27052704 let current_line = text.char_to_line(pos);
2706− let indent_level = indent::suggested_indent_for_pos(
2705+ let indent = indent::indent_for_newline(
27072706 doc.language_config(),
27082707 doc.syntax(),
2708+ &doc.indent_style,
2709+ doc.tab_width(),
27092710 text,
2711+ current_line,
27102712 pos,
27112713 current_line,
2712− true,
2713− )
2714− .unwrap_or_else(|| {
2715− indent::indent_level_for_line(text.line(current_line), doc.tab_width())
2716− });
2717−
2718− let indent = doc.indent_unit().repeat(indent_level);
2714+ );
27192715 let mut text = String::new();
27202716 // If we are between pairs (such as brackets), we want to
27212717 // insert an additional line which is indented one level
@@ -2727,7 +2723,7 @@ pub mod insert {
27272723 .is_some();
27282724
27292725 let new_head_pos = if on_auto_pair {
2730− let inner_indent = doc.indent_unit().repeat(indent_level + 1);
2726+ let inner_indent = indent.clone() + doc.indent_style.as_str();
27312727 text.reserve_exact(2 + indent.len() + inner_indent.len());
27322728 text.push_str(doc.line_ending.as_str());
27332729 text.push_str(&inner_indent);
helix-term/src/health.rs+1−1
@@ -19,7 +19,7 @@ impl TsFeature {
1919 match *self {
2020 Self::Highlight => "highlights.scm",
2121 Self::TextObject => "textobjects.scm",
22− Self::AutoIndent => "indents.toml",
22+ Self::AutoIndent => "indents.scm",
2323 }
2424 }
2525
runtime/queries/c/indents.scmnew file+33
@@ -0,0 +1,33 @@
1+[
2+ (compound_statement)
3+ (field_declaration_list)
4+ (enumerator_list)
5+ (parameter_list)
6+ (init_declarator)
7+ (case_statement)
8+ (expression_statement)
9+] @indent
10+
11+[
12+ "case"
13+ "}"
14+ "]"
15+] @outdent
16+
17+(if_statement
18+ consequence: (_) @indent
19+ (#not-kind-eq? @indent "compound_statement")
20+ (#set! "scope" "all"))
21+(while_statement
22+ body: (_) @indent
23+ (#not-kind-eq? @indent "compound_statement")
24+ (#set! "scope" "all"))
25+(do_statement
26+ body: (_) @indent
27+ (#not-kind-eq? @indent "compound_statement")
28+ (#set! "scope" "all"))
29+(for_statement
30+ ")"
31+ (_) @indent
32+ (#not-kind-eq? @indent "compound_statement")
33+ (#set! "scope" "all"))
runtime/queries/c/indents.tomldeleted−16
@@ -1,16 +0,0 @@
1−indent = [
2− "compound_statement",
3− "field_declaration_list",
4− "enumerator_list",
5− "parameter_list",
6− "init_declarator",
7− "case_statement",
8− "condition_clause",
9− "expression_statement",
10−]
11−
12−outdent = [
13− "case",
14− "}",
15− "]",
16−]
runtime/queries/cmake/indents.scmnew file+10
@@ -0,0 +1,10 @@
1+[
2+ (if_condition)
3+ (foreach_loop)
4+ (while_loop)
5+ (function_def)
6+ (macro_def)
7+ (normal_command)
8+] @indent
9+
10+")" @outdent
runtime/queries/cmake/indents.tomldeleted−12
@@ -1,12 +0,0 @@
1−indent = [
2− "if_condition",
3− "foreach_loop",
4− "while_loop",
5− "function_def",
6− "macro_def",
7− "normal_command",
8−]
9−
10−outdent = [
11− ")"
12−]
runtime/queries/cpp/indents.scmnew file+3
@@ -0,0 +1,3 @@
1+; inherits: c
2+
3+(access_specifier) @outdent
runtime/queries/cpp/indents.tomldeleted−17
@@ -1,17 +0,0 @@
1−indent = [
2− "compound_statement",
3− "field_declaration_list",
4− "enumerator_list",
5− "parameter_list",
6− "init_declarator",
7− "case_statement",
8− "condition_clause",
9− "expression_statement",
10−]
11−
12−outdent = [
13− "case",
14− "access_specifier",
15− "}",
16− "]",
17−]
runtime/queries/dart/indents.scmnew file+20
@@ -0,0 +1,20 @@
1+[
2+ (class_body)
3+ (function_body)
4+ (function_expression_body)
5+ (declaration)
6+ (initializers)
7+ (switch_block)
8+ (if_statement)
9+ (formal_parameter_list)
10+ (formal_parameter)
11+ (list_literal)
12+ (return_statement)
13+ (arguments)
14+] @indent
15+
16+[
17+ "}"
18+ "]"
19+ ")"
20+] @outdent
runtime/queries/dart/indents.tomldeleted−20
@@ -1,20 +0,0 @@
1−indent = [
2− "class_body",
3− "function_body",
4− "function_expression_body",
5− "declaration",
6− "initializers",
7− "switch_block",
8− "if_statement",
9− "formal_parameter_list",
10− "formal_parameter",
11− "list_literal",
12− "return_statement",
13− "arguments"
14−]
15−
16−outdent = [
17− "}",
18− "]",
19− ")"
20−]
runtime/queries/fish/indents.scmnew file+12
@@ -0,0 +1,12 @@
1+[
2+ (function_definition)
3+ (while_statement)
4+ (for_statement)
5+ (if_statement)
6+ (begin_statement)
7+ (switch_statement)
8+] @indent
9+
10+[
11+ "end"
12+] @outdent
runtime/queries/fish/indents.tomldeleted−12
@@ -1,12 +0,0 @@
1−indent = [
2− "function_definition",
3− "while_statement",
4− "for_statement",
5− "if_statement",
6− "begin_statement",
7− "switch_statement",
8−]
9−
10−outdent = [
11− "end"
12−]
runtime/queries/glsl/indents.scmnew file+19
@@ -0,0 +1,19 @@
1+[
2+ (init_declarator)
3+ (compound_statement)
4+ (preproc_arg)
5+ (field_declaration_list)
6+ (case_statement)
7+ (conditional_expression)
8+ (enumerator_list)
9+ (struct_specifier)
10+ (compound_literal_expression)
11+] @indent
12+
13+[
14+ "#define"
15+ "#ifdef"
16+ "#endif"
17+ "{"
18+ "}"
19+] @outdent
runtime/queries/glsl/indents.tomldeleted−19
@@ -1,19 +0,0 @@
1−indent = [
2− "init_declarator",
3− "compound_statement",
4− "preproc_arg",
5− "field_declaration_list",
6− "case_statement",
7− "conditional_expression",
8− "enumerator_list",
9− "struct_specifier",
10− "compound_literal_expression"
11−]
12−
13−outdent = [
14− "#define",
15− "#ifdef",
16− "#endif",
17− "{",
18− "}"
19−]
runtime/queries/go/indents.scmnew file+26
@@ -0,0 +1,26 @@
1+[
2+ (import_declaration)
3+ (const_declaration)
4+ (type_declaration)
5+ (type_spec)
6+ (func_literal)
7+ (literal_value)
8+ (element)
9+ (keyed_element)
10+ (expression_case)
11+ (default_case)
12+ (type_case)
13+ (communication_case)
14+ (argument_list)
15+ (field_declaration_list)
16+ (block)
17+ (type_switch_statement)
18+ (expression_switch_statement)
19+] @indent
20+
21+[
22+ "case"
23+ "}"
24+ "]"
25+ ")"
26+] @outdent
runtime/queries/go/indents.tomldeleted−30
@@ -1,30 +0,0 @@
1−indent = [
2− "import_declaration",
3− "const_declaration",
4− #"var_declaration",
5− #"short_var_declaration",
6− "type_declaration",
7− "type_spec",
8− # simply block should be enough
9− # "function_declaration",
10− # "method_declaration",
11− # "composite_literal",
12− "func_literal",
13− "literal_value",
14− "element",
15− "keyed_element",
16− "expression_case",
17− "default_case",
18− "type_case",
19− "communication_case",
20− "argument_list",
21− "field_declaration_list",
22− "block",
23−]
24−
25−outdent = [
26− "case",
27− "}",
28− "]",
29− ")"
30−]
runtime/queries/hcl/indents.scmnew file+13
@@ -0,0 +1,13 @@
1+[
2+ (object)
3+ (block)
4+ (tuple)
5+ (for_tuple_expr)
6+ (for_object_expr)
7+] @indent
8+
9+[
10+ (object_end)
11+ (block_end)
12+ (tuple_end)
13+] @outdent
runtime/queries/hcl/indents.tomldeleted−13
@@ -1,13 +0,0 @@
1−indent = [
2− "object",
3− "block",
4− "tuple",
5− "for_tuple_expr",
6− "for_object_expr"
7−]
8−
9−outdent = [
10− "object_end",
11− "block_end",
12− "tuple_end"
13−]
runtime/queries/javascript/indents.scmnew file+22
@@ -0,0 +1,22 @@
1+[
2+ (array)
3+ (object)
4+ (arguments)
5+ (formal_parameters)
6+
7+ (statement_block)
8+ (object_pattern)
9+ (class_body)
10+ (named_imports)
11+
12+ (binary_expression)
13+ (return_statement)
14+ (template_substitution)
15+ (export_clause)
16+] @indent
17+
18+[
19+ "}"
20+ "]"
21+ ")"
22+] @outdent
runtime/queries/javascript/indents.tomldeleted−28
@@ -1,28 +0,0 @@
1−indent = [
2− "array",
3− "object",
4− "arguments",
5− "formal_parameters",
6−
7− "statement_block",
8− "object_pattern",
9− "class_body",
10− "named_imports",
11−
12− "binary_expression",
13− "return_statement",
14− "template_substitution",
15− # (expression_statement (call_expression))
16− "export_clause",
17−
18− # typescript
19− "enum_declaration",
20− "interface_declaration",
21− "object_type",
22−]
23−
24−outdent = [
25− "}",
26− "]",
27− ")"
28−]
runtime/queries/json/indents.scmnew file+9
@@ -0,0 +1,9 @@
1+[
2+ (object)
3+ (array)
4+] @indent
5+
6+[
7+ "]"
8+ "}"
9+] @outdent
runtime/queries/json/indents.tomldeleted−9
@@ -1,9 +0,0 @@
1−indent = [
2− "object",
3− "array"
4−]
5−
6−outdent = [
7− "]",
8− "}"
9−]
runtime/queries/llvm-mir-yaml/indents.scmnew file+2
@@ -0,0 +1,2 @@
1+(block_mapping_pair) @indent
2+
runtime/queries/llvm-mir-yaml/indents.tomldeleted−3
@@ -1,3 +0,0 @@
1−indent = [
2− "block_mapping_pair",
3−]
runtime/queries/llvm-mir/indents.scmnew file+3
@@ -0,0 +1,3 @@
1+(basic_block) @indent
2+
3+(label) @outdent
runtime/queries/llvm-mir/indents.tomldeleted−7
@@ -1,7 +0,0 @@
1−indent = [
2− "basic_block",
3−]
4−
5−outdent = [
6− "label",
7−]
runtime/queries/llvm/indents.scmnew file+6
@@ -0,0 +1,6 @@
1+[
2+ (function_body)
3+ (instruction)
4+] @indent
5+
6+"}" @outdent
runtime/queries/llvm/indents.tomldeleted−8
@@ -1,8 +0,0 @@
1−indent = [
2− "function_body",
3− "instruction",
4−]
5−
6−outdent = [
7− "}",
8−]
runtime/queries/lua/indents.scmnew file+24
@@ -0,0 +1,24 @@
1+[
2+ (function_definition)
3+ (variable_declaration)
4+ (local_variable_declaration)
5+ (field)
6+ (local_function)
7+ (function)
8+ (if_statement)
9+ (for_statement)
10+ (for_in_statement)
11+ (repeat_statement)
12+ (return_statement)
13+ (while_statement)
14+ (table)
15+ (arguments)
16+ (do_statement)
17+] @indent
18+
19+[
20+ "end"
21+ "until"
22+ "}"
23+ ")"
24+] @outdent
runtime/queries/lua/indents.tomldeleted−24
@@ -1,24 +0,0 @@
1−indent = [
2− "function_definition",
3− "variable_declaration",
4− "local_variable_declaration",
5− "field",
6− "local_function",
7− "function",
8− "if_statement",
9− "for_statement",
10− "for_in_statement",
11− "repeat_statement",
12− "return_statement",
13− "while_statement",
14− "table",
15− "arguments",
16− "do_statement",
17−]
18−
19−oudent = [
20− "end",
21− "until",
22− "}",
23− ")",
24−]
runtime/queries/nix/indents.scmnew file+18
@@ -0,0 +1,18 @@
1+[
2+ ; "function",
3+ (bind)
4+ (assert)
5+ (with)
6+ (let)
7+ (if)
8+
9+ (attrset)
10+ (list)
11+ (indented_string)
12+ (parenthesized)
13+] @indent
14+
15+[
16+ "}"
17+ "]"
18+] @outdent
runtime/queries/nix/indents.tomldeleted−18
@@ -1,18 +0,0 @@
1−indent = [
2− # "function",
3− "bind",
4− "assert",
5− "with",
6− "let",
7− "if",
8−
9− "attrset",
10− "list",
11− "indented_string",
12− "parenthesized",
13−]
14−
15−outdent = [
16− "}",
17− "]",
18−]
runtime/queries/ocaml/indents.scmnew file+12
@@ -0,0 +1,12 @@
1+[
2+ (let_binding)
3+ (type_binding)
4+ (structure)
5+ (signature)
6+ (record_declaration)
7+ (function_expression)
8+ (match_case)
9+] @indent
10+
11+"}" @outdent
12+
runtime/queries/ocaml/indents.tomldeleted−13
@@ -1,13 +0,0 @@
1−indent = [
2− "let_binding",
3− "type_binding",
4− "structure",
5− "signature",
6− "record_declaration",
7− "function_expression",
8− "match_case",
9−]
10−
11−outdent = [
12− "}",
13−]
runtime/queries/perl/indents.scmnew file+15
@@ -0,0 +1,15 @@
1+[
2+ (function)
3+ (identifier)
4+ (method_invocation)
5+ (if_statement)
6+ (unless_statement)
7+ (if_simple_statement)
8+ (unless_simple_statement)
9+ (variable_declaration)
10+ (block)
11+ (list_item)
12+ (word_list_qw)
13+] @indent
14+
15+"}" @outdent
runtime/queries/perl/indents.tomldeleted−17
@@ -1,17 +0,0 @@
1−indent = [
2− "function",
3− "identifier",
4− "method_invocation",
5− "if_statement",
6− "unless_statement",
7− "if_simple_statement",
8− "unless_simple_statement",
9− "variable_declaration",
10− "block",
11− "list_item",
12− "word_list_qw"
13−]
14−
15−outdent = [
16− "}"
17−]
runtime/queries/php/indents.scmnew file+17
@@ -0,0 +1,17 @@
1+[
2+ (array_creation_expression)
3+ (arguments)
4+ (formal_parameters)
5+ (compound_statement)
6+ (declaration_list)
7+ (binary_expression)
8+ (return_statement)
9+ (expression_statement)
10+ (switch_block)
11+ (anonymous_function_use_clause)
12+] @indent
13+
14+[
15+ "}"
16+ ")"
17+] @outdent
runtime/queries/php/indents.tomldeleted−17
@@ -1,17 +0,0 @@
1−indent = [
2− "array_creation_expression",
3− "arguments",
4− "formal_parameters",
5− "compound_statement",
6− "declaration_list",
7− "binary_expression",
8− "return_statement",
9− "expression_statement",
10− "switch_block",
11− "anonymous_function_use_clause",
12−]
13−
14−oudent = [
15− "}",
16− ")",
17−]
runtime/queries/protobuf/indents.scmnew file+11
@@ -0,0 +1,11 @@
1+[
2+ (messageBody)
3+ (enumBody)
4+ (oneofBody)
5+ (serviceBody)
6+ (rpcBody)
7+ (msgLit)
8+] @indent
9+
10+"}" @outdent
11+
runtime/queries/protobuf/indents.tomldeleted−12
@@ -1,12 +0,0 @@
1−indent = [
2− "messageBody",
3− "enumBody",
4− "oneofBody",
5− "serviceBody",
6− "rpcBody",
7− "msgLit",
8−]
9−
10−outdent = [
11− "}",
12−]
runtime/queries/python/indents.scmnew file+38
@@ -0,0 +1,38 @@
1+[
2+ (list)
3+ (tuple)
4+ (dictionary)
5+ (set)
6+
7+ (if_statement)
8+ (for_statement)
9+ (while_statement)
10+ (with_statement)
11+ (try_statement)
12+ (import_from_statement)
13+
14+ (parenthesized_expression)
15+ (generator_expression)
16+ (list_comprehension)
17+ (set_comprehension)
18+ (dictionary_comprehension)
19+
20+ (tuple_pattern)
21+ (list_pattern)
22+ (argument_list)
23+ (parameters)
24+ (binary_operator)
25+
26+ (function_definition)
27+ (class_definition)
28+] @indent
29+
30+[
31+ ")"
32+ "]"
33+ "}"
34+ (return_statement)
35+ (pass_statement)
36+ (raise_statement)
37+] @outdent
38+
runtime/queries/python/indents.tomldeleted−39
@@ -1,39 +0,0 @@
1−indent = [
2− "list",
3− "tuple",
4− "dictionary",
5− "set",
6−
7− "if_statement",
8− "for_statement",
9− "while_statement",
10− "with_statement",
11− "try_statement",
12− "import_from_statement",
13−
14− "parenthesized_expression",
15− "generator_expression",
16− "list_comprehension",
17− "set_comprehension",
18− "dictionary_comprehension",
19−
20− "tuple_pattern",
21− "list_pattern",
22− "argument_list",
23− "parameters",
24− "binary_operator",
25−
26− "function_definition",
27− "class_definition",
28−]
29−
30−outdent = [
31− ")",
32− "]",
33− "}",
34− "return_statement",
35− "pass_statement",
36− "raise_statement",
37−]
38−
39−ignore = ["string"]
runtime/queries/ruby/indents.scmnew file+25
@@ -0,0 +1,25 @@
1+[
2+ (argument_list)
3+ (array)
4+ (begin)
5+ (block)
6+ (call)
7+ (class)
8+ (case)
9+ (do_block)
10+ (elsif)
11+ (if)
12+ (hash)
13+ (method)
14+ (module)
15+ (singleton_class)
16+ (singleton_method)
17+] @indent
18+
19+[
20+ ")"
21+ "}"
22+ "]"
23+ "end"
24+ "when"
25+] @outdent
runtime/queries/ruby/indents.tomldeleted−25
@@ -1,25 +0,0 @@
1−indent = [
2− "argument_list",
3− "array",
4− "begin",
5− "block",
6− "call",
7− "class",
8− "case",
9− "do_block",
10− "elsif",
11− "if",
12− "hash",
13− "method",
14− "module",
15− "singleton_class",
16− "singleton_method",
17−]
18−
19−outdent = [
20− ")",
21− "}",
22− "]",
23− "end",
24− "when",
25−]
runtime/queries/rust/indents.scmnew file+80
@@ -0,0 +1,80 @@
1+[
2+ (use_list)
3+ (block)
4+ (match_block)
5+ (arguments)
6+ (parameters)
7+ (declaration_list)
8+ (field_declaration_list)
9+ (field_initializer_list)
10+ (struct_pattern)
11+ (tuple_pattern)
12+ (unit_expression)
13+ (enum_variant_list)
14+ (call_expression)
15+ (binary_expression)
16+ (field_expression)
17+ (tuple_expression)
18+ (array_expression)
19+ (where_clause)
20+
21+ (token_tree)
22+ (macro_definition)
23+ (token_tree_pattern)
24+ (token_repetition)
25+] @indent
26+
27+[
28+ "}"
29+ "]"
30+ ")"
31+] @outdent
32+
33+; Indent the right side of assignments.
34+; The #not-same-line? predicate is required to prevent an extra indent for e.g.
35+; an else-clause where the previous if-clause starts on the same line as the assignment.
36+(assignment_expression
37+ .
38+ (_) @expr-start
39+ right: (_) @indent
40+ (#not-same-line? @indent @expr-start)
41+ (#set! "scope" "all")
42+)
43+(compound_assignment_expr
44+ .
45+ (_) @expr-start
46+ right: (_) @indent
47+ (#not-same-line? @indent @expr-start)
48+ (#set! "scope" "all")
49+)
50+(let_declaration
51+ .
52+ (_) @expr-start
53+ value: (_) @indent
54+ (#not-same-line? @indent @expr-start)
55+ (#set! "scope" "all")
56+)
57+(if_let_expression
58+ .
59+ (_) @expr-start
60+ value: (_) @indent
61+ (#not-same-line? @indent @expr-start)
62+ (#set! "scope" "all")
63+)
64+(static_item
65+ .
66+ (_) @expr-start
67+ value: (_) @indent
68+ (#not-same-line? @indent @expr-start)
69+ (#set! "scope" "all")
70+)
71+
72+; Some field expressions where the left part is a multiline expression are not
73+; indented by cargo fmt.
74+; Because this multiline expression might be nested in an arbitrary number of
75+; field expressions, this can only be matched using a Regex.
76+(field_expression
77+ value: (_) @val
78+ "." @outdent
79+ (#match? @val "(\\A[^\\n\\r]+\\([\\t ]*(\\n|\\r).*)|(\\A[^\\n\\r]*\\{[\\t ]*(\\n|\\r))")
80+)
runtime/queries/rust/indents.tomldeleted−28
@@ -1,28 +0,0 @@
1−indent = [
2− "use_list",
3− "block",
4− "match_block",
5− "arguments",
6− "parameters",
7− "declaration_list",
8− "field_declaration_list",
9− "field_initializer_list",
10− "struct_pattern",
11− "tuple_pattern",
12− "unit_expression",
13− "enum_variant_list",
14− "call_expression",
15− "binary_expression",
16− "field_expression",
17− "tuple_expression",
18− "array_expression",
19− "where_clause",
20− "macro_invocation"
21−]
22−
23−outdent = [
24− "where",
25− "}",
26− "]",
27− ")"
28−]
runtime/queries/scala/indents.scmnew file+22
@@ -0,0 +1,22 @@
1+[
2+ (block)
3+ (arguments)
4+ (parameter)
5+ (class_definition)
6+ (trait_definition)
7+ (object_definition)
8+ (function_definition)
9+ (val_definition)
10+ (import_declaration)
11+ (while_expression)
12+ (do_while_expression)
13+ (for_expression)
14+ (try_expression)
15+ (match_expression)
16+] @indent
17+
18+[
19+ "}"
20+ "]"
21+ ")"
22+] @outdent
runtime/queries/scala/indents.tomldeleted−23
@@ -1,23 +0,0 @@
1−
2−indent = [
3− "block",
4− "arguments",
5− "parameter",
6− "class_definition",
7− "trait_definition",
8− "object_definition",
9− "function_definition",
10− "val_definition",
11− "import_declaration",
12− "while_expression",
13− "do_while_expression",
14− "for_expression",
15− "try_expression",
16− "match_expression"
17−]
18−
19−outdent = [
20− "}",
21− "]",
22− ")"
23−]
runtime/queries/svelte/indents.scmnew file+17
@@ -0,0 +1,17 @@
1+[
2+ (element)
3+ (if_statement)
4+ (each_statement)
5+ (await_statement)
6+] @indent
7+
8+[
9+ (end_tag)
10+ (else_statement)
11+ (if_end_expr)
12+ (each_end_expr)
13+ (await_end_expr)
14+ ">"
15+ "/>"
16+] @outdent
17+
runtime/queries/svelte/indents.tomldeleted−18
@@ -1,18 +0,0 @@
1−indent = [
2− "element"
3− "if_statement"
4− "each_statement"
5− "await_statement"
6−]
7−
8−outdent = [
9− "end_tag"
10− "else_statement"
11− "if_end_expr"
12− "each_end_expr"
13− "await_end_expr"
14− ">"
15− "/>"
16−]
17−
18−ignore = "comment"
runtime/queries/tablegen/indents.scmnew file+3
@@ -0,0 +1,3 @@
1+(statement) @indent
2+
3+"}" @outdent
runtime/queries/tablegen/indents.tomldeleted−7
@@ -1,7 +0,0 @@
1−indent = [
2− "statement",
3−]
4−
5−outdent = [
6− "}",
7−]
runtime/queries/typescript/indents.scmnew file+7
@@ -0,0 +1,7 @@
1+; inherits: javascript
2+
3+[
4+ (enum_declaration)
5+ (interface_declaration)
6+ (object_type)
7+] @indent
runtime/queries/typescript/indents.tomldeleted−1
@@ -1 +0,0 @@
1−../javascript/indents.toml
runtime/queries/yaml/indents.scmnew file+2
@@ -0,0 +1,2 @@
1+(block_mapping_pair) @indent
2+
runtime/queries/yaml/indents.tomldeleted−3
@@ -1,3 +0,0 @@
1−indent = [
2− "block_mapping_pair",
3−]
runtime/queries/zig/indents.scmnew file+16
@@ -0,0 +1,16 @@
1+[
2+ (Block)
3+ (BlockExpr)
4+ (ContainerDecl)
5+ (SwitchExpr)
6+ (AssignExpr)
7+ (ErrorUnionExpr)
8+ (Statement)
9+ (InitList)
10+] @indent
11+
12+[
13+ "}"
14+ "]"
15+ ")"
16+] @outdent
runtime/queries/zig/indents.tomldeleted−16
@@ -1,16 +0,0 @@
1−indent = [
2− "Block",
3− "BlockExpr",
4− "ContainerDecl",
5− "SwitchExpr",
6− "AssignExpr",
7− "ErrorUnionExpr",
8− "Statement",
9− "InitList"
10−]
11−
12−outdent = [
13− "}",
14− "]",
15− ")"
16−]