summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/indent.rs140
-rw-r--r--helix-term/src/commands.rs68
-rw-r--r--runtime/queries/rust/indents.toml1
3 files changed, 98 insertions, 111 deletions
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index c2baf3cc..28066aa6 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -1,6 +1,5 @@
use crate::{
chars::{char_is_line_ending, char_is_whitespace},
- find_first_non_whitespace_char,
syntax::{IndentQuery, LanguageConfiguration, Syntax},
tree_sitter::Node,
Rope, RopeSlice,
@@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
/// To determine indentation of a newly inserted line, figure out the indentation at the last col
/// of the previous line.
-#[allow(dead_code)]
-fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
let mut len = 0;
for ch in line.chars() {
match ch {
@@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
Some(node)
}
-fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
- // NOTE: can't use contains() on query because of comparing Vec<String> and &str
- // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
-
+/// Calculate the indentation at a given treesitter node.
+/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
+/// This is because the indentation is only increased starting at the second line of the node.
+fn calculate_indentation(
+ query: &IndentQuery,
+ node: Option<Node>,
+ line: usize,
+ newline: bool,
+) -> usize {
let mut increment: isize = 0;
let mut node = match node {
@@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
None => return 0,
};
- let mut prev_start = node.start_position().row;
-
- // if we're calculating indentation for a brand new line then the current node will become the
- // parent node. We need to take it's indentation level into account too.
- let node_kind = node.kind();
- if newline && query.indent.contains(node_kind) {
- increment += 1;
- }
-
- while let Some(parent) = node.parent() {
- let parent_kind = parent.kind();
- let start = parent.start_position().row;
-
- // detect deeply nested indents in the same line
- // .map(|a| { <-- ({ is two scopes
- // let len = 1; <-- indents one level
- // }) <-- }) is two scopes
- let starts_same_line = start == prev_start;
-
- if query.outdent.contains(node.kind()) && !starts_same_line {
- // we outdent by skipping the rules for the current level and jumping up
- // node = parent;
- increment -= 1;
- // continue;
+ let mut current_line = line;
+ let mut consider_indent = newline;
+ let mut increment_from_line: isize = 0;
+
+ loop {
+ let node_kind = node.kind();
+ let start = node.start_position().row;
+ if current_line != start {
+ // Indent/dedent by at most one per line:
+ // .map(|a| { <-- ({ is two scopes
+ // let len = 1; <-- indents one level
+ // }) <-- }) is two scopes
+ if consider_indent || increment_from_line < 0 {
+ increment += increment_from_line.signum();
+ }
+ increment_from_line = 0;
+ current_line = start;
+ consider_indent = true;
}
- if query.indent.contains(parent_kind) // && not_first_or_last_sibling
- && !starts_same_line
- {
- // println!("is_scope {}", parent_kind);
- prev_start = start;
- increment += 1
+ if query.outdent.contains(node_kind) {
+ increment_from_line -= 1;
+ }
+ if query.indent.contains(node_kind) {
+ increment_from_line += 1;
}
- // if last_scope && increment > 0 && ...{ ignore }
-
- node = parent;
+ if let Some(parent) = node.parent() {
+ node = parent;
+ } else {
+ break;
+ }
+ }
+ if consider_indent || increment_from_line < 0 {
+ increment += increment_from_line.signum();
}
-
increment.max(0) as usize
}
-#[allow(dead_code)]
-fn suggested_indent_for_line(
- language_config: &LanguageConfiguration,
- syntax: Option<&Syntax>,
- text: RopeSlice,
- line_num: usize,
- _tab_width: usize,
-) -> usize {
- if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
- return suggested_indent_for_pos(
- Some(language_config),
- syntax,
- text,
- start + text.line_to_char(line_num),
- false,
- );
- };
-
- // if the line is blank, indent should be zero
- 0
-}
-
// TODO: two usecases: if we are triggering this for a new, blank line:
// - it should return 0 when mass indenting stuff
// - 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(
syntax: Option<&Syntax>,
text: RopeSlice,
pos: usize,
+ line: usize,
new_line: bool,
-) -> usize {
+) -> Option<usize> {
if let (Some(query), Some(syntax)) = (
language_config.and_then(|config| config.indent_query()),
syntax,
) {
let byte_start = text.char_to_byte(pos);
let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
-
- // let config = load indentation query config from Syntax(should contain language_config)
-
// TODO: special case for comments
// TODO: if preserve_leading_whitespace
- calculate_indentation(query, node, new_line)
+ Some(calculate_indentation(query, node, line, new_line))
} else {
- // TODO: heuristics for non-tree sitter grammars
- 0
+ None
}
}
@@ -484,14 +459,23 @@ where
for i in 0..doc.len_lines() {
let line = text.line(i);
- let indent = indent_level_for_line(line, tab_width);
- assert_eq!(
- suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
- indent,
- "line {}: {}",
- i,
- line
- );
+ if let Some(pos) = crate::find_first_non_whitespace_char(line) {
+ let indent = indent_level_for_line(line, tab_width);
+ assert_eq!(
+ suggested_indent_for_pos(
+ Some(&language_config),
+ Some(&syntax),
+ text,
+ text.line_to_char(i) + pos,
+ i,
+ false
+ ),
+ Some(indent),
+ "line {}: \"{}\"",
+ i,
+ line
+ );
+ }
}
}
}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index e61c3cf3..842d8b60 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3689,22 +3689,22 @@ fn open(cx: &mut Context, open: Open) {
let mut offs = 0;
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
- let line = range.cursor_line(text);
+ let cursor_line = range.cursor_line(text);
- let line = match open {
+ let new_line = match open {
// adjust position to the end of the line (next line - 1)
- Open::Below => line + 1,
+ Open::Below => cursor_line + 1,
// adjust position to the end of the previous line (current line - 1)
- Open::Above => line,
+ Open::Above => cursor_line,
};
// Index to insert newlines after, as well as the char width
// to use to compensate for those inserted newlines.
- let (line_end_index, line_end_offset_width) = if line == 0 {
+ let (line_end_index, line_end_offset_width) = if new_line == 0 {
(0, 0)
} else {
(
- line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
doc.line_ending.len_chars(),
)
};
@@ -3715,8 +3715,10 @@ fn open(cx: &mut Context, open: Open) {
doc.syntax(),
text,
line_end_index,
+ new_line.saturating_sub(1),
true,
- );
+ )
+ .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
let indent = doc.indent_unit().repeat(indent_level);
let indent_len = indent.len();
let mut text = String::with_capacity(1 + indent_len);
@@ -4451,48 +4453,48 @@ pub mod insert {
};
let curr = contents.get_char(pos).unwrap_or(' ');
- // TODO: offset range.head by 1? when calculating?
+ let current_line = text.char_to_line(pos);
let indent_level = indent::suggested_indent_for_pos(
doc.language_config(),
doc.syntax(),
text,
- pos.saturating_sub(1),
+ pos,
+ current_line,
true,
- );
- let indent = doc.indent_unit().repeat(indent_level);
- let mut text = String::with_capacity(1 + indent.len());
- text.push_str(doc.line_ending.as_str());
- text.push_str(&indent);
+ )
+ .unwrap_or_else(|| {
+ indent::indent_level_for_line(text.line(current_line), doc.tab_width())
+ });
- let head = pos + offs + text.chars().count();
+ let indent = doc.indent_unit().repeat(indent_level);
+ let mut text = String::new();
+ // If we are between pairs (such as brackets), we want to insert an additional line which is indented one level more and place the cursor there
+ let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
+ let inner_indent = doc.indent_unit().repeat(indent_level + 1);
+ text.reserve_exact(2 + indent.len() + inner_indent.len());
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&inner_indent);
+ let new_head_pos = pos + offs + text.chars().count();
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&indent);
+ new_head_pos
+ } else {
+ text.reserve_exact(1 + indent.len());
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&indent);
+ pos + offs + text.chars().count()
+ };
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
- ranges.push(Range::new(
- if range.is_empty() {
- head
- } else {
- range.anchor + offs
- },
- head,
- ));
-
- // if between a bracket pair
- if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
- // another newline, indent the end bracket one level less
- let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
- text.push_str(doc.line_ending.as_str());
- text.push_str(&indent);
- }
-
+ ranges.push(Range::new(new_head_pos, new_head_pos));
offs += text.chars().count();
(pos, pos, Some(text.into()))
});
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
- //
doc.apply(&transaction, view.id);
}
diff --git a/runtime/queries/rust/indents.toml b/runtime/queries/rust/indents.toml
index 3900f0b9..51a0ceea 100644
--- a/runtime/queries/rust/indents.toml
+++ b/runtime/queries/rust/indents.toml
@@ -9,6 +9,7 @@ indent = [
"field_initializer_list",
"struct_pattern",
"tuple_pattern",
+ "unit_expression",
"enum_variant_list",
"call_expression",
"binary_expression",