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",
|
"theme",
|
||||||
"time",
|
"time",
|
||||||
"tree-sitter-html",
|
"tree-sitter-html",
|
||||||
|
"tree-sitter-python",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
"ui",
|
"ui",
|
||||||
|
|
|
@ -79,6 +79,7 @@ theme.workspace = true
|
||||||
tree-sitter-html = { workspace = true, optional = true }
|
tree-sitter-html = { workspace = true, optional = true }
|
||||||
tree-sitter-rust = { workspace = true, optional = true }
|
tree-sitter-rust = { workspace = true, optional = true }
|
||||||
tree-sitter-typescript = { workspace = true, optional = true }
|
tree-sitter-typescript = { workspace = true, optional = true }
|
||||||
|
tree-sitter-python = { workspace = true, optional = true }
|
||||||
unicode-segmentation.workspace = true
|
unicode-segmentation.workspace = true
|
||||||
unicode-script.workspace = true
|
unicode-script.workspace = true
|
||||||
unindent = { workspace = true, optional = true }
|
unindent = { workspace = true, optional = true }
|
||||||
|
|
|
@ -26,6 +26,7 @@ use language::{
|
||||||
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
|
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
|
||||||
LanguageSettingsContent, LspInsertMode, PrettierSettings,
|
LanguageSettingsContent, LspInsertMode, PrettierSettings,
|
||||||
},
|
},
|
||||||
|
tree_sitter_python,
|
||||||
};
|
};
|
||||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||||
use lsp::CompletionParams;
|
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> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
(_ "[" "]" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
|
||||||
(function_definition
|
(function_definition
|
||||||
":" @start
|
":" @start
|
||||||
body: (block) @indent
|
body: (block) @indent
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue