language: Fix auto-indentation for Python code blocks in Markdown (#43853)

Closes #43722

Release Notes:

- Fixed an issue where auto-indentation didn’t work correctly for Python
code blocks in Markdown.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Jeff Brennan
2025-12-17 05:10:39 -05:00
committed by GitHub
parent 637ff34254
commit a7bab0b050
2 changed files with 67 additions and 10 deletions

View File

@@ -25972,6 +25972,48 @@ async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
"});
}
#[gpui::test]
async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
language_registry.add(markdown_lang());
language_registry.add(python_lang);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| {
buffer.set_language_registry(language_registry);
buffer.set_language(Some(markdown_lang()), cx);
});
// Test that `else:` correctly outdents to match `if:` inside the Python code block
cx.set_state(indoc! {"
# Heading
```python
def main():
if condition:
pass
ˇ
```
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("else:", window, cx);
});
cx.run_until_parked();
cx.assert_editor_state(indoc! {"
# Heading
```python
def main():
if condition:
pass
else:ˇ
```
"});
}
#[gpui::test]
async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
init_test(cx, |_| {});

View File

@@ -3216,6 +3216,7 @@ impl BufferSnapshot {
struct StartPosition {
start: Point,
suffix: SharedString,
language: Arc<Language>,
}
// Find the suggested indentation ranges based on the syntax tree.
@@ -3259,6 +3260,7 @@ impl BufferSnapshot {
start_positions.push(StartPosition {
start: Point::from_ts_point(capture.node.start_position()),
suffix: suffix.clone(),
language: mat.language.clone(),
});
}
}
@@ -3319,7 +3321,7 @@ impl BufferSnapshot {
// Find the suggested indentation increases and decreased based on regexes.
let mut regex_outdent_map = HashMap::default();
let mut last_seen_suffix: HashMap<String, Vec<Point>> = HashMap::default();
let mut last_seen_suffix: HashMap<String, Vec<StartPosition>> = HashMap::default();
let mut start_positions_iter = start_positions.iter().peekable();
let mut indent_change_rows = Vec::<(u32, Ordering)>::new();
@@ -3327,14 +3329,21 @@ impl BufferSnapshot {
Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0)
..Point::new(row_range.end, 0),
|row, line| {
if config
let indent_len = self.indent_size_for_line(row).len;
let row_language = self.language_at(Point::new(row, indent_len)).cloned();
let row_language_config = row_language
.as_ref()
.map(|lang| lang.config())
.unwrap_or(config);
if row_language_config
.decrease_indent_pattern
.as_ref()
.is_some_and(|regex| regex.is_match(line))
{
indent_change_rows.push((row, Ordering::Less));
}
if config
if row_language_config
.increase_indent_pattern
.as_ref()
.is_some_and(|regex| regex.is_match(line))
@@ -3343,16 +3352,16 @@ impl BufferSnapshot {
}
while let Some(pos) = start_positions_iter.peek() {
if pos.start.row < row {
let pos = start_positions_iter.next().unwrap();
let pos = start_positions_iter.next().unwrap().clone();
last_seen_suffix
.entry(pos.suffix.to_string())
.or_default()
.push(pos.start);
.push(pos);
} else {
break;
}
}
for rule in &config.decrease_indent_patterns {
for rule in &row_language_config.decrease_indent_patterns {
if rule.pattern.as_ref().is_some_and(|r| r.is_match(line)) {
let row_start_column = self.indent_size_for_line(row).len;
let basis_row = rule
@@ -3360,10 +3369,16 @@ impl BufferSnapshot {
.iter()
.filter_map(|valid_suffix| last_seen_suffix.get(valid_suffix))
.flatten()
.filter(|start_point| start_point.column <= row_start_column)
.max_by_key(|start_point| start_point.row);
if let Some(outdent_to_row) = basis_row {
regex_outdent_map.insert(row, outdent_to_row.row);
.filter(|pos| {
row_language
.as_ref()
.or(self.language.as_ref())
.is_some_and(|lang| Arc::ptr_eq(lang, &pos.language))
})
.filter(|pos| pos.start.column <= row_start_column)
.max_by_key(|pos| pos.start.row);
if let Some(outdent_to) = basis_row {
regex_outdent_map.insert(row, outdent_to.start.row);
}
break;
}