From d32934a893584402ac49f0775ba77bd1af90ecbd Mon Sep 17 00:00:00 2001 From: Mayank Verma Date: Mon, 17 Nov 2025 14:35:54 +0530 Subject: [PATCH] languages: Fix indentation for if/else statements in C/C++ without braces (#41670) Closes #41179 Release Notes: - Fixed indentation for if/else statements in C/C++ without braces --- crates/languages/Cargo.toml | 1 + crates/languages/src/c.rs | 231 ++++++++++++++++++++++-- crates/languages/src/c/config.toml | 2 +- crates/languages/src/cpp.rs | 254 +++++++++++++++++++++++++++ crates/languages/src/cpp/config.toml | 2 +- crates/languages/src/lib.rs | 1 + 6 files changed, 477 insertions(+), 14 deletions(-) create mode 100644 crates/languages/src/cpp.rs diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index e78f29a8d6ef49726471fa186a2e9dee095fa0d5..635cb11ba5ef5bfd904eaac865d86cad6d6309fc 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -98,6 +98,7 @@ text.workspace = true theme = { workspace = true, features = ["test-support"] } tree-sitter-bash.workspace = true tree-sitter-c.workspace = true +tree-sitter-cpp.workspace = true tree-sitter-css.workspace = true tree-sitter-go.workspace = true tree-sitter-python.workspace = true diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 3351c9df033a5e4550e34b5c9dbf1d119b189f6d..cffd01136be2b3762735087eaf5844866d47e174 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -395,10 +395,10 @@ mod tests { use language::{AutoindentMode, Buffer}; use settings::SettingsStore; use std::num::NonZeroU32; + use unindent::Unindent; #[gpui::test] - async fn test_c_autoindent(cx: &mut TestAppContext) { - // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); + async fn test_c_autoindent_basic(cx: &mut TestAppContext) { cx.update(|cx| { let test_settings = SettingsStore::test(cx); cx.set_global(test_settings); @@ -413,23 +413,230 @@ mod tests { cx.new(|cx| { let mut buffer = Buffer::local("", cx).with_language(language, cx); - // empty function buffer.edit([(0..0, "int main() {}")], None, cx); - // indent inside braces let ix = buffer.len() - 1; buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx); - assert_eq!(buffer.text(), "int main() {\n \n}"); + assert_eq!( + buffer.text(), + "int main() {\n \n}", + "content inside braces should be indented" + ); - // indent body of single-statement if statement - let ix = buffer.len() - 2; - buffer.edit([(ix..ix, "if (a)\nb;")], Some(AutoindentMode::EachLine), cx); - assert_eq!(buffer.text(), "int main() {\n if (a)\n b;\n}"); + buffer + }); + } - // indent inside field expression - let ix = buffer.len() - 3; + #[gpui::test] + async fn test_c_autoindent_if_else(cx: &mut TestAppContext) { + 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.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() { + if (a) + b; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b; + } + "# + .unindent(), + "body of if-statement without braces should be indented" + ); + + let ix = buffer.len() - 4; buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx); - assert_eq!(buffer.text(), "int main() {\n if (a)\n b\n .c;\n}"); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b + .c; + } + "# + .unindent(), + "field expression (.c) should be indented further than the statement body" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) a++; + else b++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) a++; + else b++; + } + "# + .unindent(), + "single-line if/else without braces should align at the same level" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + b++; + else + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b++; + else + c++; + } + "# + .unindent(), + "multi-line if/else without braces should indent statement bodies" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + if (b) + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + if (b) + c++; + } + "# + .unindent(), + "nested if statements without braces should indent properly" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + b++; + else if (c) + d++; + else + f++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b++; + else if (c) + d++; + else + f++; + } + "# + .unindent(), + "else-if chains should align all conditions at same level with indented bodies" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) { + b++; + } else + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) { + b++; + } else + c++; + } + "# + .unindent(), + "mixed braces should indent properly" + ); buffer }); diff --git a/crates/languages/src/c/config.toml b/crates/languages/src/c/config.toml index 76a27ccc81911bcf25c7da3efef191214eab7b00..c490269b12309632d2fd8fb944ed48ee74c46075 100644 --- a/crates/languages/src/c/config.toml +++ b/crates/languages/src/c/config.toml @@ -4,7 +4,7 @@ path_suffixes = ["c"] line_comments = ["// "] decrease_indent_patterns = [ { pattern = "^\\s*\\{.*\\}?\\s*$", valid_after = ["if", "for", "while", "do", "switch", "else"] }, - { pattern = "^\\s*else\\s*$", valid_after = ["if"] } + { pattern = "^\\s*else\\b", valid_after = ["if"] } ] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/languages/src/cpp.rs b/crates/languages/src/cpp.rs new file mode 100644 index 0000000000000000000000000000000000000000..2094d946eadbf32bdf3a4660d226c660eda9d4dc --- /dev/null +++ b/crates/languages/src/cpp.rs @@ -0,0 +1,254 @@ +#[cfg(test)] +mod tests { + use gpui::{AppContext as _, BorrowAppContext, TestAppContext}; + use language::{AutoindentMode, Buffer}; + use settings::SettingsStore; + use std::num::NonZeroU32; + use unindent::Unindent; + + #[gpui::test] + async fn test_cpp_autoindent_basic(cx: &mut TestAppContext) { + 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.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, "int main() {}")], None, cx); + + let ix = buffer.len() - 1; + buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx); + assert_eq!( + buffer.text(), + "int main() {\n \n}", + "content inside braces should be indented" + ); + + buffer + }); + } + + #[gpui::test] + async fn test_cpp_autoindent_if_else(cx: &mut TestAppContext) { + 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.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() { + if (a) + b; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b; + } + "# + .unindent(), + "body of if-statement without braces should be indented" + ); + + let ix = buffer.len() - 4; + buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b + .c; + } + "# + .unindent(), + "field expression (.c) should be indented further than the statement body" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) a++; + else b++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) a++; + else b++; + } + "# + .unindent(), + "single-line if/else without braces should align at the same level" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + b++; + else + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b++; + else + c++; + } + "# + .unindent(), + "multi-line if/else without braces should indent statement bodies" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + if (b) + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + if (b) + c++; + } + "# + .unindent(), + "nested if statements without braces should indent properly" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) + b++; + else if (c) + d++; + else + f++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) + b++; + else if (c) + d++; + else + f++; + } + "# + .unindent(), + "else-if chains should align all conditions at same level with indented bodies" + ); + + buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx); + buffer.edit( + [( + 0..0, + r#" + int main() { + if (a) { + b++; + } else + c++; + } + "# + .unindent(), + )], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + r#" + int main() { + if (a) { + b++; + } else + c++; + } + "# + .unindent(), + "mixed braces should indent properly" + ); + + buffer + }); + } +} diff --git a/crates/languages/src/cpp/config.toml b/crates/languages/src/cpp/config.toml index 4d3c0a0a38664f4dd584a0ce3f3544662b19bbae..1a994789232e4a58f4bdb2436865c0c28b9164f0 100644 --- a/crates/languages/src/cpp/config.toml +++ b/crates/languages/src/cpp/config.toml @@ -4,7 +4,7 @@ path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "ipp", "inl line_comments = ["// ", "/// ", "//! "] decrease_indent_patterns = [ { pattern = "^\\s*\\{.*\\}?\\s*$", valid_after = ["if", "for", "while", "do", "switch", "else"] }, - { pattern = "^\\s*else\\s*$", valid_after = ["if"] } + { pattern = "^\\s*else\\b", valid_after = ["if"] } ] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 76e1ae5edd2593907bd374d398946a1f6083a82e..0fe5a5b77477e4358dbf03a9d4131f45b3b6efec 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -19,6 +19,7 @@ use crate::{ mod bash; mod c; +mod cpp; mod css; mod go; mod json;