on master help sign in
Make the indent heuristic configurable
commit938a710904ae6d328d4008626d98acb9e907813a authorDaniel Ebert <daniel.ebert@protonmail.com> dateTue 19 Sep 2023, 15:31 (3 years ago) parent559bfc1
4 files changed+76−45
helix-core/src/indent.rs+52−44 helix-core/src/syntax.rs+16 helix-term/src/commands.rs+3 helix-view/src/editor.rs+5−1
helix-core/src/indent.rs+52−44
@@ -6,7 +6,7 @@ use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
77 find_first_non_whitespace_char,
88 graphemes::{grapheme_width, tab_width_at},
9− syntax::{LanguageConfiguration, RopeProvider, Syntax},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
1010 tree_sitter::Node,
1111 Rope, RopeGraphemes, RopeSlice,
1212};
@@ -931,6 +931,7 @@ pub fn treesitter_indent_for_pos<'a>(
931931pub fn indent_for_newline(
932932 language_config: Option<&LanguageConfiguration>,
933933 syntax: Option<&Syntax>,
934+ indent_heuristic: &IndentationHeuristic,
934935 indent_style: &IndentStyle,
935936 tab_width: usize,
936937 text: RopeSlice,
@@ -939,7 +940,12 @@ pub fn indent_for_newline(
939940 current_line: usize,
940941) -> String {
941942 let indent_width = indent_style.indent_width(tab_width);
942− if let (Some(query), Some(syntax)) = (
943+ if let (
944+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
945+ Some(query),
946+ Some(syntax),
947+ ) = (
948+ indent_heuristic,
943949 language_config.and_then(|config| config.indent_query()),
944950 syntax,
945951 ) {
@@ -953,49 +959,51 @@ pub fn indent_for_newline(
953959 line_before_end_pos,
954960 true,
955961 ) {
956− // We want to compute the indentation not only based on the
957− // syntax tree but also on the actual indentation of a previous
958− // line. This makes indentation computation more resilient to
959− // incomplete queries, incomplete source code & differing indentation
960− // styles for the same language.
961− // However, using the indent of a previous line as a baseline may not
962− // make sense, e.g. if it has a different alignment than the new line.
963− // In order to prevent edge cases with long running times, we only try
964− // a constant number of (non-empty) lines.
965− const MAX_ATTEMPTS: usize = 2;
966− let mut num_attempts = 0;
967− for line_idx in (0..=line_before).rev() {
968− let line = text.line(line_idx);
969− let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
970− Some(i) => i,
971− None => {
972− continue;
962+ if let IndentationHeuristic::Hybrid = indent_heuristic {
963+ // We want to compute the indentation not only based on the
964+ // syntax tree but also on the actual indentation of a previous
965+ // line. This makes indentation computation more resilient to
966+ // incomplete queries, incomplete source code & differing indentation
967+ // styles for the same language.
968+ // However, using the indent of a previous line as a baseline may not
969+ // make sense, e.g. if it has a different alignment than the new line.
970+ // In order to prevent edge cases with long running times, we only try
971+ // a constant number of (non-empty) lines.
972+ const MAX_ATTEMPTS: usize = 2;
973+ let mut num_attempts = 0;
974+ for line_idx in (0..=line_before).rev() {
975+ let line = text.line(line_idx);
976+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
977+ Some(i) => i,
978+ None => {
979+ continue;
980+ }
981+ };
982+ if let Some(indent) = (|| {
983+ let computed_indent = treesitter_indent_for_pos(
984+ query,
985+ syntax,
986+ tab_width,
987+ indent_width,
988+ text,
989+ line_idx,
990+ text.line_to_char(line_idx) + first_non_whitespace_char,
991+ false,
992+ )?;
993+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
994+ indent.relative_indent(
995+ &computed_indent,
996+ leading_whitespace,
997+ indent_style,
998+ tab_width,
999+ )
1000+ })() {
1001+ return indent;
1002+ }
1003+ num_attempts += 1;
1004+ if num_attempts == MAX_ATTEMPTS {
1005+ break;
9731006 }
974− };
975− if let Some(indent) = (|| {
976− let computed_indent = treesitter_indent_for_pos(
977− query,
978− syntax,
979− tab_width,
980− indent_width,
981− text,
982− line_idx,
983− text.line_to_char(line_idx) + first_non_whitespace_char,
984− false,
985− )?;
986− let leading_whitespace = line.slice(0..first_non_whitespace_char);
987− indent.relative_indent(
988− &computed_indent,
989− leading_whitespace,
990− indent_style,
991− tab_width,
992− )
993− })() {
994− return indent;
995− }
996− num_attempts += 1;
997− if num_attempts == MAX_ATTEMPTS {
998− break;
9991007 }
10001008 }
10011009 return indent.to_string(indent_style, tab_width);
helix-core/src/syntax.rs+16
@@ -442,6 +442,22 @@ pub struct IndentationConfiguration {
442442 pub unit: String,
443443}
444444
445+/// How the indentation for a newly inserted line should be determined.
446+/// If the selected heuristic is not available (e.g. because the current
447+/// language has no tree-sitter indent queries), a simpler one will be used.
448+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
449+#[serde(rename_all = "kebab-case")]
450+pub enum IndentationHeuristic {
451+ /// Just copy the indentation of the line that the cursor is currently on.
452+ Simple,
453+ /// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
454+ TreeSitter,
455+ /// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
456+ /// and the line before. Add this to the actual indentation level of the line before.
457+ #[default]
458+ Hybrid,
459+}
460+
445461/// Configuration for auto pairs
446462#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
447463#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
helix-term/src/commands.rs+3
@@ -3053,6 +3053,7 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
30533053 let indent = indent::indent_for_newline(
30543054 language_config,
30553055 syntax,
3056+ &doc.config.load().indent_heuristic,
30563057 &doc.indent_style,
30573058 tab_width,
30583059 text,
@@ -3181,6 +3182,7 @@ fn open(cx: &mut Context, open: Open) {
31813182 let indent = indent::indent_for_newline(
31823183 doc.language_config(),
31833184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
31843186 &doc.indent_style,
31853187 doc.tab_width(),
31863188 text,
@@ -3720,6 +3722,7 @@ pub mod insert {
37203722 let indent = indent::indent_for_newline(
37213723 doc.language_config(),
37223724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
37233726 &doc.indent_style,
37243727 doc.tab_width(),
37253728 text,
helix-view/src/editor.rs+5−1
@@ -42,7 +42,7 @@ use anyhow::{anyhow, bail, Error};
4242pub use helix_core::diagnostic::Severity;
4343use helix_core::{
4444 auto_pairs::AutoPairs,
45− syntax::{self, AutoPairConfig, SoftWrap},
45+ syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
4646 Change, LineEnding, NATIVE_LINE_ENDING,
4747};
4848use helix_core::{Position, Selection};
@@ -291,6 +291,9 @@ pub struct Config {
291291 pub insert_final_newline: bool,
292292 /// Enables smart tab
293293 pub smart_tab: Option<SmartTabConfig>,
294+ /// Which indent heuristic to use when a new line is inserted
295+ #[serde(default)]
296+ pub indent_heuristic: IndentationHeuristic,
294297}
295298
296299#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
@@ -841,6 +844,7 @@ impl Default for Config {
841844 default_line_ending: LineEndingConfig::default(),
842845 insert_final_newline: true,
843846 smart_tab: Some(SmartTabConfig::default()),
847+ indent_heuristic: IndentationHeuristic::default(),
844848 }
845849 }
846850}