Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider actual indentation of previous line for smart indent #8307

Merged
merged 7 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`

### `[editor.statusline]` Section

Expand Down
9 changes: 9 additions & 0 deletions book/src/guides/indent.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ Note that it matters where these added indents begin. For example,
multiple indent level increases that start on the same line only increase
the total indent level by 1. See [Capture types](#capture-types).

By default, Helix uses the `hybrid` indentation heuristic. This means that
indent queries are not used to compute the expected absolute indentation of a
line but rather the expected difference in indentation between the new and an
already existing line. This difference is then added to the actual indentation
of the already existing line. Since this makes errors in the indent queries
harder to find, it is recommended to disable it when testing via
`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
the `tree-sitter` heuristic is used.

## Indent queries

When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
Expand Down
409 changes: 309 additions & 100 deletions helix-core/src/indent.rs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions helix-core/tests/indent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ fn test_treesitter_indent(
let suggested_indent = treesitter_indent_for_pos(
indent_query,
&syntax,
&indent_style,
tab_width,
indent_style.indent_width(tab_width),
text,
i,
text.line_to_char(i) + pos,
false,
)
.unwrap();
.unwrap()
.to_string(&indent_style, tab_width);
assert!(
line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
"Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
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 @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -3720,6 +3722,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 @@ -841,6 +844,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
13 changes: 13 additions & 0 deletions runtime/queries/c/indents.scm
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@
(parameter_list
. (parameter_declaration) @anchor
(#set! "scope" "tail")) @align
(argument_list
. (_) @anchor
(#set! "scope" "tail")) @align
; These are a bit opinionated since some people just indent binary/ternary expressions spanning multiple lines.
; Since they are only triggered when a newline is inserted into an already complete binary/ternary expression,
; this should happen rarely, so it is not a big deal either way.
; Additionally, adding these queries has the advantage of preventing such continuation lines from being used
; as the baseline when the `hybrid` indent heuristic is used (which is desirable since their indentation is so inconsistent).
(binary_expression
(#set! "scope" "tail")) @anchor @align
(conditional_expression
"?" @anchor
(#set! "scope" "tail")) @align
Loading