@@ -1,6 +1,5 @@
11use crate::{
22 chars::{char_is_line_ending, char_is_whitespace},
3− find_first_non_whitespace_char,
43 syntax::{IndentQuery, LanguageConfiguration, Syntax},
54 tree_sitter::Node,
65 Rope, RopeSlice,
@@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
174173
175174/// To determine indentation of a newly inserted line, figure out the indentation at the last col
176175/// of the previous line.
177−#[allow(dead_code)]
178−fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
176+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
179177 let mut len = 0;
180178 for ch in line.chars() {
181179 match ch {
@@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
210208 Some(node)
211209}
212210
213−fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
214− // NOTE: can't use contains() on query because of comparing Vec<String> and &str
215− // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
216−
211+/// Calculate the indentation at a given treesitter node.
212+/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
213+/// This is because the indentation is only increased starting at the second line of the node.
214+fn calculate_indentation(
215+ query: &IndentQuery,
216+ node: Option<Node>,
217+ line: usize,
218+ newline: bool,
219+) -> usize {
217220 let mut increment: isize = 0;
218221
219222 let mut node = match node {
@@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
221224 None => return 0,
222225 };
223226
224− let mut prev_start = node.start_position().row;
225−
226− // if we're calculating indentation for a brand new line then the current node will become the
227− // parent node. We need to take it's indentation level into account too.
228− let node_kind = node.kind();
229− if newline && query.indent.contains(node_kind) {
230− increment += 1;
231− }
232−
233− while let Some(parent) = node.parent() {
234− let parent_kind = parent.kind();
235− let start = parent.start_position().row;
236−
237− // detect deeply nested indents in the same line
238− // .map(|a| { <-- ({ is two scopes
239− // let len = 1; <-- indents one level
240− // }) <-- }) is two scopes
241− let starts_same_line = start == prev_start;
242−
243− if query.outdent.contains(node.kind()) && !starts_same_line {
244− // we outdent by skipping the rules for the current level and jumping up
245− // node = parent;
246− increment -= 1;
247− // continue;
227+ let mut current_line = line;
228+ let mut consider_indent = newline;
229+ let mut increment_from_line: isize = 0;
230+
231+ loop {
232+ let node_kind = node.kind();
233+ let start = node.start_position().row;
234+ if current_line != start {
235+ // Indent/dedent by at most one per line:
236+ // .map(|a| { <-- ({ is two scopes
237+ // let len = 1; <-- indents one level
238+ // }) <-- }) is two scopes
239+ if consider_indent || increment_from_line < 0 {
240+ increment += increment_from_line.signum();
241+ }
242+ increment_from_line = 0;
243+ current_line = start;
244+ consider_indent = true;
248245 }
249246
250− if query.indent.contains(parent_kind) // && not_first_or_last_sibling
251− && !starts_same_line
252− {
253− // println!("is_scope {}", parent_kind);
254− prev_start = start;
255− increment += 1
247+ if query.outdent.contains(node_kind) {
248+ increment_from_line -= 1;
249+ }
250+ if query.indent.contains(node_kind) {
251+ increment_from_line += 1;
256252 }
257253
258− // if last_scope && increment > 0 && ...{ ignore }
259−
260− node = parent;
254+ if let Some(parent) = node.parent() {
255+ node = parent;
256+ } else {
257+ break;
258+ }
259+ }
260+ if consider_indent || increment_from_line < 0 {
261+ increment += increment_from_line.signum();
261262 }
262−
263263 increment.max(0) as usize
264264}
265265
266−#[allow(dead_code)]
267−fn suggested_indent_for_line(
268− language_config: &LanguageConfiguration,
269− syntax: Option<&Syntax>,
270− text: RopeSlice,
271− line_num: usize,
272− _tab_width: usize,
273−) -> usize {
274− if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
275− return suggested_indent_for_pos(
276− Some(language_config),
277− syntax,
278− text,
279− start + text.line_to_char(line_num),
280− false,
281− );
282− };
283−
284− // if the line is blank, indent should be zero
285− 0
286−}
287−
288266// TODO: two usecases: if we are triggering this for a new, blank line:
289267// - it should return 0 when mass indenting stuff
290268// - it should look up the wrapper node and count it too when we press o/O
@@ -293,23 +271,20 @@ pub fn suggested_indent_for_pos(
293271 syntax: Option<&Syntax>,
294272 text: RopeSlice,
295273 pos: usize,
274+ line: usize,
296275 new_line: bool,
297−) -> usize {
276+) -> Option<usize> {
298277 if let (Some(query), Some(syntax)) = (
299278 language_config.and_then(|config| config.indent_query()),
300279 syntax,
301280 ) {
302281 let byte_start = text.char_to_byte(pos);
303282 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
304−
305− // let config = load indentation query config from Syntax(should contain language_config)
306−
307283 // TODO: special case for comments
308284 // TODO: if preserve_leading_whitespace
309− calculate_indentation(query, node, new_line)
285+ Some(calculate_indentation(query, node, line, new_line))
310286 } else {
311− // TODO: heuristics for non-tree sitter grammars
312− 0
287+ None
313288 }
314289}
315290
@@ -484,14 +459,23 @@ where
484459
485460 for i in 0..doc.len_lines() {
486461 let line = text.line(i);
487− let indent = indent_level_for_line(line, tab_width);
488− assert_eq!(
489− suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
490− indent,
491− "line {}: {}",
492− i,
493− line
494− );
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+ }
495479 }
496480 }
497481}