mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
Make the indent heuristic configurable
This commit is contained in:
parent
559bfc1f5e
commit
938a710904
@ -6,7 +6,7 @@
|
||||
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);
|
||||
|
@ -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)]
|
||||
|
@ -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 fn insert_newline(cx: &mut Context) {
|
||||
let indent = indent::indent_for_newline(
|
||||
doc.language_config(),
|
||||
doc.syntax(),
|
||||
&doc.config.load().indent_heuristic,
|
||||
&doc.indent_style,
|
||||
doc.tab_width(),
|
||||
text,
|
||||
|
@ -42,7 +42,7 @@
|
||||
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 @@ fn default() -> Self {
|
||||
default_line_ending: LineEndingConfig::default(),
|
||||
insert_final_newline: true,
|
||||
smart_tab: Some(SmartTabConfig::default()),
|
||||
indent_heuristic: IndentationHeuristic::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user