Skip to content

Commit

Permalink
Make the indent heuristic configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
Triton171 committed Sep 19, 2023
1 parent b1ec9a6 commit 7a1bc99
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 45 deletions.
96 changes: 52 additions & 44 deletions helix-core/src/indent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -927,6 +927,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,
Expand All @@ -935,7 +936,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,
) {
Expand All @@ -949,49 +955,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);
Expand Down
16 changes: 16 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,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)]
Expand Down
3 changes: 3 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2978,6 +2978,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,
Expand Down Expand Up @@ -3106,6 +3107,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,
Expand Down Expand Up @@ -3645,6 +3647,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,
Expand Down
6 changes: 5 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -846,6 +849,7 @@ impl Default for Config {
default_line_ending: LineEndingConfig::default(),
insert_final_newline: true,
smart_tab: Some(SmartTabConfig::default()),
indent_heuristic: IndentationHeuristic::default(),
}
}
}
Expand Down

0 comments on commit 7a1bc99

Please sign in to comment.