From b6e680ea3d04fd9e7e2d74a5bb40205bf70b6368 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Mon, 3 Feb 2025 18:37:52 -0600 Subject: [PATCH] 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 --- 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(-) create mode 100644 crates/languages/src/bash/indents.scm diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 388a61e5145d552908ddf7e39fe59e17dfdd4b2a..23f59beb171d57e3c40bfd8eff25002009688737 100644 --- a/crates/languages/Cargo.toml +++ b/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 diff --git a/crates/languages/src/bash.rs b/crates/languages/src/bash.rs index bc4b7be4671746565eb7c7482a4bb32edd90ae8a..ef9a18a9c529f529d741ba8d69e22c269ce66e78 100644 --- a/crates/languages/src/bash.rs +++ b/crates/languages/src/bash.rs @@ -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::(|store, cx| { + store.update_user_settings::(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, 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 + }); + } +} diff --git a/crates/languages/src/bash/config.toml b/crates/languages/src/bash/config.toml index 08bb8ee0957ac3f30fc2cbf81b51e04a43b18456..91d01d6270cb5b7e474546ab4fdb87604f1d899e 100644 --- a/crates/languages/src/bash/config.toml +++ b/crates/languages/src/bash/config.toml @@ -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 diff --git a/crates/languages/src/bash/indents.scm b/crates/languages/src/bash/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..acdcddabfe20d4c1efdeacc23c8d097e3ca0b094 --- /dev/null +++ b/crates/languages/src/bash/indents.scm @@ -0,0 +1,12 @@ +(function_definition + "function"? + body: ( + _ + "{" @start + "}" @end + )) @indent + +(array + "(" @start + ")" @end + ) @indent