@@ -1,13 +1,13 @@
1−use std::collections::HashMap;
1+use std::{borrow::Cow, collections::HashMap};
3use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
3use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
5use crate::{
5use crate::{
6 chars::{char_is_line_ending, char_is_whitespace},
6 chars::{char_is_line_ending, char_is_whitespace},
7− graphemes::tab_width_at,
7+ graphemes::{grapheme_width, tab_width_at},
8 syntax::{LanguageConfiguration, RopeProvider, Syntax},
8 syntax::{LanguageConfiguration, RopeProvider, Syntax},
9 tree_sitter::Node,
9 tree_sitter::Node,
10− Rope, RopeSlice,
10+ Rope, RopeGraphemes, RopeSlice,
13/// Enum representing indentation style.
13/// Enum representing indentation style.
@@ -237,19 +237,33 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
237/// This is usually constructed in one of 2 ways:
237/// This is usually constructed in one of 2 ways:
238/// - Successively add indent captures to get the (added) indent from a single line
238/// - Successively add indent captures to get the (added) indent from a single line
239/// - Successively add the indent results for each line
239/// - Successively add the indent results for each line
240+/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
241+/// - max(0, indent - outdent) tabs, if tabs are used for indentation
242+/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
240#[derive(Default, Debug, PartialEq, Eq, Clone)]
243#[derive(Default, Debug, PartialEq, Eq, Clone)]
241pub struct Indentation {
244pub struct Indentation {
242− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
243− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
244 indent: usize,
245 indent: usize,
245 indent_always: usize,
246 indent_always: usize,
246 outdent: usize,
247 outdent: usize,
247 outdent_always: usize,
248 outdent_always: usize,
249+ /// The alignment, as a string containing only tabs & spaces. Storing this as a string instead of e.g.
250+ /// the (visual) width ensures that the alignment is preserved even if the tab width changes.
251+ align: Option<String>,
249impl Indentation {
254impl Indentation {
250 /// Add some other [Indentation] to this.
255 /// Add some other [Indentation] to this.
251− /// The added indent should be the total added indent from one line
256+ /// The added indent should be the total added indent from one line.
257+ /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
252− fn add_line(&mut self, added: &Indentation) {
258+ fn add_line(&mut self, added: Indentation) {
259+ // Align overrides the indent from outer scopes.
260+ if self.align.is_some() {
263+ if added.align.is_some() {
264+ self.align = added.align;
253 self.indent += added.indent;
267 self.indent += added.indent;
254 self.indent_always += added.indent_always;
268 self.indent_always += added.indent_always;
255 self.outdent += added.outdent;
269 self.outdent += added.outdent;
@@ -280,10 +294,12 @@ impl Indentation {
280 self.outdent_always += 1;
294 self.outdent_always += 1;
281 self.outdent = 0;
295 self.outdent = 0;
297+ IndentCaptureType::Align(align) => {
298+ self.align = Some(align);
286− fn as_string(&self, indent_style: &IndentStyle) -> String {
302+ fn into_string(self, indent_style: &IndentStyle) -> String {
287 let indent = self.indent_always + self.indent;
303 let indent = self.indent_always + self.indent;
288 let outdent = self.outdent_always + self.outdent;
304 let outdent = self.outdent_always + self.outdent;
@@ -293,7 +309,13 @@ impl Indentation {
293 log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
309 log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
312+ let mut indent_string = if let Some(align) = self.align {
296− indent_style.as_str().repeat(indent_level)
317+ indent_string.push_str(&indent_style.as_str().repeat(indent_level));
@@ -303,13 +325,14 @@ struct IndentCapture {
303 capture_type: IndentCaptureType,
325 capture_type: IndentCaptureType,
304 scope: IndentScope,
326 scope: IndentScope,
307−#[derive(Debug, Clone, Copy, PartialEq)]
328+#[derive(Debug, Clone, PartialEq)]
308enum IndentCaptureType {
329enum IndentCaptureType {
310 IndentAlways,
331 IndentAlways,
312 OutdentAlways,
333 OutdentAlways,
334+ /// Alignment given as a string of whitespace
315impl IndentCaptureType {
338impl IndentCaptureType {
@@ -317,6 +340,7 @@ impl IndentCaptureType {
317 match self {
340 match self {
318 IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
341 IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
319 IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
342 IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
343+ IndentCaptureType::Align(_) => IndentScope::All,
@@ -348,46 +372,35 @@ struct IndentQueryResult {
348 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
372 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
375+fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
376+ let mut node_line = node.start_position().row;
377+ // Adjust for the new line that will be inserted
378+ if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
383+fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
384+ let mut node_line = node.end_position().row;
385+ // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
386+ if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
351fn query_indents(
392fn query_indents(
352 query: &Query,
393 query: &Query,
353 syntax: &Syntax,
394 syntax: &Syntax,
354 cursor: &mut QueryCursor,
395 cursor: &mut QueryCursor,
355 text: RopeSlice,
396 text: RopeSlice,
356 range: std::ops::Range<usize>,
397 range: std::ops::Range<usize>,
357− // Position of the (optional) newly inserted line break.
398+ new_line_byte_pos: Option<usize>,
358− // Given as (line, byte_pos)
359− new_line_break: Option<(usize, usize)>,
360) -> IndentQueryResult {
399) -> IndentQueryResult {
361 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
400 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
362 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
401 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
363 cursor.set_byte_range(range);
402 cursor.set_byte_range(range);
365− let get_node_start_line = |node: Node| {
366− let mut node_line = node.start_position().row;
368− // Adjust for the new line that will be inserted
369− if let Some((line, byte)) = new_line_break {
370− if node_line == line && node.start_byte() >= byte {
378− let get_node_end_line = |node: Node| {
379− let mut node_line = node.end_position().row;
381− // Adjust for the new line that will be inserted
382− if let Some((line, byte)) = new_line_break {
383− if node_line == line && node.end_byte() < byte {
391 // Iterate over all captures from the query
404 // Iterate over all captures from the query
392 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
405 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
393 // Skip matches where not all custom predicates are fulfilled
406 // Skip matches where not all custom predicates are fulfilled
@@ -418,8 +431,8 @@ fn query_indents(
418 let n2 = m.nodes_for_capture_index(*capt2).next();
431 let n2 = m.nodes_for_capture_index(*capt2).next();
419 match (n1, n2) {
432 match (n1, n2) {
420 (Some(n1), Some(n2)) => {
433 (Some(n1), Some(n2)) => {
421− let n1_line = get_node_start_line(n1);
434+ let n1_line = get_node_start_line(n1, new_line_byte_pos);
422− let n2_line = get_node_start_line(n2);
435+ let n2_line = get_node_start_line(n2, new_line_byte_pos);
423 let same_line = n1_line == n2_line;
436 let same_line = n1_line == n2_line;
424 same_line==(pred.operator.as_ref()=="same-line?")
437 same_line==(pred.operator.as_ref()=="same-line?")
@@ -437,7 +450,7 @@ fn query_indents(
438 match node {
451 match node {
439 Some(node) => {
452 Some(node) => {
440− let (start_line, end_line) = (get_node_start_line(node), get_node_end_line(node));
453+ let (start_line, end_line) = (get_node_start_line(node,new_line_byte_pos), get_node_end_line(node, new_line_byte_pos));
441 let one_line = end_line == start_line;
454 let one_line = end_line == start_line;
442 one_line != (pred.operator.as_ref() == "not-one-line?")
455 one_line != (pred.operator.as_ref() == "not-one-line?")
@@ -458,6 +471,11 @@ fn query_indents(
459 continue;
472 continue;
474+ // A list of pairs (node_id, indent_capture) that are added by this match.
475+ // They cannot be added to indent_captures immediately since they may depend on other captures (such as an @anchor).
476+ let mut added_indent_captures: Vec<(usize, IndentCapture)> = Vec::new();
477+ // The row/column position of the optional anchor in this query
478+ let mut anchor: Option<tree_sitter::Node> = None;
461 for capture in m.captures {
479 for capture in m.captures {
462 let capture_name = query.capture_names()[capture.index as usize].as_str();
480 let capture_name = query.capture_names()[capture.index as usize].as_str();
463 let capture_type = match capture_name {
481 let capture_type = match capture_name {
@@ -465,6 +483,16 @@ fn query_indents(
465 "indent.always" => IndentCaptureType::IndentAlways,
483 "indent.always" => IndentCaptureType::IndentAlways,
466 "outdent" => IndentCaptureType::Outdent,
484 "outdent" => IndentCaptureType::Outdent,
467 "outdent.always" => IndentCaptureType::OutdentAlways,
485 "outdent.always" => IndentCaptureType::OutdentAlways,
486+ // The alignment will be updated to the correct value at the end, when the anchor is known.
487+ "align" => IndentCaptureType::Align(String::from("")),
489+ if anchor.is_some() {
490+ log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
492+ anchor = Some(capture.node);
468 "extend" => {
496 "extend" => {
469 extend_captures
497 extend_captures
470 .entry(capture.node.id())
498 .entry(capture.node.id())
@@ -514,11 +542,41 @@ fn query_indents(
545+ added_indent_captures.push((capture.node.id(), indent_capture))
547+ for (node_id, mut capture) in added_indent_captures {
548+ // Set the anchor for all align queries.
549+ if let IndentCaptureType::Align(_) = capture.capture_type {
550+ let anchor = match anchor {
553+ "Invalid indent query: @align requires an accompanying @anchor."
557+ Some(anchor) => anchor,
559+ // Create a string of tabs & spaces that should have the same width
560+ // as the string that precedes the anchor (independent of the tab width).
561+ let mut align = String::new();
562+ for grapheme in RopeGraphemes::new(
563+ text.line(anchor.start_position().row)
564+ .byte_slice(0..anchor.start_position().column),
566+ if grapheme == "\t" {
570+ std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))),
574+ capture.capture_type = IndentCaptureType::Align(align);
517 indent_captures
576 indent_captures
518− .entry(capture.node.id())
577+ .entry(node_id)
519− // Most entries only need to contain a single IndentCapture
520 .or_insert_with(|| Vec::with_capacity(1))
578 .or_insert_with(|| Vec::with_capacity(1))
521− .push(indent_capture);
579+ .push(capture);
@@ -648,6 +706,7 @@ pub fn treesitter_indent_for_pos(
648 new_line: bool,
706 new_line: bool,
649) -> Option<String> {
707) -> Option<String> {
650 let byte_pos = text.char_to_byte(pos);
708 let byte_pos = text.char_to_byte(pos);
709+ let new_line_byte_pos = new_line.then_some(byte_pos);
651 // The innermost tree-sitter node which is considered for the indent
710 // The innermost tree-sitter node which is considered for the indent
652 // computation. It may change if some predeceding node is extended
711 // computation. It may change if some predeceding node is extended
653 let mut node = syntax
712 let mut node = syntax
@@ -685,13 +744,13 @@ pub fn treesitter_indent_for_pos(
685 &mut cursor,
744 &mut cursor,
687 query_range,
746 query_range,
688− new_line.then_some((line, byte_pos)),
747+ new_line_byte_pos,
690 ts_parser.cursors.push(cursor);
749 ts_parser.cursors.push(cursor);
691 (query_result, deepest_preceding)
750 (query_result, deepest_preceding)
694− let indent_captures = query_result.indent_captures;
753+ let mut indent_captures = query_result.indent_captures;
695 let extend_captures = query_result.extend_captures;
754 let extend_captures = query_result.extend_captures;
697 // Check for extend captures, potentially changing the node that the indent calculation starts with
756 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -719,8 +778,10 @@ pub fn treesitter_indent_for_pos(
719 // one entry for each ancestor of the node (which is what we iterate over)
778 // one entry for each ancestor of the node (which is what we iterate over)
720 let is_first = *first_in_line.last().unwrap();
779 let is_first = *first_in_line.last().unwrap();
722− // Apply all indent definitions for this node
781+ // Apply all indent definitions for this node.
782+ // Since we only iterate over each node once, we can remove the
783+ // corresponding captures from the HashMap to avoid cloning them.
723− if let Some(definitions) = indent_captures.get(&node.id()) {
784+ if let Some(definitions) = indent_captures.remove(&node.id()) {
724 for definition in definitions {
785 for definition in definitions {
725 match definition.scope {
786 match definition.scope {
726 IndentScope::All => {
787 IndentScope::All => {
@@ -738,29 +799,19 @@ pub fn treesitter_indent_for_pos(
740 if let Some(parent) = node.parent() {
801 if let Some(parent) = node.parent() {
741− let mut node_line = node.start_position().row;
802+ let node_line = get_node_start_line(node, new_line_byte_pos);
742− let mut parent_line = parent.start_position().row;
803+ let parent_line = get_node_start_line(parent, new_line_byte_pos);
744− if node_line == line && new_line {
745− // Also consider the line that will be inserted
746− if node.start_byte() >= byte_pos {
749− if parent.start_byte() >= byte_pos {
754 if node_line != parent_line {
805 if node_line != parent_line {
755 // Don't add indent for the line below the line of the query
806 // Don't add indent for the line below the line of the query
756 if node_line < line + (new_line as usize) {
807 if node_line < line + (new_line as usize) {
757− result.add_line(&indent_for_line_below);
808+ result.add_line(indent_for_line_below);
760 if node_line == parent_line + 1 {
811 if node_line == parent_line + 1 {
761 indent_for_line_below = indent_for_line;
812 indent_for_line_below = indent_for_line;
763− result.add_line(&indent_for_line);
814+ result.add_line(indent_for_line);
764 indent_for_line_below = Indentation::default();
815 indent_for_line_below = Indentation::default();
@@ -775,14 +826,13 @@ pub fn treesitter_indent_for_pos(
775 if (node.start_position().row < line)
826 if (node.start_position().row < line)
776 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
827 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
778− result.add_line(&indent_for_line_below);
829+ result.add_line(indent_for_line_below);
781− result.add_line(&indent_for_line);
831+ result.add_line(indent_for_line);
785− Some(result.as_string(indent_style))
835+ Some(result.into_string(indent_style))
788/// Returns the indentation for a new line.
838/// Returns the indentation for a new line.