Cargo.lock 🔗
@@ -4702,6 +4702,7 @@ dependencies = [
"theme",
"time",
"tree-sitter-html",
+ "tree-sitter-python",
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",
Smit Barmase created
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
Cargo.lock | 1
crates/editor/Cargo.toml | 1
crates/editor/src/editor_tests.rs | 325 +++++++++++++++++++++++++++
crates/languages/src/python/indents.scm | 3
4 files changed, 330 insertions(+)
@@ -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