From 938a710904ae6d328d4008626d98acb9e907813a Mon Sep 17 00:00:00 2001 From: Daniel Ebert Date: Tue, 19 Sep 2023 15:31:38 +0200 Subject: Make the indent heuristic configurable --- helix-core/src/indent.rs | 96 ++++++++++++++++++++++++++---------------------- helix-core/src/syntax.rs | 16 ++++++++ 2 files changed, 68 insertions(+), 44 deletions(-) (limited to 'helix-core') 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)] -- cgit v1.2.3-70-g09d2