aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/indent.rs96
-rw-r--r--helix-core/src/syntax.rs16
-rw-r--r--helix-term/src/commands.rs3
-rw-r--r--helix-view/src/editor.rs6
4 files changed, 76 insertions, 45 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)]
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index e557977a..1b8f9e1f 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3053,6 +3053,7 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
let indent = indent::indent_for_newline(
language_config,
syntax,
+ &doc.config.load().indent_heuristic,
&doc.indent_style,
tab_width,
text,
@@ -3181,6 +3182,7 @@ fn open(cx: &mut Context, open: Open) {
let indent = indent::indent_for_newline(
doc.language_config(),
doc.syntax(),
+ &doc.config.load().indent_heuristic,
&doc.indent_style,
doc.tab_width(),
text,
@@ -3720,6 +3722,7 @@ pub mod insert {
let indent = indent::indent_for_newline(
doc.language_config(),
doc.syntax(),
+ &doc.config.load().indent_heuristic,
&doc.indent_style,
doc.tab_width(),
text,
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 5a81cb5d..f2e85307 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -42,7 +42,7 @@ use anyhow::{anyhow, bail, Error};
pub use helix_core::diagnostic::Severity;
use helix_core::{
auto_pairs::AutoPairs,
- syntax::{self, AutoPairConfig, SoftWrap},
+ syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
Change, LineEnding, NATIVE_LINE_ENDING,
};
use helix_core::{Position, Selection};
@@ -291,6 +291,9 @@ pub struct Config {
pub insert_final_newline: bool,
/// Enables smart tab
pub smart_tab: Option<SmartTabConfig>,
+ /// Which indent heuristic to use when a new line is inserted
+ #[serde(default)]
+ pub indent_heuristic: IndentationHeuristic,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
@@ -841,6 +844,7 @@ impl Default for Config {
default_line_ending: LineEndingConfig::default(),
insert_final_newline: true,
smart_tab: Some(SmartTabConfig::default()),
+ indent_heuristic: IndentationHeuristic::default(),
}
}
}