crates/languages/Cargo.toml 🔗
@@ -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
Ben Kunkle created
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
crates/languages/Cargo.toml | 1
crates/languages/src/bash.rs | 100 +++++++++++++++++++++++++++++
crates/languages/src/bash/config.toml | 19 ++++
crates/languages/src/bash/indents.scm | 12 +++
4 files changed, 130 insertions(+), 2 deletions(-)
@@ -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
@@ -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
+ });
+ }
+}
@@ -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
@@ -0,0 +1,12 @@
+(function_definition
+ "function"?
+ body: (
+ _
+ "{" @start
+ "}" @end
+ )) @indent
+
+(array
+ "(" @start
+ ")" @end
+ ) @indent