From aeeacf5439b2d30d01e38d65d767e6f31b255ecc Mon Sep 17 00:00:00 2001 From: Apoorva Verma <76636952+apoorva-01@users.noreply.github.com> Date: Mon, 13 Jul 2026 04:32:26 +0530 Subject: [PATCH] Indent statements under C/C++ case labels (#60758) # Objective Fixes #60261 Nothing under a `case` gets indented in C or C++, and the next `case` doesn't come back out. It stays flat until you hit `editor: format`. ## Solution The indent queries had no rule for case labels. Added the same pair `cpp/indents.scm` uses for `access_specifier`: open an indent range at the label, close it at the switch's `}`, outdent on the next label. The part that got me: `@start` uses the *end* of the node you capture. Fine for `access_specifier`, which is just `public:`. But a `case_statement` swallows the statements under it, so capturing the node starts the range after the last one. Capture the `:` instead. `default:` is a `case_statement` too, so it comes along free. Only affects what happens after a label, so existing `case` alignment doesn't move. ## Testing `test_c_autoindent_switch_case` and `test_cpp_autoindent_switch_case` cover a plain case, fall-through labels and `default`. Both fail on main. The `access_specifier` tests still pass. Not mine, but worth knowing: `typescript::tests::test_outline` and `test_generator_function_outline` are flaky on main, failing about half my full `cargo test -p languages --lib` runs with or without this change. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed statements under a `case` label not being indented in C and C++ ([#60261](https://github.com/zed-industries/zed/issues/60261)). --- crates/grammars/src/c/indents.scm | 9 ++++ crates/grammars/src/cpp/indents.scm | 9 ++++ crates/languages/src/c.rs | 64 +++++++++++++++++++++++++++++ crates/languages/src/cpp.rs | 64 +++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) diff --git a/crates/grammars/src/c/indents.scm b/crates/grammars/src/c/indents.scm index 41f3c4fd3d..d119e5eedc 100644 --- a/crates/grammars/src/c/indents.scm +++ b/crates/grammars/src/c/indents.scm @@ -17,6 +17,15 @@ "{" "}" @end) @indent +(compound_statement + (case_statement + ":" @start) + "}" @end) @indent + +(compound_statement + (case_statement) + (case_statement) @outdent) + (_ "(" ")" @end) @indent diff --git a/crates/grammars/src/cpp/indents.scm b/crates/grammars/src/cpp/indents.scm index d8c71736e9..d8b677348b 100644 --- a/crates/grammars/src/cpp/indents.scm +++ b/crates/grammars/src/cpp/indents.scm @@ -25,6 +25,15 @@ (access_specifier) (access_specifier) @outdent) +(compound_statement + (case_statement + ":" @start) + "}" @end) @indent + +(compound_statement + (case_statement) + (case_statement) @outdent) + (_ "(" ")" @end) @indent diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 80d794cd61..eb24dea0de 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -451,6 +451,70 @@ mod tests { }); } + #[gpui::test] + async fn test_c_autoindent_switch_case(cx: &mut TestAppContext) { + cx.update(|cx| { + let test_settings = SettingsStore::test(cx); + cx.set_global(test_settings); + cx.update_global::(|store, cx| { + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2); + }); + }); + }); + let language = crate::language("c", tree_sitter_c::LANGUAGE.into()); + + cx.new(|cx| { + let mut buffer = Buffer::local("", cx).with_language(language, cx); + + buffer.edit( + [( + 0..0, + r#" + int main() { + switch (a) { + case 1: + b++; + break; + case 2: + case 3: + c++; + break; + default: + d++; + } + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + switch (a) { + case 1: + b++; + break; + case 2: + case 3: + c++; + break; + default: + d++; + } + } + "# + .unindent(), + "statements under a case label should be indented, and the next label outdented" + ); + + buffer + }); + } + #[gpui::test] async fn test_c_autoindent_if_else(cx: &mut TestAppContext) { cx.update(|cx| { diff --git a/crates/languages/src/cpp.rs b/crates/languages/src/cpp.rs index 0577a55826..f09438a939 100644 --- a/crates/languages/src/cpp.rs +++ b/crates/languages/src/cpp.rs @@ -16,6 +16,70 @@ mod tests { use std::num::NonZeroU32; use unindent::Unindent; + #[gpui::test] + async fn test_cpp_autoindent_switch_case(cx: &mut TestAppContext) { + cx.update(|cx| { + let test_settings = SettingsStore::test(cx); + cx.set_global(test_settings); + cx.update_global::(|store, cx| { + store.update_user_settings(cx, |s| { + s.project.all_languages.defaults.tab_size = NonZeroU32::new(2); + }); + }); + }); + let language = crate::language("cpp", tree_sitter_cpp::LANGUAGE.into()); + + cx.new(|cx| { + let mut buffer = Buffer::local("", cx).with_language(language, cx); + + buffer.edit( + [( + 0..0, + r#" + int main() { + switch (a) { + case 1: + b++; + break; + case 2: + case 3: + c++; + break; + default: + d++; + } + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + switch (a) { + case 1: + b++; + break; + case 2: + case 3: + c++; + break; + default: + d++; + } + } + "# + .unindent(), + "statements under a case label should be indented, and the next label outdented" + ); + + buffer + }); + } + #[gpui::test] async fn test_cpp_autoindent_access_specifier(cx: &mut TestAppContext) { cx.update(|cx| {