Support bash autoindenting (#24156)

Creates an indents.scm file for bash and adds regexes for
`{increase,decrease}_indent_pattern` in
`crates/languages/src/bash/config.toml`
so that autoindent works as expected in bash

Note that this PR does not attempt to handle all cases where indenting
might be desired in bash. I am aiming to support ~80% of what people
want while avoiding the more gnarly/edge cases like indented blocks in
case statements and indenting for associative arrays.
This is done with the explicit hope that someone (possibly from the
community) more familiar with and passionate about bash can come through
at a later date and handle those cases

Closes #23628

Release Notes:

- Add basic support for autoindent functionality in bash/shell files
This commit is contained in:
Ben Kunkle 2025-02-03 18:37:52 -06:00 committed by GitHub
parent dfd11c3d3b
commit b6e680ea3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 130 additions and 2 deletions

View file

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

View file

@ -15,3 +15,103 @@ pub(super) fn bash_task_context() -> ContextProviderWithTasks {
},
]))
}
#[cfg(test)]
mod tests {
use gpui::{AppContext as _, BorrowAppContext, Context, TestAppContext};
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings::SettingsStore;
use std::num::NonZeroU32;
#[gpui::test]
async fn test_bash_autoindent(cx: &mut TestAppContext) {
cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
let language = crate::language("bash", tree_sitter_bash::LANGUAGE.into());
cx.update(|cx| {
let test_settings = SettingsStore::test(cx);
cx.set_global(test_settings);
language::init(cx);
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(2)
});
});
});
cx.new(|cx| {
let mut buffer = Buffer::local("", cx).with_language(language, cx);
let expect_indents_to =
|buffer: &mut Buffer, cx: &mut Context<Buffer>, input: &str, expected: &str| {
buffer.edit( [(0..buffer.len(), input)], Some(AutoindentMode::EachLine), cx, );
assert_eq!(buffer.text(), expected);
};
// indent function correctly
expect_indents_to(
&mut buffer,
cx,
"function name() {\necho \"Hello, World!\"\n}",
"function name() {\n echo \"Hello, World!\"\n}",
);
// indent if-else correctly
expect_indents_to(
&mut buffer,
cx,
"if true;then\nfoo\nelse\nbar\nfi",
"if true;then\n foo\nelse\n bar\nfi",
);
// indent if-elif-else correctly
expect_indents_to(
&mut buffer,
cx,
"if true;then\nfoo\nelif true;then\nbar\nelse\nbar\nfi",
"if true;then\n foo\nelif true;then\n bar\nelse\n bar\nfi",
);
// indent case-when-else correctly
expect_indents_to(
&mut buffer,
cx,
"case $1 in\nfoo) echo \"Hello, World!\";;\n*) echo \"Unknown argument\";;\nesac",
"case $1 in\n foo) echo \"Hello, World!\";;\n *) echo \"Unknown argument\";;\nesac",
);
// indent for-loop correctly
expect_indents_to(
&mut buffer,
cx,
"for i in {1..10};do\nfoo\ndone",
"for i in {1..10};do\n foo\ndone",
);
// indent while-loop correctly
expect_indents_to(
&mut buffer,
cx,
"while true; do\nfoo\ndone",
"while true; do\n foo\ndone",
);
// indent array correctly
expect_indents_to(
&mut buffer,
cx,
"array=(\n1\n2\n3\n)",
"array=(\n 1\n 2\n 3\n)",
);
// indents non-"function" function correctly
expect_indents_to(
&mut buffer,
cx,
"foo() {\necho \"Hello, World!\"\n}",
"foo() {\n echo \"Hello, World!\"\n}",
);
buffer
});
}
}

View file

@ -6,8 +6,23 @@ line_comments = ["# "]
first_line_pattern = '^#!.*\b(?:ash|bash|dash|sh|zsh)\b'
brackets = [
{ start = "[", end = "]", close = true, newline = false },
{ start = "(", end = ")", close = true, newline = false },
{ start = "{", end = "}", close = true, newline = false },
{ start = "(", end = ")", close = true, newline = true },
{ start = "{", end = "}", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
### WARN: the following is not working when you insert an `elif` just before an else
### example: (^ is cursor after hitting enter)
### ```
### if true; then
### foo
### elif
### ^
### else
### bar
### fi
### ```
increase_indent_pattern = "(\\s*|;)(do|then|in|else|elif)\\b.*$"
decrease_indent_pattern = "(\\s*|;)(fi|done|esac|else|elif)\\b.*$"
# make sure to test each line mode & block mode

View file

@ -0,0 +1,12 @@
(function_definition
"function"?
body: (
_
"{" @start
"}" @end
)) @indent
(array
"(" @start
")" @end
) @indent