@@ -4,11 +4,10 @@ use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
44
55use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
7− find_first_non_whitespace_char,
87 graphemes::{grapheme_width, tab_width_at},
9− syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
8+ syntax::{LanguageConfiguration, RopeProvider, Syntax},
109 tree_sitter::Node,
11− Position, Rope, RopeGraphemes, RopeSlice,
10+ Rope, RopeGraphemes, RopeSlice,
1211};
1312
1413/// Enum representing indentation style.
@@ -22,7 +21,7 @@ pub enum IndentStyle {
2221
2322// 16 spaces
2423const INDENTS: &str = " ";
25−pub const MAX_INDENT: u8 = 16;
24+const MAX_INDENT: u8 = 16;
2625
2726impl IndentStyle {
2827 /// Creates an `IndentStyle` from an indentation string.
@@ -197,56 +196,6 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
197196 len / indent_width
198197}
199198
200−/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
201−fn whitespace_with_same_width(text: RopeSlice) -> String {
202− let mut s = String::new();
203− for grapheme in RopeGraphemes::new(text) {
204− if grapheme == "\t" {
205− s.push('\t');
206− } else {
207− s.extend(std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))));
208− }
209− }
210− s
211−}
212−
213−fn add_indent_level(
214− mut base_indent: String,
215− added_indent_level: isize,
216− indent_style: &IndentStyle,
217− tab_width: usize,
218−) -> String {
219− if added_indent_level >= 0 {
220− // Adding a non-negative indent is easy, we can simply append the indent string
221− base_indent.push_str(&indent_style.as_str().repeat(added_indent_level as usize));
222− base_indent
223− } else {
224− // In this case, we want to return a prefix of `base_indent`.
225− // Since the width of a tab depends on its offset, we cannot simply iterate over
226− // the chars of `base_indent` in reverse until we have the desired indent reduction,
227− // instead we iterate over them twice in forward direction.
228− let base_indent_rope = RopeSlice::from(base_indent.as_str());
229− #[allow(deprecated)]
230− let base_indent_width =
231− crate::visual_coords_at_pos(base_indent_rope, base_indent_rope.len_chars(), tab_width)
232− .col;
233− let target_indent_width = base_indent_width
234− .saturating_sub((-added_indent_level) as usize * indent_style.indent_width(tab_width));
235− #[allow(deprecated)]
236− let char_end_idx = crate::pos_at_visual_coords(
237− base_indent_rope,
238− Position {
239− row: 0,
240− col: target_indent_width,
241− },
242− tab_width,
243− );
244− let byte_end_idx = base_indent_rope.char_to_byte(char_end_idx);
245− base_indent.truncate(byte_end_idx);
246− base_indent
247− }
248−}
249−
250199/// Computes for node and all ancestors whether they are the first node on their line.
251200/// The first entry in the return value represents the root node, the last one the node itself
252201fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -292,21 +241,21 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
292241/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293242/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294243#[derive(Default, Debug, PartialEq, Eq, Clone)]
295−pub struct Indentation<'a> {
244+pub struct Indentation {
296245 indent: usize,
297246 indent_always: usize,
298247 outdent: usize,
299248 outdent_always: usize,
300249 /// The alignment, as a string containing only tabs & spaces. Storing this as a string instead of e.g.
301250 /// the (visual) width ensures that the alignment is preserved even if the tab width changes.
302− align: Option<RopeSlice<'a>>,
251+ align: Option<String>,
303252}
304253
305−impl<'a> Indentation<'a> {
254+impl Indentation {
306255 /// Add some other [Indentation] to this.
307256 /// The added indent should be the total added indent from one line.
308257 /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
309− fn add_line(&mut self, added: Indentation<'a>) {
258+ fn add_line(&mut self, added: Indentation) {
310259 // Align overrides the indent from outer scopes.
311260 if self.align.is_some() {
312261 return;
@@ -325,7 +274,7 @@ impl<'a> Indentation<'a> {
325274 /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
326275 /// and the captures should be added starting from the innermost tree-sitter node (currently this only matters
327276 /// if multiple `@align` patterns occur on the same line).
328− fn add_capture(&mut self, added: IndentCaptureType<'a>) {
277+ fn add_capture(&mut self, added: IndentCaptureType) {
329278 match added {
330279 IndentCaptureType::Indent => {
331280 if self.indent_always == 0 {
@@ -354,62 +303,43 @@ impl<'a> Indentation<'a> {
354303 }
355304 }
356305 }
357− fn net_indent(&self) -> isize {
358− (self.indent + self.indent_always) as isize
359− - ((self.outdent + self.outdent_always) as isize)
360− }
361− /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362− fn relative_indent(
363− &self,
364− other_computed_indent: &Self,
365− other_leading_whitespace: RopeSlice,
366− indent_style: &IndentStyle,
367− tab_width: usize,
368− ) -> Option<String> {
369− if self.align == other_computed_indent.align {
370− // If self and baseline are either not aligned to anything or both aligned the same way,
371− // we can simply take `other_leading_whitespace` and add some indent / outdent to it (in the second
372− // case, the alignment should already be accounted for in `other_leading_whitespace`).
373− let indent_diff = self.net_indent() - other_computed_indent.net_indent();
374− Some(add_indent_level(
375− String::from(other_leading_whitespace),
376− indent_diff,
377− indent_style,
378− tab_width,
379− ))
306+ fn into_string(self, indent_style: &IndentStyle) -> String {
307+ let indent = self.indent_always + self.indent;
308+ let outdent = self.outdent_always + self.outdent;
309+
310+ let indent_level = if indent >= outdent {
311+ indent - outdent
380312 } else {
381− // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
382− None
383− }
384− }
385− pub fn to_string(&self, indent_style: &IndentStyle, tab_width: usize) -> String {
386− add_indent_level(
387− self.align
388− .map_or_else(String::new, whitespace_with_same_width),
389− self.net_indent(),
390− indent_style,
391− tab_width,
392− )
313+ log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
314+ 0
315+ };
316+ let mut indent_string = if let Some(align) = self.align {
317+ align
318+ } else {
319+ String::new()
320+ };
321+ indent_string.push_str(&indent_style.as_str().repeat(indent_level));
322+ indent_string
393323 }
394324}
395325
396326/// An indent definition which corresponds to a capture from the indent query
397327#[derive(Debug)]
398−struct IndentCapture<'a> {
399− capture_type: IndentCaptureType<'a>,
328+struct IndentCapture {
329+ capture_type: IndentCaptureType,
400330 scope: IndentScope,
401331}
402332#[derive(Debug, Clone, PartialEq)]
403−enum IndentCaptureType<'a> {
333+enum IndentCaptureType {
404334 Indent,
405335 IndentAlways,
406336 Outdent,
407337 OutdentAlways,
408338 /// Alignment given as a string of whitespace
409− Align(RopeSlice<'a>),
339+ Align(String),
410340}
411341
412−impl<'a> IndentCaptureType<'a> {
342+impl IndentCaptureType {
413343 fn default_scope(&self) -> IndentScope {
414344 match self {
415345 IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
@@ -441,8 +371,8 @@ enum ExtendCapture {
441371/// each node (identified by its ID) the relevant captures (already filtered
442372/// by predicates).
443373#[derive(Debug)]
444−struct IndentQueryResult<'a> {
445− indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
374+struct IndentQueryResult {
375+ indent_captures: HashMap<usize, Vec<IndentCapture>>,
446376 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
447377}
448378
@@ -463,14 +393,14 @@ fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
463393 node_line
464394}
465395
466−fn query_indents<'a>(
396+fn query_indents(
467397 query: &Query,
468398 syntax: &Syntax,
469399 cursor: &mut QueryCursor,
470− text: RopeSlice<'a>,
400+ text: RopeSlice,
471401 range: std::ops::Range<usize>,
472402 new_line_byte_pos: Option<usize>,
473−) -> IndentQueryResult<'a> {
403+) -> IndentQueryResult {
474404 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
475405 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
476406 cursor.set_byte_range(range);
@@ -558,7 +488,7 @@ fn query_indents<'a>(
558488 "outdent" => IndentCaptureType::Outdent,
559489 "outdent.always" => IndentCaptureType::OutdentAlways,
560490 // The alignment will be updated to the correct value at the end, when the anchor is known.
561− "align" => IndentCaptureType::Align(RopeSlice::from("")),
491+ "align" => IndentCaptureType::Align(String::from("")),
562492 "anchor" => {
563493 if anchor.is_some() {
564494 log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
@@ -630,10 +560,22 @@ fn query_indents<'a>(
630560 }
631561 Some(anchor) => anchor,
632562 };
633− capture.capture_type = IndentCaptureType::Align(
563+ // Create a string of tabs & spaces that should have the same width
564+ // as the string that precedes the anchor (independent of the tab width).
565+ let mut align = String::new();
566+ for grapheme in RopeGraphemes::new(
634567 text.line(anchor.start_position().row)
635568 .byte_slice(0..anchor.start_position().column),
636− );
569+ ) {
570+ if grapheme == "\t" {
571+ align.push('\t');
572+ } else {
573+ align.extend(
574+ std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))),
575+ );
576+ }
577+ }
578+ capture.capture_type = IndentCaptureType::Align(align);
637579 }
638580 indent_captures
639581 .entry(node_id)
@@ -719,20 +661,56 @@ fn extend_nodes<'a>(
719661 }
720662}
721663
722−/// Prepare an indent query by computing:
723−/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
724−/// - The indent captures for all relevant nodes.
664+/// Use the syntax tree to determine the indentation for a given position.
665+/// This can be used in 2 ways:
666+///
667+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
668+/// - In this case, pos should be inside the first tree-sitter node on that line.
669+/// In most cases, this can just be the first non-whitespace on that line.
670+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
671+/// after pos were moved to a new line.
672+///
673+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
674+/// Each of these nodes produces some [Indentation] for:
675+///
676+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
677+/// - The line after the node. This is defined by:
678+/// - The scope `tail`.
679+/// - The scope `all` if this node is not the first node on its line.
680+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
681+/// The indents from different nodes for the same line are then combined.
682+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
683+///
684+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
685+/// ```ignore
686+/// some_function(|| {
687+/// // Both the function parameters as well as the contained block should be indented.
688+/// // Because they are on the same line, this only yields one indent level
689+/// });
690+/// ```
691+///
692+/// ```ignore
693+/// some_function(
694+/// param1,
695+/// || {
696+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
697+/// },
698+/// );
699+/// ```
725700#[allow(clippy::too_many_arguments)]
726−fn init_indent_query<'a, 'b>(
701+pub fn treesitter_indent_for_pos(
727702 query: &Query,
728− syntax: &'a Syntax,
729− text: RopeSlice<'b>,
703+ syntax: &Syntax,
704+ indent_style: &IndentStyle,
730705 tab_width: usize,
731706 indent_width: usize,
707+ text: RopeSlice,
732708 line: usize,
733− byte_pos: usize,
734− new_line_byte_pos: Option<usize>,
735−) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
709+ pos: usize,
710+ new_line: bool,
711+) -> Option<String> {
712+ let byte_pos = text.char_to_byte(pos);
713+ let new_line_byte_pos = new_line.then_some(byte_pos);
736714 // The innermost tree-sitter node which is considered for the indent
737715 // computation. It may change if some predeceding node is extended
738716 let mut node = syntax
@@ -776,6 +754,7 @@ fn init_indent_query<'a, 'b>(
776754 (query_result, deepest_preceding)
777755 })
778756 };
757+ let mut indent_captures = query_result.indent_captures;
779758 let extend_captures = query_result.extend_captures;
780759
781760 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -790,68 +769,6 @@ fn init_indent_query<'a, 'b>(
790769 indent_width,
791770 );
792771 }
793− Some((node, query_result.indent_captures))
794−}
795−
796−/// Use the syntax tree to determine the indentation for a given position.
797−/// This can be used in 2 ways:
798−///
799−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
800−/// - In this case, pos should be inside the first tree-sitter node on that line.
801−/// In most cases, this can just be the first non-whitespace on that line.
802−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
803−/// after pos were moved to a new line.
804−///
805−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
806−/// Each of these nodes produces some [Indentation] for:
807−///
808−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
809−/// - The line after the node. This is defined by:
810−/// - The scope `tail`.
811−/// - The scope `all` if this node is not the first node on its line.
812−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
813−/// The indents from different nodes for the same line are then combined.
814−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
815−///
816−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
817−/// ```ignore
818−/// some_function(|| {
819−/// // Both the function parameters as well as the contained block should be indented.
820−/// // Because they are on the same line, this only yields one indent level
821−/// });
822−/// ```
823−///
824−/// ```ignore
825−/// some_function(
826−/// param1,
827−/// || {
828−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
829−/// },
830−/// );
831−/// ```
832−#[allow(clippy::too_many_arguments)]
833−pub fn treesitter_indent_for_pos<'a>(
834− query: &Query,
835− syntax: &Syntax,
836− tab_width: usize,
837− indent_width: usize,
838− text: RopeSlice<'a>,
839− line: usize,
840− pos: usize,
841− new_line: bool,
842−) -> Option<Indentation<'a>> {
843− let byte_pos = text.char_to_byte(pos);
844− let new_line_byte_pos = new_line.then_some(byte_pos);
845− let (mut node, mut indent_captures) = init_indent_query(
846− query,
847− syntax,
848− text,
849− tab_width,
850− indent_width,
851− line,
852− byte_pos,
853− new_line_byte_pos,
854− )?;
855772 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
856773
857774 let mut result = Indentation::default();
@@ -919,7 +836,7 @@ pub fn treesitter_indent_for_pos<'a>(
919836 break;
920837 }
921838 }
922− Some(result)
839+ Some(result.into_string(indent_style))
923840}
924841
925842/// Returns the indentation for a new line.
@@ -928,7 +845,6 @@ pub fn treesitter_indent_for_pos<'a>(
928845pub fn indent_for_newline(
929846 language_config: Option<&LanguageConfiguration>,
930847 syntax: Option<&Syntax>,
931− indent_heuristic: &IndentationHeuristic,
932848 indent_style: &IndentStyle,
933849 tab_width: usize,
934850 text: RopeSlice,
@@ -937,18 +853,14 @@ pub fn indent_for_newline(
937853 current_line: usize,
938854) -> String {
939855 let indent_width = indent_style.indent_width(tab_width);
940− if let (
941− IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942− Some(query),
943− Some(syntax),
944− ) = (
945− indent_heuristic,
856+ if let (Some(query), Some(syntax)) = (
946857 language_config.and_then(|config| config.indent_query()),
947858 syntax,
948859 ) {
949860 if let Some(indent) = treesitter_indent_for_pos(
950861 query,
951862 syntax,
863+ indent_style,
952864 tab_width,
953865 indent_width,
954866 text,
@@ -956,57 +868,9 @@ pub fn indent_for_newline(
956868 line_before_end_pos,
957869 true,
958870 ) {
959− if *indent_heuristic == IndentationHeuristic::Hybrid {
960− // We want to compute the indentation not only based on the
961− // syntax tree but also on the actual indentation of a previous
962− // line. This makes indentation computation more resilient to
963− // incomplete queries, incomplete source code & differing indentation
964− // styles for the same language.
965− // However, using the indent of a previous line as a baseline may not
966− // make sense, e.g. if it has a different alignment than the new line.
967− // In order to prevent edge cases with long running times, we only try
968− // a constant number of (non-empty) lines.
969− const MAX_ATTEMPTS: usize = 4;
970− let mut num_attempts = 0;
971− for line_idx in (0..=line_before).rev() {
972− let line = text.line(line_idx);
973− let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974− Some(i) => i,
975− None => {
976− continue;
977− }
978− };
979− if let Some(indent) = (|| {
980− let computed_indent = treesitter_indent_for_pos(
981− query,
982− syntax,
983− tab_width,
984− indent_width,
985− text,
986− line_idx,
987− text.line_to_char(line_idx) + first_non_whitespace_char,
988− false,
989− )?;
990− let leading_whitespace = line.slice(0..first_non_whitespace_char);
991− indent.relative_indent(
992− &computed_indent,
993− leading_whitespace,
994− indent_style,
995− tab_width,
996− )
997− })() {
998− return indent;
999− }
1000− num_attempts += 1;
1001− if num_attempts == MAX_ATTEMPTS {
1002− break;
1003− }
1004− }
1005− }
1006− return indent.to_string(indent_style, tab_width);
871+ return indent;
1007872 };
1008873 }
1009− // Fallback in case we either don't have indent queries or they failed for some reason
1010874 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1011875 indent_style.as_str().repeat(indent_level)
1012876}
@@ -1098,13 +962,10 @@ mod test {
1098962 ..Default::default()
1099963 };
1100964
1101− fn add_capture<'a>(
1102− mut indent: Indentation<'a>,
1103− capture: IndentCaptureType<'a>,
1104− ) -> Indentation<'a> {
965+ let add_capture = |mut indent: Indentation, capture| {
1105966 indent.add_capture(capture);
1106967 indent
1107− }
968+ };
1108969
1109970 // adding an indent to no indent makes an indent
1110971 assert_eq!(
@@ -1199,74 +1060,4 @@ mod test {
11991060 )
12001061 );
12011062 }
1202−
1203− #[test]
1204− fn test_relative_indent() {
1205− let indent_style = IndentStyle::Spaces(4);
1206− let tab_width: usize = 4;
1207− let no_align = [
1208− Indentation::default(),
1209− Indentation {
1210− indent: 1,
1211− ..Default::default()
1212− },
1213− Indentation {
1214− indent: 5,
1215− outdent: 1,
1216− ..Default::default()
1217− },
1218− ];
1219− let align = no_align.clone().map(|indent| Indentation {
1220− align: Some(RopeSlice::from("12345")),
1221− ..indent
1222− });
1223− let different_align = Indentation {
1224− align: Some(RopeSlice::from("123456")),
1225− ..Default::default()
1226− };
1227−
1228− // Check that relative and absolute indentation computation are the same when the line we compare to is
1229− // indented as we expect.
1230− let check_consistency = |indent: &Indentation, other: &Indentation| {
1231− assert_eq!(
1232− indent.relative_indent(
1233− other,
1234− RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235− &indent_style,
1236− tab_width
1237− ),
1238− Some(indent.to_string(&indent_style, tab_width))
1239− );
1240− };
1241− for a in &no_align {
1242− for b in &no_align {
1243− check_consistency(a, b);
1244− }
1245− }
1246− for a in &align {
1247− for b in &align {
1248− check_consistency(a, b);
1249− }
1250− }
1251−
1252− // Relative indent computation makes no sense if the alignment differs
1253− assert_eq!(
1254− align[0].relative_indent(
1255− &no_align[0],
1256− RopeSlice::from(" "),
1257− &indent_style,
1258− tab_width
1259− ),
1260− None
1261− );
1262− assert_eq!(
1263− align[0].relative_indent(
1264− &different_align,
1265− RopeSlice::from(" "),
1266− &indent_style,
1267− tab_width
1268− ),
1269− None
1270− );
1271− }
12721063}