Dart support (#7220)

Abdullah Alsigar created

This is my first contribution, feedback is welcome.

Release Notes:

- Added Dart language support
([#5343](https://github.com/zed-industries/zed/issues/5343)).

Change summary

Cargo.lock                                   |  10 +
Cargo.toml                                   |   1 
crates/zed/Cargo.toml                        |   1 
crates/zed/src/languages.rs                  |   3 
crates/zed/src/languages/dart.rs             |  53 +++++
crates/zed/src/languages/dart/brackets.scm   |   6 
crates/zed/src/languages/dart/config.toml    |  13 +
crates/zed/src/languages/dart/highlights.scm | 209 ++++++++++++++++++++++
crates/zed/src/languages/dart/indents.scm    |   7 
crates/zed/src/languages/dart/outline.scm    |  18 +
10 files changed, 321 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -10136,6 +10136,15 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-dart"
+version = "0.0.1"
+source = "git+https://github.com/agent3bood/tree-sitter-dart?rev=48934e3bf757a9b78f17bdfaa3e2b4284656fdc7#48934e3bf757a9b78f17bdfaa3e2b4284656fdc7"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-dockerfile"
 version = "0.1.0"
@@ -12011,6 +12020,7 @@ dependencies = [
  "tree-sitter-clojure",
  "tree-sitter-cpp",
  "tree-sitter-css",
+ "tree-sitter-dart",
  "tree-sitter-dockerfile",
  "tree-sitter-elixir",
  "tree-sitter-elm",

Cargo.toml 🔗

@@ -236,6 +236,7 @@ tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-shar
 tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
 tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
 tree-sitter-dockerfile = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev = "33e22c33bcdbfc33d42806ee84cfd0b1248cc392" }
+tree-sitter-dart = { git = "https://github.com/agent3bood/tree-sitter-dart", rev = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7" }
 tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
 tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
 tree-sitter-embedded-template = "0.20.0"

crates/zed/Cargo.toml 🔗

@@ -119,6 +119,7 @@ tree-sitter-clojure.workspace = true
 tree-sitter-cpp.workspace = true
 tree-sitter-css.workspace = true
 tree-sitter-dockerfile.workspace = true
+tree-sitter-dart.workspace = true
 tree-sitter-elixir.workspace = true
 tree-sitter-elm.workspace = true
 tree-sitter-embedded-template.workspace = true

crates/zed/src/languages.rs 🔗

@@ -14,6 +14,7 @@ mod c;
 mod clojure;
 mod csharp;
 mod css;
+mod dart;
 mod deno;
 mod dockerfile;
 mod elixir;
@@ -120,6 +121,7 @@ pub fn init(
         ("vue", tree_sitter_vue::language()),
         ("yaml", tree_sitter_yaml::language()),
         ("zig", tree_sitter_zig::language()),
+        ("dart", tree_sitter_dart::language()),
     ]);
 
     let language = |asset_dir_name: &'static str, adapters| {
@@ -326,6 +328,7 @@ pub fn init(
             node_runtime.clone(),
         ))],
     );
+    language("dart", vec![Arc::new(dart::DartLanguageServer {})]);
 }
 
 #[cfg(any(test, feature = "test-support"))]

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

@@ -0,0 +1,53 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp::LanguageServerBinary;
+use std::{any::Any, path::PathBuf};
+
+pub struct DartLanguageServer;
+
+#[async_trait]
+impl LspAdapter for DartLanguageServer {
+    fn name(&self) -> LanguageServerName {
+        LanguageServerName("dart".into())
+    }
+
+    fn short_name(&self) -> &'static str {
+        "dart"
+    }
+
+    async fn fetch_latest_server_version(
+        &self,
+        _: &dyn LspAdapterDelegate,
+    ) -> Result<Box<dyn 'static + Send + Any>> {
+        Ok(Box::new(()))
+    }
+
+    async fn fetch_server_binary(
+        &self,
+        _: Box<dyn 'static + Send + Any>,
+        _: PathBuf,
+        _: &dyn LspAdapterDelegate,
+    ) -> Result<LanguageServerBinary> {
+        Err(anyhow!("dart must me installed from dart.dev/get-dart"))
+    }
+
+    async fn cached_server_binary(
+        &self,
+        _: PathBuf,
+        _: &dyn LspAdapterDelegate,
+    ) -> Option<LanguageServerBinary> {
+        Some(LanguageServerBinary {
+            path: "dart".into(),
+            arguments: vec!["language-server".into(), "--protocol=lsp".into()],
+        })
+    }
+
+    fn can_be_reinstalled(&self) -> bool {
+        false
+    }
+
+    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+        None
+    }
+}

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

