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:
parent
230eb12f72
commit
0079c99c2c
4 changed files with 330 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4702,6 +4702,7 @@ dependencies = [
|
|||
"theme",
|
||||
"time",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
|
||||
(function_definition
|
||||
":" @start
|
||||
body: (block) @indent
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue