editor: Add python indentation tests (#30902)

This PR add tests for a recent PR: [language: Fix indent suggestions for
significant indented languages like
Python](https://github.com/zed-industries/zed/pull/29625)

It also covers cases from past related issues so that we don't end up
circling back to them on future fixes.

- [Python incorrect auto-indentation for
except:](https://github.com/zed-industries/zed/issues/10832)
- [Python for/while...else indention overridden by if statement
](https://github.com/zed-industries/zed/issues/30795)
- [Python: erroneous indent on newline when comment ends in
:](https://github.com/zed-industries/zed/issues/25416)
- [Newline in Python file does not indent
](https://github.com/zed-industries/zed/issues/16288)
- [Tab Indentation works incorrectly when there are multiple
cursors](https://github.com/zed-industries/zed/issues/26157)

Release Notes:

- N/A
This commit is contained in:
Smit Barmase 2025-05-18 07:29:25 +05:30 committed by GitHub
parent 230eb12f72
commit 0079c99c2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 330 additions and 0 deletions

1
Cargo.lock generated
View file

@ -4702,6 +4702,7 @@ dependencies = [
"theme",
"time",
"tree-sitter-html",
"tree-sitter-python",
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",

View file

@ -79,6 +79,7 @@ theme.workspace = true
tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
tree-sitter-python = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-script.workspace = true
unindent = { workspace = true, optional = true }

View file

@ -26,6 +26,7 @@ use language::{
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
LanguageSettingsContent, LspInsertMode, PrettierSettings,
},
tree_sitter_python,
};
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use lsp::CompletionParams;
@ -20210,6 +20211,330 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test cursor move to start of each line on tab
// for `if`, `elif`, `else`, `while`, `with` and `for`
cx.set_state(indoc! {"
def main():
ˇ for item in items:
ˇ while item.active:
ˇ if item.value > 10:
ˇ continue
ˇ elif item.value < 0:
ˇ break
ˇ else:
ˇ with item.context() as ctx:
ˇ yield count
ˇ else:
ˇ log('while else')
ˇ else:
ˇ log('for else')
"});
cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
cx.assert_editor_state(indoc! {"
def main():
ˇfor item in items:
ˇwhile item.active:
ˇif item.value > 10:
ˇcontinue
ˇelif item.value < 0:
ˇbreak
ˇelse:
ˇwith item.context() as ctx:
ˇyield count
ˇelse:
ˇlog('while else')
ˇelse:
ˇlog('for else')
"});
// test relative indent is preserved when tab
// for `if`, `elif`, `else`, `while`, `with` and `for`
cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
cx.assert_editor_state(indoc! {"
def main():
ˇfor item in items:
ˇwhile item.active:
ˇif item.value > 10:
ˇcontinue
ˇelif item.value < 0:
ˇbreak
ˇelse:
ˇwith item.context() as ctx:
ˇyield count
ˇelse:
ˇlog('while else')
ˇelse:
ˇlog('for else')
"});
// test cursor move to start of each line on tab
// for `try`, `except`, `else`, `finally`, `match` and `def`
cx.set_state(indoc! {"
def main():
ˇ try:
ˇ fetch()
ˇ except ValueError:
ˇ handle_error()
ˇ else:
ˇ match value:
ˇ case _:
ˇ finally:
ˇ def status():
ˇ return 0
"});
cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
cx.assert_editor_state(indoc! {"
def main():
ˇtry:
ˇfetch()
ˇexcept ValueError:
ˇhandle_error()
ˇelse:
ˇmatch value:
ˇcase _:
ˇfinally:
ˇdef status():
ˇreturn 0
"});
// test relative indent is preserved when tab
// for `try`, `except`, `else`, `finally`, `match` and `def`
cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
cx.assert_editor_state(indoc! {"
def main():
ˇtry:
ˇfetch()
ˇexcept ValueError:
ˇhandle_error()
ˇelse:
ˇmatch value:
ˇcase _:
ˇfinally:
ˇdef status():
ˇreturn 0
"});
}
#[gpui::test]
async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test `else` auto outdents when typed inside `if` block
cx.set_state(indoc! {"
def main():
if i == 2:
return
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("else:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
if i == 2:
return
else:ˇ
"});
// test `except` auto outdents when typed inside `try` block
cx.set_state(indoc! {"
def main():
try:
i = 2
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("except:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
try:
i = 2
except:ˇ
"});
// test `else` auto outdents when typed inside `except` block
cx.set_state(indoc! {"
def main():
try:
i = 2
except:
j = 2
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("else:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
try:
i = 2
except:
j = 2
else:ˇ
"});
// test `finally` auto outdents when typed inside `else` block
cx.set_state(indoc! {"
def main():
try:
i = 2
except:
j = 2
else:
k = 2
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("finally:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
try:
i = 2
except:
j = 2
else:
k = 2
finally:ˇ
"});
// TODO: test `except` auto outdents when typed inside `try` block right after for block
// cx.set_state(indoc! {"
// def main():
// try:
// for i in range(n):
// pass
// ˇ
// "});
// cx.update_editor(|editor, window, cx| {
// editor.handle_input("except:", window, cx);
// });
// cx.assert_editor_state(indoc! {"
// def main():
// try:
// for i in range(n):
// pass
// except:ˇ
// "});
// TODO: test `else` auto outdents when typed inside `except` block right after for block
// cx.set_state(indoc! {"
// def main():
// try:
// i = 2
// except:
// for i in range(n):
// pass
// ˇ
// "});
// cx.update_editor(|editor, window, cx| {
// editor.handle_input("else:", window, cx);
// });
// cx.assert_editor_state(indoc! {"
// def main():
// try:
// i = 2
// except:
// for i in range(n):
// pass
// else:ˇ
// "});
// TODO: test `finally` auto outdents when typed inside `else` block right after for block
// cx.set_state(indoc! {"
// def main():
// try:
// i = 2
// except:
// j = 2
// else:
// for i in range(n):
// pass
// ˇ
// "});
// cx.update_editor(|editor, window, cx| {
// editor.handle_input("finally:", window, cx);
// });
// cx.assert_editor_state(indoc! {"
// def main():
// try:
// i = 2
// except:
// j = 2
// else:
// for i in range(n):
// pass
// finally:ˇ
// "});
// test `else` stays at correct indent when typed after `for` block
cx.set_state(indoc! {"
def main():
for i in range(10):
if i == 3:
break
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("else:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
for i in range(10):
if i == 3:
break
else:ˇ
"});
}
#[gpui::test]
async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
init_test(cx, |_| {});
update_test_language_settings(cx, |settings| {
settings.defaults.extend_comment_on_newline = Some(false);
});
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test correct indent after newline on comment
cx.set_state(indoc! {"
# COMMENT:ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.newline(&Newline, window, cx);
});
cx.assert_editor_state(indoc! {"
# COMMENT:
ˇ
"});
// test correct indent after newline in curly brackets
cx.set_state(indoc! {"
{ˇ}
"});
cx.update_editor(|editor, window, cx| {
editor.newline(&Newline, window, cx);
});
cx.run_until_parked();
cx.assert_editor_state(indoc! {"
{
ˇ
}
"});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point

View file

@ -1,3 +1,6 @@
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(function_definition
":" @start
body: (block) @indent