language: Add context-aware decrease indent for Python (#33370)

Closes #33238, follow-up to
https://github.com/zed-industries/zed/pull/29625.

Changes:

- Removed `significant_indentation`, which was the way to introduce
indentation scoping in languages like Python. However, it turned out to
be unnecessarily complicated to define and maintain.
- Introduced `decrease_indent_patterns`, which takes a `pattern` keyword
to automatically outdent and `valid_after` keywords to treat as valid
code points to snap to. The outdent happens to the most recent
`valid_after` keyword that also has less or equal indentation than the
currently typed keyword.

Fixes:

1. In Python, typing `except`, `finally`, `else`, and so on now
automatically indents intelligently based on the context in which it
appears. For instance:

```py
try:
    if a == 1:
        try:
             b = 2
             ^  # <-- typing "except:" here would indent it to inner try block
```

but,

```py
try:
    if a == 1:
        try:
             b = 2
    ^  # <-- typing "except:" here would indent it to outer try block
```

2. Fixes comments not maintaining indent.

Release Notes:

- Improved auto outdent for Python while typing keywords like `except`,
`else`, `finally`, etc.
- Fixed the issue where comments in Python would not maintain their
indentation.
This commit is contained in:
Smit Barmase 2025-06-26 11:11:03 +05:30 committed by GitHub
parent 1753432406
commit d09c7eb317
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 211 additions and 181 deletions

View file

@ -21771,9 +21771,9 @@ async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestApp
cx.set_state(indoc! {"
def main():
ˇ try:
ˇ fetch()
ˇ fetch()
ˇ except ValueError:
ˇ handle_error()
ˇ handle_error()
ˇ else:
ˇ match value:
ˇ case _:
@ -21901,74 +21901,101 @@ async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
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:ˇ
// "});
// test `else` does not 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 `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:ˇ
// "});
// 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:ˇ
"});
// 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 `except` outdents to inner "try" block
cx.set_state(indoc! {"
def main():
try:
i = 2
if i == 2:
try:
i = 3
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("except:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
try:
i = 2
if i == 2:
try:
i = 3
except:ˇ
"});
// test `except` outdents to outer "try" block
cx.set_state(indoc! {"
def main():
try:
i = 2
if i == 2:
try:
i = 3
ˇ
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("except:", window, cx);
});
cx.assert_editor_state(indoc! {"
def main():
try:
i = 2
if i == 2:
try:
i = 3
except:ˇ
"});
// test `else` stays at correct indent when typed after `for` block
cx.set_state(indoc! {"