From dd1c88afa5c703ced5bb36205415d2501ce4cb23 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 28 Mar 2022 18:18:49 -0700 Subject: [PATCH] Add basic TypeScript and TSX support Co-Authored-By: Keith Simmons --- Cargo.lock | 11 + crates/zed/Cargo.toml | 1 + crates/zed/languages/tsx/brackets.scm | 1 + crates/zed/languages/tsx/config.toml | 12 + crates/zed/languages/tsx/highlights-jsx.scm | 0 crates/zed/languages/tsx/highlights.scm | 1 + crates/zed/languages/tsx/indents.scm | 1 + crates/zed/languages/tsx/outline.scm | 1 + crates/zed/languages/typescript/brackets.scm | 5 + crates/zed/languages/typescript/config.toml | 12 + .../zed/languages/typescript/highlights.scm | 219 ++++++++++++++++++ crates/zed/languages/typescript/indents.scm | 15 ++ crates/zed/languages/typescript/outline.scm | 55 +++++ crates/zed/src/language.rs | 52 +++-- 14 files changed, 373 insertions(+), 13 deletions(-) create mode 120000 crates/zed/languages/tsx/brackets.scm create mode 100644 crates/zed/languages/tsx/config.toml create mode 100644 crates/zed/languages/tsx/highlights-jsx.scm create mode 120000 crates/zed/languages/tsx/highlights.scm create mode 120000 crates/zed/languages/tsx/indents.scm create mode 120000 crates/zed/languages/tsx/outline.scm create mode 100644 crates/zed/languages/typescript/brackets.scm create mode 100644 crates/zed/languages/typescript/config.toml create mode 100644 crates/zed/languages/typescript/highlights.scm create mode 100644 crates/zed/languages/typescript/indents.scm create mode 100644 crates/zed/languages/typescript/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 9c36923964..a90a6d2adf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5415,6 +5415,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-typescript" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8ed0ecb931cdff13c6a13f45ccd615156e2779d9ffb0395864e05505e6e86d" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "ttf-parser" version = "0.9.0" @@ -6025,6 +6035,7 @@ dependencies = [ "tree-sitter-json", "tree-sitter-markdown", "tree-sitter-rust", + "tree-sitter-typescript", "unindent", "url", "util", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index fc9946b778..869a6a9999 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -98,6 +98,7 @@ tree-sitter-c = "0.20.1" tree-sitter-json = "0.19.0" tree-sitter-rust = "0.20.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +tree-sitter-typescript = "0.20.1" url = "2.2" [dev-dependencies] diff --git a/crates/zed/languages/tsx/brackets.scm b/crates/zed/languages/tsx/brackets.scm new file mode 120000 index 0000000000..e6835c943b --- /dev/null +++ b/crates/zed/languages/tsx/brackets.scm @@ -0,0 +1 @@ +../typescript/brackets.scm \ No newline at end of file diff --git a/crates/zed/languages/tsx/config.toml b/crates/zed/languages/tsx/config.toml new file mode 100644 index 0000000000..62717266df --- /dev/null +++ b/crates/zed/languages/tsx/config.toml @@ -0,0 +1,12 @@ +name = "TSX" +path_suffixes = ["tsx"] +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 = false, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "/*", end = " */", close = true, newline = false }, +] diff --git a/crates/zed/languages/tsx/highlights-jsx.scm b/crates/zed/languages/tsx/highlights-jsx.scm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/zed/languages/tsx/highlights.scm b/crates/zed/languages/tsx/highlights.scm new file mode 120000 index 0000000000..226302a5d1 --- /dev/null +++ b/crates/zed/languages/tsx/highlights.scm @@ -0,0 +1 @@ +../typescript/highlights.scm \ No newline at end of file diff --git a/crates/zed/languages/tsx/indents.scm b/crates/zed/languages/tsx/indents.scm new file mode 120000 index 0000000000..502c2a060a --- /dev/null +++ b/crates/zed/languages/tsx/indents.scm @@ -0,0 +1 @@ +../typescript/indents.scm \ No newline at end of file diff --git a/crates/zed/languages/tsx/outline.scm b/crates/zed/languages/tsx/outline.scm new file mode 120000 index 0000000000..a0df409fda --- /dev/null +++ b/crates/zed/languages/tsx/outline.scm @@ -0,0 +1 @@ +../typescript/outline.scm \ No newline at end of file diff --git a/crates/zed/languages/typescript/brackets.scm b/crates/zed/languages/typescript/brackets.scm new file mode 100644 index 0000000000..63395f81d8 --- /dev/null +++ b/crates/zed/languages/typescript/brackets.scm @@ -0,0 +1,5 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/languages/typescript/config.toml b/crates/zed/languages/typescript/config.toml new file mode 100644 index 0000000000..5d491d2d32 --- /dev/null +++ b/crates/zed/languages/typescript/config.toml @@ -0,0 +1,12 @@ +name = "TypeScript" +path_suffixes = ["ts"] +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 = false, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "/*", end = " */", close = true, newline = false }, +] diff --git a/crates/zed/languages/typescript/highlights.scm b/crates/zed/languages/typescript/highlights.scm new file mode 100644 index 0000000000..cb4e82b33d --- /dev/null +++ b/crates/zed/languages/typescript/highlights.scm @@ -0,0 +1,219 @@ +; Variables + +(identifier) @variable + +; Properties + +(property_identifier) @property + +; Function and method calls + +(call_expression + function: (identifier) @function) + +(call_expression + function: (member_expression + property: (property_identifier) @function.method)) + +; Function and method definitions + +(function + name: (identifier) @function) +(function_declaration + name: (identifier) @function) +(method_definition + name: (property_identifier) @function.method) + +(pair + key: (property_identifier) @function.method + value: [(function) (arrow_function)]) + +(assignment_expression + left: (member_expression + property: (property_identifier) @function.method) + right: [(function) (arrow_function)]) + +(variable_declarator + name: (identifier) @function + value: [(function) (arrow_function)]) + +(assignment_expression + left: (identifier) @function + right: [(function) (arrow_function)]) + +; Special identifiers + +((identifier) @constructor + (#match? @constructor "^[A-Z]")) + +([ + (identifier) + (shorthand_property_identifier) + (shorthand_property_identifier_pattern) + ] @constant + (#match? @constant "^[A-Z_][A-Z\\d_]+$")) + +; Literals + +(this) @variable.builtin +(super) @variable.builtin + +[ + (true) + (false) + (null) + (undefined) +] @constant.builtin + +(comment) @comment + +[ + (string) + (template_string) +] @string + +(regex) @string.special +(number) @number + +; Tokens + +(template_substitution + "${" @punctuation.special + "}" @punctuation.special) @embedded + +[ + ";" + "?." + "." + "," +] @punctuation.delimiter + +[ + "-" + "--" + "-=" + "+" + "++" + "+=" + "*" + "*=" + "**" + "**=" + "/" + "/=" + "%" + "%=" + "<" + "<=" + "<<" + "<<=" + "=" + "==" + "===" + "!" + "!=" + "!==" + "=>" + ">" + ">=" + ">>" + ">>=" + ">>>" + ">>>=" + "~" + "^" + "&" + "|" + "^=" + "&=" + "|=" + "&&" + "||" + "??" + "&&=" + "||=" + "??=" +] @operator + +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +[ + "as" + "async" + "await" + "break" + "case" + "catch" + "class" + "const" + "continue" + "debugger" + "default" + "delete" + "do" + "else" + "export" + "extends" + "finally" + "for" + "from" + "function" + "get" + "if" + "import" + "in" + "instanceof" + "let" + "new" + "of" + "return" + "set" + "static" + "switch" + "target" + "throw" + "try" + "typeof" + "var" + "void" + "while" + "with" + "yield" +] @keyword + +; Types + +(type_identifier) @type +(predefined_type) @type.builtin + +((identifier) @type + (#match? @type "^[A-Z]")) + +(type_arguments + "<" @punctuation.bracket + ">" @punctuation.bracket) + +; Keywords + +[ "abstract" + "declare" + "enum" + "export" + "implements" + "interface" + "keyof" + "namespace" + "private" + "protected" + "public" + "type" + "readonly" + "override" +] @keyword \ No newline at end of file diff --git a/crates/zed/languages/typescript/indents.scm b/crates/zed/languages/typescript/indents.scm new file mode 100644 index 0000000000..107e6ff8e0 --- /dev/null +++ b/crates/zed/languages/typescript/indents.scm @@ -0,0 +1,15 @@ +[ + (call_expression) + (assignment_expression) + (member_expression) + (lexical_declaration) + (variable_declaration) + (assignment_expression) + (if_statement) + (for_statement) +] @indent + +(_ "[" "]" @end) @indent +(_ "<" ">" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/languages/typescript/outline.scm b/crates/zed/languages/typescript/outline.scm new file mode 100644 index 0000000000..f8691fa41d --- /dev/null +++ b/crates/zed/languages/typescript/outline.scm @@ -0,0 +1,55 @@ +(internal_module + "namespace" @context + name: (_) @name) @item + +(enum_declaration + "enum" @context + name: (_) @name) @item + +(function_declaration + "async"? @context + "function" @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item + +(interface_declaration + "interface" @context + name: (_) @name) @item + +(program + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (_) @name) @item)) + +(class_declaration + "class" @context + name: (_) @name) @item + +(method_definition + [ + "get" + "set" + "async" + "*" + "readonly" + "static" + (override_modifier) + (accessibility_modifier) + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item + +(public_field_definition + [ + "declare" + "readonly" + "abstract" + "static" + (accessibility_modifier) + ]* @context + name: (_) @name) @item diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index fbf413c644..50540bc82c 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -555,6 +555,11 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi tree_sitter_rust::language(), Some(Arc::new(RustLspAdapter)), ), + ( + "tsx", + tree_sitter_typescript::language_tsx(), + None, // + ), ( "typescript", tree_sitter_typescript::language_typescript(), @@ -578,17 +583,26 @@ fn language( ) .unwrap(); let mut language = Language::new(config, Some(grammar)); - if let Some(query) = load_query(&format!("{}/highlights.scm", name)) { - language = language.with_highlights_query(query.as_ref()).unwrap(); + + if let Some(query) = load_query(name, "/highlights") { + language = language + .with_highlights_query(query.as_ref()) + .expect("failed to evaluate highlights query"); } - if let Some(query) = load_query(&format!("{}/brackets.scm", name)) { - language = language.with_brackets_query(query.as_ref()).unwrap(); + if let Some(query) = load_query(name, "/brackets") { + language = language + .with_brackets_query(query.as_ref()) + .expect("failed to load brackets query"); } - if let Some(query) = load_query(&format!("{}/indents.scm", name)) { - language = language.with_indents_query(query.as_ref()).unwrap(); + if let Some(query) = load_query(name, "/indents") { + language = language + .with_indents_query(query.as_ref()) + .expect("failed to load indents query"); } - if let Some(query) = load_query(&format!("{}/outline.scm", name)) { - language = language.with_outline_query(query.as_ref()).unwrap(); + if let Some(query) = load_query(name, "/outline") { + language = language + .with_outline_query(query.as_ref()) + .expect("failed to load outline query"); } if let Some(lsp_adapter) = lsp_adapter { language = language.with_lsp_adapter(lsp_adapter) @@ -596,11 +610,23 @@ fn language( language } -fn load_query(path: &str) -> Option> { - LanguageDir::get(path).map(|item| match item.data { - Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), - Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), - }) +fn load_query(name: &str, filename_prefix: &str) -> Option> { + let mut result = None; + for path in LanguageDir::iter() { + if let Some(remainder) = path.strip_prefix(name) { + if remainder.starts_with(filename_prefix) { + let contents = match LanguageDir::get(path.as_ref()).unwrap().data { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), + }; + match &mut result { + None => result = Some(contents), + Some(r) => r.to_mut().push_str(contents.as_ref()), + } + } + } + } + result } #[cfg(test)]