Allow auto-indenting with syntax errors when using regex-based indent matches to improve bash auto-indent behavior (#24160)

Ben Kunkle and Conrad Irwin created

- Fixes auto-indent issues around `elif` caused by auto-indent being prevented due to syntax errors generated before `elif` clause completed

Release Notes:

- Fixed an issue where inserting an elif before an else in bash would
not properly auto-indent

---------

Co-authored-by: Conrad Irwin <conrad@zed.dev>

Change summary

Cargo.lock                            |  1 
crates/language/src/buffer.rs         | 21 +++++++++++-----
crates/languages/Cargo.toml           |  1 
crates/languages/src/bash.rs          | 36 +++++++++++++++++++++++++++++
crates/languages/src/bash/config.toml |  2 
5 files changed, 53 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7050,6 +7050,7 @@ dependencies = [
  "pet-fs",
  "pet-poetry",
  "pet-reporter",
+ "pretty_assertions",
  "project",
  "regex",
  "rope",

crates/language/src/buffer.rs 🔗

@@ -2897,12 +2897,19 @@ impl BufferSnapshot {
             let mut indent_from_prev_row = false;
             let mut outdent_from_prev_row = false;
             let mut outdent_to_row = u32::MAX;
+            let mut from_regex = false;
 
             while let Some((indent_row, delta)) = indent_changes.peek() {
                 match indent_row.cmp(&row) {
                     Ordering::Equal => match delta {
-                        Ordering::Less => outdent_from_prev_row = true,
-                        Ordering::Greater => indent_from_prev_row = true,
+                        Ordering::Less => {
+                            from_regex = true;
+                            outdent_from_prev_row = true
+                        }
+                        Ordering::Greater => {
+                            indent_from_prev_row = true;
+                            from_regex = true
+                        }
                         _ => {}
                     },
 
@@ -2935,32 +2942,32 @@ impl BufferSnapshot {
                 Some(IndentSuggestion {
                     basis_row: prev_row,
                     delta: Ordering::Equal,
-                    within_error,
+                    within_error: within_error && !from_regex,
                 })
             } else if indent_from_prev_row {
                 Some(IndentSuggestion {
                     basis_row: prev_row,
                     delta: Ordering::Greater,
-                    within_error,
+                    within_error: within_error && !from_regex,
                 })
             } else if outdent_to_row < prev_row {
                 Some(IndentSuggestion {
                     basis_row: outdent_to_row,
                     delta: Ordering::Equal,
-                    within_error,
+                    within_error: within_error && !from_regex,
                 })
             } else if outdent_from_prev_row {
                 Some(IndentSuggestion {
                     basis_row: prev_row,
                     delta: Ordering::Less,
-                    within_error,
+                    within_error: within_error && !from_regex,
                 })
             } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row)
             {
                 Some(IndentSuggestion {
                     basis_row: prev_row,
                     delta: Ordering::Equal,
-                    within_error,
+                    within_error: within_error && !from_regex,
                 })
             } else {
                 None

crates/languages/Cargo.toml 🔗

@@ -94,3 +94,4 @@ tree-sitter-go.workspace = true
 tree-sitter-c.workspace = true
 tree-sitter-css.workspace = true
 tree-sitter-bash.workspace = true
+pretty_assertions.workspace = true

crates/languages/src/bash.rs 🔗

@@ -22,6 +22,8 @@ mod tests {
     use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
     use settings::SettingsStore;
     use std::num::NonZeroU32;
+    use unindent::Unindent;
+    use util::test::marked_text_offsets;
 
     #[gpui::test]
     async fn test_bash_autoindent(cx: &mut TestAppContext) {
@@ -111,6 +113,40 @@ mod tests {
                 "foo() {\n  echo \"Hello, World!\"\n}",
             );
 
+            let (input, offsets) = marked_text_offsets(
+                &r#"
+                if foo; then
+                  1ˇ
+                else
+                  3
+                fi
+                "#
+                .unindent(),
+            );
+
+            buffer.edit([(0..buffer.len(), input)], None, cx);
+            buffer.edit(
+                [(offsets[0]..offsets[0], "\n")],
+                Some(AutoindentMode::EachLine),
+                cx,
+            );
+            buffer.edit(
+                [(offsets[0] + 3..offsets[0] + 3, "elif")],
+                Some(AutoindentMode::EachLine),
+                cx,
+            );
+            let expected = r#"
+                if foo; then
+                  1
+                elif
+                else
+                  3
+                fi
+                "#
+            .unindent();
+
+            pretty_assertions::assert_eq!(buffer.text(), expected);
+
             buffer
         });
     }

crates/languages/src/bash/config.toml 🔗

@@ -24,5 +24,5 @@ brackets = [
 ### fi
 ### ```
 increase_indent_pattern = "(\\s*|;)(do|then|in|else|elif)\\b.*$"
-decrease_indent_pattern = "(\\s*|;)(fi|done|esac|else|elif)\\b.*$"
+decrease_indent_pattern = "(\\s*|;)\\b(fi|done|esac|else|elif)\\b.*$"
 # make sure to test each line mode & block mode