@@ -1,6 +1,5 @@
1use crate::{
1use crate::{
2 chars::{char_is_line_ending, char_is_whitespace},
2 chars::{char_is_line_ending, char_is_whitespace},
3− find_first_non_whitespace_char,
4 syntax::{IndentQuery, LanguageConfiguration, Syntax},
3 syntax::{IndentQuery, LanguageConfiguration, Syntax},
5 tree_sitter::Node,
4 tree_sitter::Node,
6 Rope, RopeSlice,
5 Rope, RopeSlice,
@@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
175/// To determine indentation of a newly inserted line, figure out the indentation at the last col
174/// To determine indentation of a newly inserted line, figure out the indentation at the last col
176/// of the previous line.
175/// of the previous line.
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 {
179 let mut len = 0;
177 let mut len = 0;
180 for ch in line.chars() {
178 for ch in line.chars() {
181 match ch {
179 match ch {
@@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
210 Some(node)
208 Some(node)
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.
213−fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
214+fn calculate_indentation(
214− // NOTE: can't use contains() on query because of comparing Vec<String> and &str
215+ query: &IndentQuery,
215− // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
216+ node: Option<Node>,
217 let mut increment: isize = 0;
220 let mut increment: isize = 0;
219 let mut node = match node {
222 let mut node = match node {
@@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
221 None => return 0,
224 None => return 0,
224− let mut prev_start = node.start_position().row;
227+ let mut current_line = line;
225−
228+ let mut consider_indent = newline;
226− // if we're calculating indentation for a brand new line then the current node will become the
229+ let mut increment_from_line: isize = 0;
227− // parent node. We need to take it's indentation level into account too.
230+
228− let node_kind = node.kind();
231+ loop {
229− if newline && query.indent.contains(node_kind) {
232+ let node_kind = node.kind();
233− while let Some(parent) = node.parent() {
234− let parent_kind = parent.kind();
235− let start = parent.start_position().row;
233+ let start = node.start_position().row;
236−
234+ if current_line != start {
237− // detect deeply nested indents in the same line
235+ // Indent/dedent by at most one per line:
238− // .map(|a| { <-- ({ is two scopes
236+ // .map(|a| { <-- ({ is two scopes
239− // let len = 1; <-- indents one level
237+ // let len = 1; <-- indents one level
240− // }) <-- }) is two scopes
238+ // }) <-- }) is two scopes
241− let starts_same_line = start == prev_start;
239+ if consider_indent || increment_from_line < 0 {
242−
240+ increment += increment_from_line.signum();
243− if query.outdent.contains(node.kind()) && !starts_same_line {
241+ }
244− // we outdent by skipping the rules for the current level and jumping up
242+ increment_from_line = 0;
245− // node = parent;
243+ current_line = start;
246− increment -= 1;
244+ consider_indent = true;
250− if query.indent.contains(parent_kind) // && not_first_or_last_sibling
247+ if query.outdent.contains(node_kind) {
251− && !starts_same_line
248+ increment_from_line -= 1;
253− // println!("is_scope {}", parent_kind);
250+ if query.indent.contains(node_kind) {
254− prev_start = start;
251+ increment_from_line += 1;
258− // if last_scope && increment > 0 && ...{ ignore }
254+ if let Some(parent) = node.parent() {
260− node = parent;
256+ } else {
260+ if consider_indent || increment_from_line < 0 {
261+ increment += increment_from_line.signum();
263 increment.max(0) as usize
263 increment.max(0) as usize
267−fn suggested_indent_for_line(
268− language_config: &LanguageConfiguration,
269− syntax: Option<&Syntax>,
274− if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
275− return suggested_indent_for_pos(
276− Some(language_config),
279− start + text.line_to_char(line_num),
284− // if the line is blank, indent should be zero
288// TODO: two usecases: if we are triggering this for a new, blank line:
266// TODO: two usecases: if we are triggering this for a new, blank line:
289// - it should return 0 when mass indenting stuff
267// - it should return 0 when mass indenting stuff
290// - it should look up the wrapper node and count it too when we press o/O
268// - 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(
293 syntax: Option<&Syntax>,
271 syntax: Option<&Syntax>,
294 text: RopeSlice,
272 text: RopeSlice,
295 pos: usize,
273 pos: usize,
296 new_line: bool,
275 new_line: bool,
297−) -> usize {
276+) -> Option<usize> {
298 if let (Some(query), Some(syntax)) = (
277 if let (Some(query), Some(syntax)) = (
299 language_config.and_then(|config| config.indent_query()),
278 language_config.and_then(|config| config.indent_query()),
302 let byte_start = text.char_to_byte(pos);
281 let byte_start = text.char_to_byte(pos);
303 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
282 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
305− // let config = load indentation query config from Syntax(should contain language_config)
307 // TODO: special case for comments
283 // TODO: special case for comments
308 // TODO: if preserve_leading_whitespace
284 // TODO: if preserve_leading_whitespace
309− calculate_indentation(query, node, new_line)
285+ Some(calculate_indentation(query, node, line, new_line))
311− // TODO: heuristics for non-tree sitter grammars
287+ None
@@ -484,14 +459,23 @@ where
485 for i in 0..doc.len_lines() {
460 for i in 0..doc.len_lines() {
486 let line = text.line(i);
461 let line = text.line(i);
462+ if let Some(pos) = crate::find_first_non_whitespace_char(line) {
487− let indent = indent_level_for_line(line, tab_width);
463+ let indent = indent_level_for_line(line, tab_width);
488− assert_eq!(
464+ assert_eq!(
489− suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
465+ suggested_indent_for_pos(
466+ Some(&language_config),
469+ text.line_to_char(i) + pos,
490− indent,
473+ Some(indent),
491− "line {}: {}",
474+ "line {}: \"{}\"",