Merge pull request #1098 from zed-industries/cpp

Max Brunsfeld created

Introduce support for C++

Change summary

Cargo.lock                                  |  11 +
crates/language/src/language.rs             |   3 
crates/zed/Cargo.toml                       |   1 
crates/zed/src/languages.rs                 |   5 
crates/zed/src/languages/c.rs               | 136 +++++++++++++++++++
crates/zed/src/languages/c/config.toml      |   2 
crates/zed/src/languages/c/highlights.scm   |   1 
crates/zed/src/languages/cpp/brackets.scm   |   3 
crates/zed/src/languages/cpp/config.toml    |  11 +
crates/zed/src/languages/cpp/highlights.scm | 158 +++++++++++++++++++++++
crates/zed/src/languages/cpp/indents.scm    |   7 +
crates/zed/src/languages/cpp/outline.scm    | 101 ++++++++++++++
crates/zed/src/languages/json.rs            |   2 
crates/zed/src/languages/typescript.rs      |   2 
crates/zed/src/main.rs                      |  14 ++
15 files changed, 449 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5318,6 +5318,16 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-cpp"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a869e3c5cef4e5db4e9ab16a8dc84d73010e60ada14cdc60d2f6d8aed17779d"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-json"
 version = "0.19.0"
@@ -6030,6 +6040,7 @@ dependencies = [
  "toml",
  "tree-sitter",
  "tree-sitter-c",
+ "tree-sitter-cpp",
  "tree-sitter-json 0.20.0",
  "tree-sitter-markdown",
  "tree-sitter-rust",

crates/language/src/language.rs 🔗

@@ -77,7 +77,8 @@ pub trait LspAdapter: 'static + Send + Sync {
         container_dir: PathBuf,
     ) -> BoxFuture<'static, Result<PathBuf>>;
     fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>>;
-    fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
+
+    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
     fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option<CodeLabel> {
         None

crates/zed/Cargo.toml 🔗

@@ -88,6 +88,7 @@ tiny_http = "0.8"
 toml = "0.5"
 tree-sitter = "0.20.6"
 tree-sitter-c = "0.20.1"
+tree-sitter-cpp = "0.20.0"
 tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
 tree-sitter-rust = "0.20.1"
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }

crates/zed/src/languages.rs 🔗

@@ -22,6 +22,11 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi
             tree_sitter_c::language(),
             Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
         ),
+        (
+            "cpp",
+            tree_sitter_cpp::language(),
+            Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
+        ),
         (
             "json",
             tree_sitter_json::language(),

crates/zed/src/languages/c.rs 🔗

@@ -106,5 +106,139 @@ impl super::LspAdapter for CLspAdapter {
         .boxed()
     }
 
-    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+    fn label_for_completion(
+        &self,
+        completion: &lsp::CompletionItem,
+        language: &Language,
+    ) -> Option<CodeLabel> {
+        let label = completion
+            .label
+            .strip_prefix("•")
+            .unwrap_or(&completion.label)
+            .trim();
+
+        match completion.kind {
+            Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
+                let detail = completion.detail.as_ref().unwrap();
+                let text = format!("{} {}", detail, label);
+                let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
+                let runs = language.highlight_text(&source, 11..11 + text.len());
+                return Some(CodeLabel {
+                    filter_range: detail.len() + 1..text.len(),
+                    text,
+                    runs,
+                });
+            }
+            Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
+                if completion.detail.is_some() =>
+            {
+                let detail = completion.detail.as_ref().unwrap();
+                let text = format!("{} {}", detail, label);
+                let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
+                return Some(CodeLabel {
+                    filter_range: detail.len() + 1..text.len(),
+                    text,
+                    runs,
+                });
+            }
+            Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
+                if completion.detail.is_some() =>
+            {
+                let detail = completion.detail.as_ref().unwrap();
+                let text = format!("{} {}", detail, label);
+                let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
+                return Some(CodeLabel {
+                    filter_range: detail.len() + 1..text.rfind('(').unwrap_or(text.len()),
+                    text,
+                    runs,
+                });
+            }
+            Some(kind) => {
+                let highlight_name = match kind {
+                    lsp::CompletionItemKind::STRUCT
+                    | lsp::CompletionItemKind::INTERFACE
+                    | lsp::CompletionItemKind::CLASS
+                    | lsp::CompletionItemKind::ENUM => Some("type"),
+                    lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
+                    lsp::CompletionItemKind::KEYWORD => Some("keyword"),
+                    lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
+                        Some("constant")
+                    }
+                    _ => None,
+                };
+                if let Some(highlight_id) = language
+                    .grammar()
+                    .and_then(|g| g.highlight_id_for_name(highlight_name?))
+                {
+                    let mut label = CodeLabel::plain(label.to_string(), None);
+                    label.runs.push((
+                        0..label.text.rfind('(').unwrap_or(label.text.len()),
+                        highlight_id,
+                    ));
+                    return Some(label);
+                }
+            }
+            _ => {}
+        }
+        Some(CodeLabel::plain(label.to_string(), None))
+    }
+
+    fn label_for_symbol(
+        &self,
+        name: &str,
+        kind: lsp::SymbolKind,
+        language: &Language,
+    ) -> Option<CodeLabel> {
+        let (text, filter_range, display_range) = match kind {
+            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
+                let text = format!("void {} () {{}}", name);
+                let filter_range = 0..name.len();
+                let display_range = 5..5 + name.len();
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::STRUCT => {
+                let text = format!("struct {} {{}}", name);
+                let filter_range = 7..7 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::ENUM => {
+                let text = format!("enum {} {{}}", name);
+                let filter_range = 5..5 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::INTERFACE | lsp::SymbolKind::CLASS => {
+                let text = format!("class {} {{}}", name);
+                let filter_range = 6..6 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::CONSTANT => {
+                let text = format!("const int {} = 0;", name);
+                let filter_range = 10..10 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::MODULE => {
+                let text = format!("namespace {} {{}}", name);
+                let filter_range = 10..10 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            lsp::SymbolKind::TYPE_PARAMETER => {
+                let text = format!("typename {} {{}};", name);
+                let filter_range = 9..9 + name.len();
+                let display_range = 0..filter_range.end;
+                (text, filter_range, display_range)
+            }
+            _ => return None,
+        };
+
+        Some(CodeLabel {
+            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
+            text: text[display_range].to_string(),
+            filter_range,
+        })
+    }
 }

crates/zed/src/languages/cpp/config.toml 🔗

@@ -0,0 +1,11 @@
+name = "C++"
+path_suffixes = ["cc", "cpp", "h", "hpp"]
+line_comment = "// "
+autoclose_before = ";:.,=}])>"
+brackets = [
+    { start = "{", end = "}", close = true, newline = true },
+    { start = "[", end = "]", close = true, newline = true },
+    { start = "(", end = ")", close = true, newline = true },
+    { start = "\"", end = "\"", close = true, newline = false },
+    { start = "/*", end = " */", close = true, newline = false },
+]

crates/zed/src/languages/cpp/highlights.scm 🔗

@@ -0,0 +1,158 @@
+(call_expression
+  function: (qualified_identifier
+    name: (identifier) @function))
+
+(call_expression
+  function: (identifier) @function)
+
+(call_expression
+  function: (field_expression
+    field: (field_identifier) @function))
+
+(preproc_function_def
+  name: (identifier) @function.special)
+
+(template_function
+  name: (identifier) @function)
+
+(template_method
+  name: (field_identifier) @function)
+
+(function_declarator
+  declarator: (identifier) @function)
+
+(function_declarator
+  declarator: (qualified_identifier
+    name: (identifier) @function))
+
+(function_declarator
+  declarator: (field_identifier) @function)
+
+((namespace_identifier) @type
+ (#match? @type "^[A-Z]"))
+
+(auto) @type
+(type_identifier) @type
+
+(identifier) @variable
+
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]*$"))
+
+(field_identifier) @property
+(statement_identifier) @label
+(this) @variable.builtin
+
+[
+  "break"
+  "case"
+  "catch"
+  "class"
+  "co_await"
+  "co_return"
+  "co_yield"
+  "const"
+  "constexpr"
+  "continue"
+  "default"
+  "delete"
+  "do"
+  "else"
+  "enum"
+  "explicit"
+  "extern"
+  "final"
+  "for"
+  "friend"
+  "if"
+  "if"
+  "inline"
+  "mutable"
+  "namespace"
+  "new"
+  "noexcept"
+  "override"
+  "private"
+  "protected"
+  "public"
+  "return"
+  "sizeof"
+  "static"
+  "struct"
+  "switch"
+  "template"
+  "throw"
+  "try"
+  "typedef"
+  "typename"
+  "union"
+  "using"
+  "virtual"
+  "volatile"
+  "while"
+  (primitive_type)
+  (type_qualifier)
+] @keyword
+
+[
+  "#define"
+  "#elif"
+  "#else"
+  "#endif"
+  "#if"
+  "#ifdef"
+  "#ifndef"
+  "#include"
+  (preproc_directive)
+] @keyword
+
+(comment) @comment
+
+[
+  (true)
+  (false)
+  (null)
+  (nullptr)
+] @constant
+
+(number_literal) @number
+
+[
+  (string_literal)
+  (system_lib_string)
+  (char_literal)
+  (raw_string_literal)
+] @string
+
+[
+  "."
+  ";"
+] @punctuation.delimiter
+
+[
+  "{"
+  "}"
+  "("
+  ")"
+  "["
+  "]"
+] @punctuation.bracket
+
+[
+  "--"
+  "-"
+  "-="
+  "->"
+  "="
+  "!="
+  "*"
+  "&"
+  "&&"
+  "+"
+  "++"
+  "+="
+  "<"
+  "=="
+  ">"
+  "||"
+] @operator

crates/zed/src/languages/cpp/outline.scm 🔗

@@ -0,0 +1,101 @@
+(preproc_def
+    "#define" @context
+    name: (_) @name) @item
+
+(preproc_function_def
+    "#define" @context
+    name: (_) @name
+    parameters: (preproc_params
+        "(" @context
+        ")" @context)) @item
+
+(type_definition
+    "typedef" @context
+    declarator: (_) @name) @item
+
+(struct_specifier
+    "struct" @context
+    name: (_) @name) @item
+
+(class_specifier
+    "class" @context
+    name: (_) @name) @item
+
+(enum_specifier
+    "enum" @context
+    name: (_) @name) @item
+
+(enumerator
+    name: (_) @name) @item
+
+(declaration
+    (storage_class_specifier) @context
+    (type_qualifier)? @context
+    type: (_) @context
+    declarator: (init_declarator
+      declarator: (_) @name)) @item
+
+(function_definition
+    (type_qualifier)? @context
+    type: (_)? @context
+    declarator: [
+        (function_declarator
+            declarator: (_) @name
+            parameters: (parameter_list
+                "(" @context
+                ")" @context))
+        (pointer_declarator
+            "*" @context
+            declarator: (function_declarator
+                declarator: (_) @name
+                parameters: (parameter_list
+                    "(" @context
+                    ")" @context)))
+    ]
+    (type_qualifier)? @context) @item
+
+(declaration
+    (type_qualifier)? @context
+    type: (_)? @context
+    declarator: [
+        (field_identifier) @name
+        (pointer_declarator
+            "*" @context
+            declarator: (field_identifier) @name)
+        (function_declarator
+            declarator: (_) @name
+            parameters: (parameter_list
+                "(" @context
+                ")" @context))
+        (pointer_declarator
+            "*" @context
+            declarator: (function_declarator
+                declarator: (_) @name
+                parameters: (parameter_list
+                    "(" @context
+                    ")" @context)))
+    ]
+    (type_qualifier)? @context) @item
+
+(field_declaration
+    (type_qualifier)? @context
+    type: (_) @context
+    declarator: [
+        (field_identifier) @name
+        (pointer_declarator
+            "*" @context
+            declarator: (field_identifier) @name)
+        (function_declarator
+            declarator: (_) @name
+            parameters: (parameter_list
+                "(" @context
+                ")" @context))
+        (pointer_declarator
+            "*" @context
+            declarator: (function_declarator
+                declarator: (_) @name
+                parameters: (parameter_list
+                    "(" @context
+                    ")" @context)))
+    ]
+    (type_qualifier)? @context) @item

crates/zed/src/languages/json.rs 🔗

@@ -120,8 +120,6 @@ impl LspAdapter for JsonLspAdapter {
         .boxed()
     }
 
-    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-
     fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true

crates/zed/src/languages/typescript.rs 🔗

@@ -113,8 +113,6 @@ impl LspAdapter for TypeScriptLspAdapter {
         .boxed()
     }
 
-    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
-
     fn label_for_completion(
         &self,
         item: &lsp::CompletionItem,

crates/zed/src/main.rs 🔗

@@ -74,6 +74,20 @@ fn main() {
                 ..Default::default()
             },
         )
+        .with_overrides(
+            "C",
+            settings::LanguageOverride {
+                tab_size: Some(2),
+                ..Default::default()
+            },
+        )
+        .with_overrides(
+            "C++",
+            settings::LanguageOverride {
+                tab_size: Some(2),
+                ..Default::default()
+            },
+        )
         .with_overrides(
             "Markdown",
             settings::LanguageOverride {