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:
parent
22ad207baf
commit
7c76cee16d
5 changed files with 84 additions and 34 deletions
|
@ -2857,6 +2857,7 @@ impl BufferSnapshot {
|
||||||
) -> Option<impl Iterator<Item = Option<IndentSuggestion>> + '_> {
|
) -> Option<impl Iterator<Item = Option<IndentSuggestion>> + '_> {
|
||||||
let config = &self.language.as_ref()?.config;
|
let config = &self.language.as_ref()?.config;
|
||||||
let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
|
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.
|
// Find the suggested indentation ranges based on the syntax tree.
|
||||||
let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0);
|
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() {
|
while let Some(mat) = matches.peek() {
|
||||||
let mut start: Option<Point> = None;
|
let mut start: Option<Point> = None;
|
||||||
let mut end: Option<Point> = None;
|
let mut end: Option<Point> = None;
|
||||||
|
let mut outdent: Option<Point> = None;
|
||||||
|
|
||||||
let config = &indent_configs[mat.grammar_index];
|
let config = &indent_configs[mat.grammar_index];
|
||||||
for capture in mat.captures {
|
for capture in mat.captures {
|
||||||
|
@ -2887,16 +2889,23 @@ impl BufferSnapshot {
|
||||||
} else if Some(capture.index) == config.end_capture_ix {
|
} else if Some(capture.index) == config.end_capture_ix {
|
||||||
end = Some(Point::from_ts_point(capture.node.start_position()));
|
end = Some(Point::from_ts_point(capture.node.start_position()));
|
||||||
} else if Some(capture.index) == config.outdent_capture_ix {
|
} 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();
|
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 let Some((start, end)) = start.zip(end) {
|
||||||
if start.row == end.row {
|
if start.row == end.row && !significant_indentation {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = start..end;
|
let range = start..end;
|
||||||
match indent_ranges.binary_search_by_key(&range.start, |r| r.start) {
|
match indent_ranges.binary_search_by_key(&range.start, |r| r.start) {
|
||||||
Err(ix) => indent_ranges.insert(ix, range),
|
Err(ix) => indent_ranges.insert(ix, range),
|
||||||
|
@ -2932,16 +2941,20 @@ impl BufferSnapshot {
|
||||||
matches.advance();
|
matches.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
outdent_positions.sort();
|
// we don't use outdent positions to truncate in case of significant indentation
|
||||||
for outdent_position in outdent_positions {
|
// rather we use them to expand (handled above)
|
||||||
// find the innermost indent range containing this outdent_position
|
if !significant_indentation {
|
||||||
// set its end to the outdent position
|
outdent_positions.sort();
|
||||||
if let Some(range_to_truncate) = indent_ranges
|
for outdent_position in outdent_positions {
|
||||||
.iter_mut()
|
// find the innermost indent range containing this outdent_position
|
||||||
.filter(|indent_range| indent_range.contains(&outdent_position))
|
// set its end to the outdent position
|
||||||
.next_back()
|
if let Some(range_to_truncate) = indent_ranges
|
||||||
{
|
.iter_mut()
|
||||||
range_to_truncate.end = outdent_position;
|
.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 {
|
if range.start.row == prev_row && range.end > row_start {
|
||||||
indent_from_prev_row = true;
|
indent_from_prev_row = true;
|
||||||
}
|
}
|
||||||
if range.end > prev_row_start && range.end <= row_start {
|
if significant_indentation && self.is_line_blank(row) && range.start.row == prev_row
|
||||||
outdent_to_row = outdent_to_row.min(range.start.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -681,6 +681,10 @@ pub struct LanguageConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[schemars(schema_with = "bracket_pair_config_json_schema")]
|
#[schemars(schema_with = "bracket_pair_config_json_schema")]
|
||||||
pub brackets: BracketPairConfig,
|
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
|
/// If set to true, auto indentation uses last non empty line to determine
|
||||||
/// the indentation level for a new line.
|
/// the indentation level for a new line.
|
||||||
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
||||||
|
@ -884,6 +888,7 @@ impl Default for LanguageConfig {
|
||||||
jsx_tag_auto_close: None,
|
jsx_tag_auto_close: None,
|
||||||
completion_query_characters: Default::default(),
|
completion_query_characters: Default::default(),
|
||||||
debuggers: Default::default(),
|
debuggers: Default::default(),
|
||||||
|
significant_indentation: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1209,7 +1209,7 @@ mod tests {
|
||||||
append(&mut buffer, "foo(\n1)", cx);
|
append(&mut buffer, "foo(\n1)", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text(),
|
buffer.text(),
|
||||||
"def a():\n \n if a:\n b()\n else:\n foo(\n 1)"
|
"def a():\n \n if a:\n b()\n else:\n foo(\n 1)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// dedent the closing paren if it is shifted to the beginning of the line
|
// dedent the closing paren if it is shifted to the beginning of the line
|
||||||
|
@ -1255,7 +1255,7 @@ mod tests {
|
||||||
|
|
||||||
// dedent "else" on the line after a closing paren
|
// dedent "else" on the line after a closing paren
|
||||||
append(&mut buffer, "\n else:\n", cx);
|
append(&mut buffer, "\n else:\n", cx);
|
||||||
assert_eq!(buffer.text(), "if a:\n b(\n )\nelse:\n ");
|
assert_eq!(buffer.text(), "if a:\n b(\n )\nelse:\n");
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,7 @@ brackets = [
|
||||||
]
|
]
|
||||||
|
|
||||||
auto_indent_using_last_non_empty_line = false
|
auto_indent_using_last_non_empty_line = false
|
||||||
increase_indent_pattern = "^[^#].*:\\s*$"
|
|
||||||
decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:"
|
|
||||||
debuggers = ["Debugpy"]
|
debuggers = ["Debugpy"]
|
||||||
|
significant_indentation = true
|
||||||
|
increase_indent_pattern = "^\\s*(try)\\b.*:"
|
||||||
|
decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:"
|
||||||
|
|
|
@ -1,18 +1,43 @@
|
||||||
(_ "[" "]" @end) @indent
|
(function_definition
|
||||||
(_ "{" "}" @end) @indent
|
":" @start
|
||||||
(_ "(" ")" @end) @indent
|
body: (block) @indent
|
||||||
|
)
|
||||||
(try_statement
|
|
||||||
body: (_) @start
|
|
||||||
[(except_clause) (finally_clause)] @end
|
|
||||||
) @indent
|
|
||||||
|
|
||||||
(if_statement
|
(if_statement
|
||||||
consequence: (_) @start
|
":" @start
|
||||||
alternative: (_) @end
|
consequence: (block) @indent
|
||||||
) @indent
|
alternative: (_)? @outdent
|
||||||
|
)
|
||||||
|
|
||||||
(_
|
(else_clause
|
||||||
alternative: (elif_clause) @start
|
":" @start
|
||||||
alternative: (_) @end
|
body: (block) @indent
|
||||||
) @indent
|
)
|
||||||
|
|
||||||
|
(elif_clause
|
||||||
|
":" @start
|
||||||
|
consequence: (block) @indent
|
||||||
|
)
|
||||||
|
|
||||||
|
(for_statement
|
||||||
|
":" @start
|
||||||
|
body: (block) @indent
|
||||||
|
)
|
||||||
|
|
||||||
|
(try_statement
|
||||||
|
":" @start
|
||||||
|
body: (block) @indent
|
||||||
|
(except_clause)? @outdent
|
||||||
|
(else_clause)? @outdent
|
||||||
|
(finally_clause)? @outdent
|
||||||
|
)
|
||||||
|
|
||||||
|
(except_clause
|
||||||
|
":" @start
|
||||||
|
(block) @indent
|
||||||
|
)
|
||||||
|
|
||||||
|
(finally_clause
|
||||||
|
":" @start
|
||||||
|
(block) @indent
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue