aboutsummaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
authorDaniel Ebert2023-09-19 13:31:38 +0000
committerBlaž Hrastnik2023-12-15 06:59:54 +0000
commit938a710904ae6d328d4008626d98acb9e907813a (patch)
treedf6a641d82d0c82b61ecc7ffe68011d0dca24453 /helix-core
parent559bfc1f5ef1bd43fd94325c0363058e32c76df4 (diff)
Make the indent heuristic configurable
Diffstat (limited to 'helix-core')
-rw-r--r--helix-core/src/indent.rs96
-rw-r--r--helix-core/src/syntax.rs16
2 files changed, 68 insertions, 44 deletions
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index dfb33939..26186ee8 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -6,7 +6,7 @@ use crate::{
chars::{char_is_line_ending, char_is_whitespace},
find_first_non_whitespace_char,
graphemes::{grapheme_width, tab_width_at},
- syntax::{LanguageConfiguration, RopeProvider, Syntax},
+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
tree_sitter::Node,
Rope, RopeGraphemes, RopeSlice,
};
@@ -931,6 +931,7 @@ pub fn treesitter_indent_for_pos<'a>(
pub fn indent_for_newline(
language_config: Option<&LanguageConfiguration>,
syntax: Option<&Syntax>,
+ indent_heuristic: &IndentationHeuristic,
indent_style: &IndentStyle,
tab_width: usize,
text: RopeSlice,
@@ -939,7 +940,12 @@ pub fn indent_for_newline(
current_line: usize,
) -> String {
let indent_width = indent_style.indent_width(tab_width);
- if let (Some(query), Some(syntax)) = (
+ if let (
+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
+ Some(query),
+ Some(syntax),
+ ) = (
+ indent_heuristic,
language_config.and_then(|config| config.indent_query()),
syntax,
) {
@@ -953,49 +959,51 @@ pub fn indent_for_newline(
line_before_end_pos,
true,
) {
- // We want to compute the indentation not only based on the
- // syntax tree but also on the actual indentation of a previous
- // line. This makes indentation computation more resilient to
- // incomplete queries, incomplete source code & differing indentation
- // styles for the same language.
- // However, using the indent of a previous line as a baseline may not
- // make sense, e.g. if it has a different alignment than the new line.
- // In order to prevent edge cases with long running times, we only try
- // a constant number of (non-empty) lines.
- const MAX_ATTEMPTS: usize = 2;
- let mut num_attempts = 0;
- for line_idx in (0..=line_before).rev() {
- let line = text.line(line_idx);
- let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
- Some(i) => i,
- None => {
- continue;
+ if let IndentationHeuristic::Hybrid = indent_heuristic {
+ // We want to compute the indentation not only based on the
+ // syntax tree but also on the actual indentation of a previous
+ // line. This makes indentation computation more resilient to
+ // incomplete queries, incomplete source code & differing indentation
+ // styles for the same language.
+ // However, using the indent of a previous line as a baseline may not
+ // make sense, e.g. if it has a different alignment than the new line.
+ // In order to prevent edge cases with long running times, we only try
+ // a constant number of (non-empty) lines.
+ const MAX_ATTEMPTS: usize = 2;
+ let mut num_attempts = 0;
+ for line_idx in (0..=line_before).rev() {
+ let line = text.line(line_idx);
+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
+ Some(i) => i,
+ None => {
+ continue;
+ }
+ };
+ if let Some(indent) = (|| {
+ let computed_indent = treesitter_indent_for_pos(
+ query,
+ syntax,
+ tab_width,
+ indent_width,
+ text,
+ line_idx,
+ text.line_to_char(line_idx) + first_non_whitespace_char,
+ false,
+ )?;
+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
+ indent.relative_indent(
+ &computed_indent,
+ leading_whitespace,
+ indent_style,
+ tab_width,
+ )
+ })() {
+ return indent;
+ }
+ num_attempts += 1;
+ if num_attempts == MAX_ATTEMPTS {
+ break;
}
- };
- if let Some(indent) = (|| {
- let computed_indent = treesitter_indent_for_pos(
- query,
- syntax,
- tab_width,
- indent_width,
- text,
- line_idx,
- text.line_to_char(line_idx) + first_non_whitespace_char,
- false,
- )?;
- let leading_whitespace = line.slice(0..first_non_whitespace_char);
- indent.relative_indent(
- &computed_indent,
- leading_whitespace,
- indent_style,
- tab_width,
- )
- })() {
- return indent;
- }
- num_attempts += 1;
- if num_attempts == MAX_ATTEMPTS {
- break;
}
}
return indent.to_string(indent_style, tab_width);
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 8c7fc4e1..dd922316 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -442,6 +442,22 @@ pub struct IndentationConfiguration {
pub unit: String,
}
+/// How the indentation for a newly inserted line should be determined.
+/// If the selected heuristic is not available (e.g. because the current
+/// language has no tree-sitter indent queries), a simpler one will be used.
+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum IndentationHeuristic {
+ /// Just copy the indentation of the line that the cursor is currently on.
+ Simple,
+ /// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
+ TreeSitter,
+ /// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
+ /// and the line before. Add this to the actual indentation level of the line before.
+ #[default]
+ Hybrid,
+}
+
/// Configuration for auto pairs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]