mirror of https://github.com/helix-editor/helix
Make the indent heuristic configurable
parent
559bfc1f5e
commit
938a710904
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
chars::{char_is_line_ending, char_is_whitespace},
|
chars::{char_is_line_ending, char_is_whitespace},
|
||||||
find_first_non_whitespace_char,
|
find_first_non_whitespace_char,
|
||||||
graphemes::{grapheme_width, tab_width_at},
|
graphemes::{grapheme_width, tab_width_at},
|
||||||
syntax::{LanguageConfiguration, RopeProvider, Syntax},
|
syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
|
||||||
tree_sitter::Node,
|
tree_sitter::Node,
|
||||||
Rope, RopeGraphemes, RopeSlice,
|
Rope, RopeGraphemes, RopeSlice,
|
||||||
};
|
};
|
||||||
|
@ -931,6 +931,7 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||||
pub fn indent_for_newline(
|
pub fn indent_for_newline(
|
||||||
language_config: Option<&LanguageConfiguration>,
|
language_config: Option<&LanguageConfiguration>,
|
||||||
syntax: Option<&Syntax>,
|
syntax: Option<&Syntax>,
|
||||||
|
indent_heuristic: &IndentationHeuristic,
|
||||||
indent_style: &IndentStyle,
|
indent_style: &IndentStyle,
|
||||||
tab_width: usize,
|
tab_width: usize,
|
||||||
text: RopeSlice,
|
text: RopeSlice,
|
||||||
|
@ -939,7 +940,12 @@ pub fn indent_for_newline(
|
||||||
current_line: usize,
|
current_line: usize,
|
||||||
) -> String {
|
) -> String {
|
||||||
let indent_width = indent_style.indent_width(tab_width);
|
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()),
|
language_config.and_then(|config| config.indent_query()),
|
||||||
syntax,
|
syntax,
|
||||||
) {
|
) {
|
||||||
|
@ -953,49 +959,51 @@ pub fn indent_for_newline(
|
||||||
line_before_end_pos,
|
line_before_end_pos,
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
// We want to compute the indentation not only based on the
|
if let IndentationHeuristic::Hybrid = indent_heuristic {
|
||||||
// syntax tree but also on the actual indentation of a previous
|
// We want to compute the indentation not only based on the
|
||||||
// line. This makes indentation computation more resilient to
|
// syntax tree but also on the actual indentation of a previous
|
||||||
// incomplete queries, incomplete source code & differing indentation
|
// line. This makes indentation computation more resilient to
|
||||||
// styles for the same language.
|
// incomplete queries, incomplete source code & differing indentation
|
||||||
// However, using the indent of a previous line as a baseline may not
|
// styles for the same language.
|
||||||
// make sense, e.g. if it has a different alignment than the new line.
|
// However, using the indent of a previous line as a baseline may not
|
||||||
// In order to prevent edge cases with long running times, we only try
|
// make sense, e.g. if it has a different alignment than the new line.
|
||||||
// a constant number of (non-empty) lines.
|
// In order to prevent edge cases with long running times, we only try
|
||||||
const MAX_ATTEMPTS: usize = 2;
|
// a constant number of (non-empty) lines.
|
||||||
let mut num_attempts = 0;
|
const MAX_ATTEMPTS: usize = 2;
|
||||||
for line_idx in (0..=line_before).rev() {
|
let mut num_attempts = 0;
|
||||||
let line = text.line(line_idx);
|
for line_idx in (0..=line_before).rev() {
|
||||||
let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
|
let line = text.line(line_idx);
|
||||||
Some(i) => i,
|
let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
|
||||||
None => {
|
Some(i) => i,
|
||||||
continue;
|
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);
|
return indent.to_string(indent_style, tab_width);
|
||||||
|
|
|
@ -442,6 +442,22 @@ pub struct IndentationConfiguration {
|
||||||
pub unit: String,
|
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
|
/// Configuration for auto pairs
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
|
||||||
|
|
|
@ -3053,6 +3053,7 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
|
||||||
let indent = indent::indent_for_newline(
|
let indent = indent::indent_for_newline(
|
||||||
language_config,
|
language_config,
|
||||||
syntax,
|
syntax,
|
||||||
|
&doc.config.load().indent_heuristic,
|
||||||
&doc.indent_style,
|
&doc.indent_style,
|
||||||
tab_width,
|
tab_width,
|
||||||
text,
|
text,
|
||||||
|
@ -3181,6 +3182,7 @@ fn open(cx: &mut Context, open: Open) {
|
||||||
let indent = indent::indent_for_newline(
|
let indent = indent::indent_for_newline(
|
||||||
doc.language_config(),
|
doc.language_config(),
|
||||||
doc.syntax(),
|
doc.syntax(),
|
||||||
|
&doc.config.load().indent_heuristic,
|
||||||
&doc.indent_style,
|
&doc.indent_style,
|
||||||
doc.tab_width(),
|
doc.tab_width(),
|
||||||
text,
|
text,
|
||||||
|
@ -3720,6 +3722,7 @@ pub mod insert {
|
||||||
let indent = indent::indent_for_newline(
|
let indent = indent::indent_for_newline(
|
||||||
doc.language_config(),
|
doc.language_config(),
|
||||||
doc.syntax(),
|
doc.syntax(),
|
||||||
|
&doc.config.load().indent_heuristic,
|
||||||
&doc.indent_style,
|
&doc.indent_style,
|
||||||
doc.tab_width(),
|
doc.tab_width(),
|
||||||
text,
|
text,
|
||||||
|
|
|
@ -42,7 +42,7 @@ use anyhow::{anyhow, bail, Error};
|
||||||
pub use helix_core::diagnostic::Severity;
|
pub use helix_core::diagnostic::Severity;
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
auto_pairs::AutoPairs,
|
auto_pairs::AutoPairs,
|
||||||
syntax::{self, AutoPairConfig, SoftWrap},
|
syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
|
||||||
Change, LineEnding, NATIVE_LINE_ENDING,
|
Change, LineEnding, NATIVE_LINE_ENDING,
|
||||||
};
|
};
|
||||||
use helix_core::{Position, Selection};
|
use helix_core::{Position, Selection};
|
||||||
|
@ -291,6 +291,9 @@ pub struct Config {
|
||||||
pub insert_final_newline: bool,
|
pub insert_final_newline: bool,
|
||||||
/// Enables smart tab
|
/// Enables smart tab
|
||||||
pub smart_tab: Option<SmartTabConfig>,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
|
||||||
|
@ -841,6 +844,7 @@ impl Default for Config {
|
||||||
default_line_ending: LineEndingConfig::default(),
|
default_line_ending: LineEndingConfig::default(),
|
||||||
insert_final_newline: true,
|
insert_final_newline: true,
|
||||||
smart_tab: Some(SmartTabConfig::default()),
|
smart_tab: Some(SmartTabConfig::default()),
|
||||||
|
indent_heuristic: IndentationHeuristic::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue