language: Fix indent suggestions for significant indented languages like Python (#29625)

Closes #26157

This fixes multiple cases where Python indentation breaks:
- [x] Adding a new line after `if`, `try`, etc. correctly indents in
that scope
- [x] Multi-cursor tabs correctly preserve relative indents
- [x] Adding a new line after `else`, `finally`, etc. correctly outdents
them
- [x] Existing Tests

Future Todo: I need to add new tests for all the above cases.

Before/After:

1. Multi-cursor tabs correctly preserve relative indents


https://github.com/user-attachments/assets/08a46ddf-5371-4e26-ae7d-f8aa0b31c4a2

2. Adding a new line after `if`, `try`, etc. correctly indents in that
scope


https://github.com/user-attachments/assets/9affae97-1a50-43c9-9e9f-c1ea3a747813

Release Notes:

- Fixes indentation-related issues involving tab, newline, etc for
Python.
This commit is contained in:
Smit Barmase 2025-05-07 23:05:42 +05:30 committed by GitHub
parent 22ad207baf
commit 7c76cee16d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 34 deletions

View file

@ -2857,6 +2857,7 @@ impl BufferSnapshot {
) -> Option<impl Iterator<Item = Option<IndentSuggestion>> + '_> {
let config = &self.language.as_ref()?.config;
let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
let significant_indentation = config.significant_indentation;
// Find the suggested indentation ranges based on the syntax tree.
let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0);
@ -2876,6 +2877,7 @@ impl BufferSnapshot {
while let Some(mat) = matches.peek() {
let mut start: Option<Point> = None;
let mut end: Option<Point> = None;
let mut outdent: Option<Point> = None;
let config = &indent_configs[mat.grammar_index];
for capture in mat.captures {
@ -2887,16 +2889,23 @@ impl BufferSnapshot {
} else if Some(capture.index) == config.end_capture_ix {
end = Some(Point::from_ts_point(capture.node.start_position()));
} else if Some(capture.index) == config.outdent_capture_ix {
outdent_positions.push(Point::from_ts_point(capture.node.start_position()));
let point = Point::from_ts_point(capture.node.start_position());
outdent.get_or_insert(point);
outdent_positions.push(point);
}
}
matches.advance();
// in case of significant indentation expand end to outdent position
let end = if significant_indentation {
outdent.or(end)
} else {
end
};
if let Some((start, end)) = start.zip(end) {
if start.row == end.row {
if start.row == end.row && !significant_indentation {
continue;
}
let range = start..end;
match indent_ranges.binary_search_by_key(&range.start, |r| r.start) {
Err(ix) => indent_ranges.insert(ix, range),
@ -2932,16 +2941,20 @@ impl BufferSnapshot {
matches.advance();
}
outdent_positions.sort();
for outdent_position in outdent_positions {
// find the innermost indent range containing this outdent_position
// set its end to the outdent position
if let Some(range_to_truncate) = indent_ranges
.iter_mut()
.filter(|indent_range| indent_range.contains(&outdent_position))
.next_back()
{
range_to_truncate.end = outdent_position;
// we don't use outdent positions to truncate in case of significant indentation
// rather we use them to expand (handled above)
if !significant_indentation {
outdent_positions.sort();
for outdent_position in outdent_positions {
// find the innermost indent range containing this outdent_position
// set its end to the outdent position
if let Some(range_to_truncate) = indent_ranges
.iter_mut()
.filter(|indent_range| indent_range.contains(&outdent_position))
.next_back()
{
range_to_truncate.end = outdent_position;
}
}
}
@ -3011,8 +3024,14 @@ impl BufferSnapshot {
if range.start.row == prev_row && range.end > row_start {
indent_from_prev_row = true;
}
if range.end > prev_row_start && range.end <= row_start {
outdent_to_row = outdent_to_row.min(range.start.row);
if significant_indentation && self.is_line_blank(row) && range.start.row == prev_row
{
indent_from_prev_row = true;
}
if !significant_indentation || !self.is_line_blank(row) {
if range.end > prev_row_start && range.end <= row_start {
outdent_to_row = outdent_to_row.min(range.start.row);
}
}
}

View file

@ -681,6 +681,10 @@ pub struct LanguageConfig {
#[serde(default)]
#[schemars(schema_with = "bracket_pair_config_json_schema")]
pub brackets: BracketPairConfig,
/// If set to true, indicates the language uses significant whitespace/indentation
/// for syntax structure (like Python) rather than brackets/braces for code blocks.
#[serde(default)]
pub significant_indentation: bool,
/// If set to true, auto indentation uses last non empty line to determine
/// the indentation level for a new line.
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
@ -884,6 +888,7 @@ impl Default for LanguageConfig {
jsx_tag_auto_close: None,
completion_query_characters: Default::default(),
debuggers: Default::default(),
significant_indentation: Default::default(),
}
}
}