@@ -0,0 +1,13 @@
+name = "Dart"
+grammar = "dart"
+path_suffixes = ["dart"]
+line_comments = ["// "]
+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, not_in = ["string"] },
+    { start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
+    { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]

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

@@ -0,0 +1,209 @@
+(dotted_identifier_list) @string
+
+; Methods
+; --------------------
+(function_type
+    name: (identifier) @function)
+(super) @function
+
+; Annotations
+; --------------------
+(annotation
+  name: (identifier) @attribute)
+
+; Operators and Tokens
+; --------------------
+(template_substitution
+  "$" @punctuation.special
+  "{" @punctuation.special
+  "}" @punctuation.special
+  ) @none
+
+(template_substitution
+  "$" @punctuation.special
+  (identifier_dollar_escaped) @variable
+  ) @none
+
+(escape_sequence) @string.escape
+
+[
+  "@"
+  "=>"
+  ".."
+  "??"
+  "=="
+  "?"
+  ":"
+  "&&"
+  "%"
+  "<"
+  ">"
+  "="
+  ">="
+  "<="
+  "||"
+  (increment_operator)
+  (is_operator)
+  (prefix_operator)
+  (equality_operator)
+  (additive_operator)
+  ] @operator
+
+[
+  "("
+  ")"
+  "["
+  "]"
+  "{"
+  "}"
+  "<"
+  ">"
+  ]  @punctuation.bracket
+
+; Delimiters
+; --------------------
+[
+  ";"
+  "."
+  ","
+  ] @punctuation.delimiter
+
+; Types
+; --------------------
+(class_definition
+  name: (identifier) @type)
+(constructor_signature
+  name: (identifier) @type)
+(scoped_identifier
+  scope: (identifier) @type)
+(function_signature
+  name: (identifier) @function)
+(getter_signature
+  (identifier) @function)
+(setter_signature
+  name: (identifier) @function)
+(enum_declaration
+  name: (identifier) @type)
+(enum_constant
+  name: (identifier) @type)
+(type_identifier) @type
+(void_type) @type
+
+((scoped_identifier
+   scope: (identifier) @type
+   name: (identifier) @type)
+  (#match? @type "^[a-zA-Z]"))
+
+(type_identifier) @type
+
+; Variables
+; --------------------
+; var keyword
+(inferred_type) @keyword
+
+(const_builtin) @constant.builtin
+(final_builtin) @constant.builtin
+
+((identifier) @type
+  (#match? @type "^_?[A-Z]"))
+
+("Function" @type)
+
+; properties
+; TODO: add method/call_expression to grammar and
+; distinguish method call from variable access
+(unconditional_assignable_selector
+  (identifier) @property)
+
+; assignments
+(assignment_expression
+  left: (assignable_expression) @variable)
+
+(this) @variable.builtin
+
+; Literals
+; --------------------
+[
+  (hex_integer_literal)
+  (decimal_integer_literal)
+  (decimal_floating_point_literal)
+  ; TODO: inaccessible nodes
+  ; (octal_integer_literal)
+  ; (hex_floating_point_literal)
+  ] @number
+
+(symbol_literal) @symbol
+(string_literal) @string
+(true) @boolean
+(false) @boolean
+(null_literal) @constant.builtin
+
+(documentation_comment) @comment
+(comment) @comment
+
+; Keywords
+; --------------------
+["import" "library" "export"] @keyword.include
+
+; Reserved words (cannot be used as identifiers)
+; TODO: "rethrow" @keyword
+[
+  ; "assert"
+  (case_builtin)
+  "extension"
+  "on"
+  "class"
+  "enum"
+  "extends"
+  "in"
+  "is"
+  "new"
+  "return"
+  "super"
+  "with"
+  ] @keyword
+
+
+; Built in identifiers:
+; alone these are marked as keywords
+[
+  "abstract"
+  "as"
+  "async"
+  "async*"
+  "yield"
+  "sync*"
+  "await"
+  "covariant"
+  "deferred"
+  "dynamic"
+  "external"
+  "factory"
+  "get"
+  "implements"
+  "interface"
+  "library"
+  "operator"
+  "mixin"
+  "part"
+  "set"
+  "show"
+  "static"
+  "typedef"
+  ] @keyword
+
+; when used as an identifier:
+((identifier) @variable.builtin
+  (#vim-match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
+
+["if" "else" "switch" "default"] @keyword
+
+[
+  "try"
+  "throw"
+  "catch"
+  "finally"
+  (break_statement)
+  ] @keyword
+
+["do" "while" "continue" "for"] @keyword

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

@@ -0,0 +1,18 @@
+(class_definition
+    "class" @context
+    name: (_) @name) @item
+
+(function_signature
+    name: (_) @name) @item
+
+(getter_signature
+    "get" @context
+    name: (_) @name) @item
+
+(setter_signature
+    "set" @context
+    name: (_) @name) @item
+
+(enum_declaration
+    "enum" @context
+    name: (_) @name) @item