├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── extension.toml ├── grammars └── templ.toml ├── languages └── templ │ ├── brackets.scm │ ├── brackets_go.scm │ ├── config.toml │ ├── embedding_go.scm │ ├── highlights.scm │ ├── highlights_go.scm │ ├── indents.scm │ ├── indents_go.scm │ ├── injections.scm │ ├── injections_go.scm │ ├── outline.scm │ ├── outline_go.scm │ ├── overrides_go.scm │ ├── structure.scm │ ├── textobjects.scm │ └── textobjects_go.scm └── src └── templ.rs /.gitignore: -------------------------------------------------------------------------------- 1 | grammars/ 2 | extension.wasm 3 | Cargo.lock 4 | target/ 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zed_templ" 3 | version = "0.0.7" 4 | edition = "2021" 5 | publish = false 6 | license = "MIT" 7 | 8 | [lib] 9 | path = "src/templ.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | zed_extension_api = "0.0.6" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mehmet Akif Duba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zed Templ 2 | 3 | This extension adds support for the [templ](https://github.com/a-h/templ) language for [Zed]([zed.dev](https://zed.dev)https://zed.dev). 4 | -------------------------------------------------------------------------------- /extension.toml: -------------------------------------------------------------------------------- 1 | id = "templ" 2 | name = "Templ" 3 | version = "0.0.7" 4 | schema_version = 1 5 | authors = ["Mehmet Akif Duba "] 6 | description = "Templ language support for Zed" 7 | repository = "https://github.com/makifdb/zed-templ" 8 | 9 | [language_servers.templ] 10 | name = "Templ Language Server" 11 | language = "Templ" 12 | 13 | [language_servers.templ.language_ids] 14 | "Templ" = "templ" 15 | 16 | [grammars.templ] 17 | repository = "https://github.com/vrischmann/tree-sitter-templ" 18 | commit = "9269b5a65e79be8fb6b6669935823263343b7ba0" 19 | -------------------------------------------------------------------------------- /grammars/templ.toml: -------------------------------------------------------------------------------- 1 | repository = "https://github.com/vrischmann/tree-sitter-templ" 2 | commit = "9269b5a65e79be8fb6b6669935823263343b7ba0" 3 | -------------------------------------------------------------------------------- /languages/templ/brackets.scm: -------------------------------------------------------------------------------- 1 | ("<" @open "/>" @close) 2 | ("" @close) 3 | ("<" @open ">" @close) 4 | ("{{" @open "}}" @close) 5 | -------------------------------------------------------------------------------- /languages/templ/brackets_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/0f584cb353afe0483d8a044619b7dabcfcd10e40/crates/languages/src/go/brackets.scm 2 | 3 | ("(" @open ")" @close) 4 | ("[" @open "]" @close) 5 | ("{" @open "}" @close) 6 | ("\"" @open "\"" @close) 7 | -------------------------------------------------------------------------------- /languages/templ/config.toml: -------------------------------------------------------------------------------- 1 | name = "Templ" 2 | grammar = "templ" 3 | path_suffixes = ["templ"] 4 | line_comments = ["// "] 5 | autoclose_before = ";:.,=}])>" 6 | brackets = [ 7 | { start = "{", end = "}", close = true, newline = true }, 8 | { start = "(", end = ")", close = true, newline = false }, 9 | { start = "[", end = "]", close = true, newline = false }, 10 | { start = "<", end = ">", close = true, newline = false }, 11 | { start = "\"", end = "\"", close = true, newline = false, not_in = [ 12 | "comment", 13 | "string", 14 | ] }, 15 | { start = "'", end = "'", close = true, newline = false, not_in = [ 16 | "comment", 17 | "string", 18 | ] }, 19 | { start = "`", end = "`", close = true, newline = false, not_in = [ 20 | "comment", 21 | "string", 22 | ] }, 23 | { start = "/*", end = " */", close = true, newline = false, not_in = [ 24 | "comment", 25 | "string", 26 | ] }, 27 | ] 28 | tab_size = 4 29 | hard_tabs = true 30 | scope_opt_in_language_servers = [ 31 | "tailwindcss-language-server", 32 | "vscode-html-language-server", 33 | "emmet-language-server", 34 | ] 35 | 36 | [overrides.string] 37 | completion_query_characters = ["-"] 38 | opt_into_language_servers = [ 39 | "tailwindcss-language-server", 40 | "vscode-html-language-server", 41 | ] 42 | -------------------------------------------------------------------------------- /languages/templ/embedding_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/0f584cb353afe0483d8a044619b7dabcfcd10e40/crates/languages/src/go/embedding.scm 2 | ( 3 | (comment)* @context 4 | . 5 | (type_declaration 6 | (type_spec 7 | name: (_) @name) 8 | ) @item 9 | ) 10 | 11 | ( 12 | (comment)* @context 13 | . 14 | (function_declaration 15 | name: (_) @name 16 | ) @item 17 | ) 18 | 19 | ( 20 | (comment)* @context 21 | . 22 | (method_declaration 23 | name: (_) @name 24 | ) @item 25 | ) 26 | -------------------------------------------------------------------------------- /languages/templ/highlights.scm: -------------------------------------------------------------------------------- 1 | (component_declaration 2 | name: (component_identifier) @function) 3 | 4 | [ 5 | (tag_start) 6 | (tag_end) 7 | (self_closing_tag) 8 | (style_tag_start) 9 | (style_tag_end) 10 | (self_closing_style_tag) 11 | ] @tag 12 | 13 | (attribute 14 | name: (attribute_name) @tag.attribute) 15 | 16 | (attribute 17 | value: (quoted_attribute_value) @string) 18 | 19 | [ 20 | (element_text) 21 | (style_element_text) 22 | ] @string.special 23 | 24 | (css_identifier) @function 25 | 26 | (css_property 27 | name: (css_property_name) @property) 28 | 29 | (css_property 30 | value: (css_property_value) @string) 31 | 32 | [ 33 | (dynamic_class_attribute_value) 34 | ] @function.method 35 | 36 | (component_import 37 | name: (component_identifier) @function) 38 | 39 | (component_render) @function.call 40 | 41 | (element_comment) @comment 42 | 43 | "@" @operator 44 | 45 | [ 46 | "templ" 47 | "css" 48 | "script" 49 | ] @keyword 50 | -------------------------------------------------------------------------------- /languages/templ/highlights_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/0f584cb353afe0483d8a044619b7dabcfcd10e40/crates/languages/src/go/highlights.scm 2 | 3 | (type_identifier) @type 4 | (field_identifier) @variable.member 5 | 6 | (keyed_element 7 | . 8 | (literal_element 9 | (identifier) @variable.member)) 10 | 11 | (call_expression 12 | function: (identifier) @function) 13 | 14 | (call_expression 15 | function: (selector_expression 16 | field: (field_identifier) @function.method)) 17 | 18 | (function_declaration 19 | name: (identifier) @function) 20 | 21 | (method_declaration 22 | name: (field_identifier) @function.method) 23 | 24 | [ 25 | "(" 26 | ")" 27 | "{" 28 | "}" 29 | "[" 30 | "]" 31 | ] @punctuation.bracket 32 | 33 | [ 34 | "--" 35 | "-" 36 | "-=" 37 | ":=" 38 | "!" 39 | "!=" 40 | "..." 41 | "*" 42 | "*" 43 | "*=" 44 | "/" 45 | "/=" 46 | "&" 47 | "&&" 48 | "&=" 49 | "%" 50 | "%=" 51 | "^" 52 | "^=" 53 | "+" 54 | "++" 55 | "+=" 56 | "<-" 57 | "<" 58 | "<<" 59 | "<<=" 60 | "<=" 61 | "=" 62 | "==" 63 | ">" 64 | ">=" 65 | ">>" 66 | ">>=" 67 | "|" 68 | "|=" 69 | "||" 70 | "~" 71 | ] @operator 72 | 73 | [ 74 | "break" 75 | "case" 76 | "chan" 77 | "const" 78 | "continue" 79 | "default" 80 | "defer" 81 | "else" 82 | "fallthrough" 83 | "for" 84 | "func" 85 | "go" 86 | "goto" 87 | "if" 88 | "import" 89 | "interface" 90 | "map" 91 | "package" 92 | "range" 93 | "return" 94 | "select" 95 | "struct" 96 | "switch" 97 | "type" 98 | "var" 99 | ] @keyword 100 | 101 | [ 102 | (interpreted_string_literal) 103 | (raw_string_literal) 104 | (rune_literal) 105 | ] @string 106 | 107 | (escape_sequence) @escape 108 | 109 | [ 110 | (int_literal) 111 | (float_literal) 112 | (imaginary_literal) 113 | ] @number 114 | 115 | [ 116 | (true) 117 | (false) 118 | (nil) 119 | (iota) 120 | ] @constant.builtin 121 | 122 | (comment) @comment 123 | -------------------------------------------------------------------------------- /languages/templ/indents.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/49c53bc0ec85c38f579efbbcda9a1a84fb54e51f/extensions/html/languages/html/indents.scm 2 | 3 | (tag_start ">" @end) @indent 4 | (self_closing_tag "/>" @end) @indent 5 | 6 | (element 7 | (tag_start) @start 8 | (tag_end)? @end) @indent 9 | -------------------------------------------------------------------------------- /languages/templ/indents_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/0f584cb353afe0483d8a044619b7dabcfcd10e40/crates/languages/src/go/indents.scm 2 | 3 | [ 4 | (assignment_statement) 5 | (call_expression) 6 | (selector_expression) 7 | ] @indent 8 | 9 | (_ "[" "]" @end) @indent 10 | (_ "{" "}" @end) @indent 11 | (_ "(" ")" @end) @indent 12 | -------------------------------------------------------------------------------- /languages/templ/injections.scm: -------------------------------------------------------------------------------- 1 | ((script_block_text) @content 2 | (#set! "language" "JavaScript")) 3 | 4 | ((script_element_text) @content 5 | (#set! "language" "JavaScript")) 6 | 7 | ((style_element_text) @content 8 | (#set! "language" "CSS")) 9 | 10 | ((expression) @content 11 | (#set! "language" "go")) 12 | 13 | ((rawgo_block) @content 14 | (#set! "language" "go")) 15 | -------------------------------------------------------------------------------- /languages/templ/injections_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/9d681bda8d607384703f14d9e6549165e5cb9ae4/crates/languages/src/go/injections.scm 2 | 3 | ; Refer to https://github.com/nvim-treesitter/nvim-treesitter/blob/master/queries/go/injections.scm#L4C1-L16C41 4 | (call_expression 5 | (selector_expression) @_function 6 | (#any-of? @_function 7 | "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" 8 | "regexp.MustCompile" "regexp.MustCompilePOSIX") 9 | (argument_list 10 | . 11 | [ 12 | (raw_string_literal) 13 | (interpreted_string_literal) 14 | ] @content 15 | (#set! "language" "regex"))) 16 | -------------------------------------------------------------------------------- /languages/templ/outline.scm: -------------------------------------------------------------------------------- 1 | (component_declaration 2 | "templ" @context 3 | name: (component_identifier) @name 4 | ) @item 5 | 6 | (component_import 7 | "@" @context 8 | (package_identifier) @name 9 | "." @name 10 | (component_identifier) @name 11 | ) @item 12 | 13 | (css_declaration 14 | "css" @context 15 | name: (css_identifier) @name 16 | ) @item 17 | 18 | (script_declaration 19 | "script" @context 20 | name: (script_identifier) @name 21 | ) @item 22 | 23 | (style_element 24 | [ 25 | (style_tag_start 26 | "style" @context ) 27 | (self_closing_style_tag 28 | "style" @context ) 29 | ] 30 | ) @item 31 | 32 | (script_element 33 | name: "script" @context 34 | ) @item 35 | 36 | (element 37 | [ 38 | (tag_start 39 | "<" 40 | name: (element_identifier) @name) 41 | (self_closing_tag 42 | "<" 43 | name: (element_identifier) @name) 44 | ] 45 | ) @item 46 | -------------------------------------------------------------------------------- /languages/templ/outline_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/2db2b636f24cce2066b722fd3167898e53b5296f/crates/languages/src/go/outline.scm 2 | 3 | (comment) @annotation 4 | (type_declaration 5 | "type" @context 6 | (type_spec 7 | name: (_) @name)) @item 8 | 9 | (function_declaration 10 | "func" @context 11 | name: (identifier) @name 12 | parameters: (parameter_list 13 | "(" 14 | ")")) @item 15 | 16 | (method_declaration 17 | "func" @context 18 | receiver: (parameter_list 19 | "(" @context 20 | (parameter_declaration 21 | name: (_) @name 22 | type: (_) @context) 23 | ")" @context) 24 | name: (field_identifier) @name 25 | parameters: (parameter_list 26 | "(" 27 | ")")) @item 28 | 29 | (const_declaration 30 | "const" @context 31 | (const_spec 32 | name: (identifier) @name) @item) 33 | 34 | (source_file 35 | (var_declaration 36 | "var" @context 37 | (var_spec 38 | name: (identifier) @name) @item)) 39 | 40 | (method_elem 41 | name: (_) @name 42 | parameters: (parameter_list 43 | "(" @context 44 | ")" @context)) @item 45 | 46 | (field_declaration 47 | name: (_) @name) @item 48 | -------------------------------------------------------------------------------- /languages/templ/overrides_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/258cf6c74684e662a52a3671b4550bad0c91e6d0/crates/languages/src/go/overrides.scm 2 | 3 | (comment) @comment.inclusive 4 | [ 5 | (interpreted_string_literal) 6 | (raw_string_literal) 7 | (rune_literal) 8 | (attribute_value) 9 | (quoted_attribute_value) 10 | ] @string 11 | -------------------------------------------------------------------------------- /languages/templ/structure.scm: -------------------------------------------------------------------------------- 1 | (component_declaration 2 | "templ" @structure.anchor 3 | (parameter_list 4 | "(" @structure.open 5 | ")" @structure.close 6 | ) 7 | ) -------------------------------------------------------------------------------- /languages/templ/textobjects.scm: -------------------------------------------------------------------------------- 1 | (component_declaration 2 | (component_block 3 | "{" 4 | (_)* @function.inside 5 | "}")) @function.around 6 | 7 | (component_declaration 8 | (component_block 9 | "{" 10 | (_)* @class.inside 11 | "}")) @class.around 12 | 13 | ; TODO: CSS blocks require the _css_block rule but it is marked private in templ/tree-sitter, not sure why. 14 | 15 | (script_declaration 16 | (script_block 17 | "{" 18 | (_)* @class.inside 19 | "}")) @class.around 20 | 21 | (script_declaration 22 | (script_block 23 | "{" 24 | (_)* @function.inside 25 | "}")) @function.around 26 | -------------------------------------------------------------------------------- /languages/templ/textobjects_go.scm: -------------------------------------------------------------------------------- 1 | ; From https://github.com/zed-industries/zed/blob/75c9dc179bb3db89915666baf56e5362761cd97c/crates/languages/src/go/textobjects.scm 2 | 3 | (function_declaration 4 | body: (_ 5 | "{" 6 | (_)* @function.inside 7 | "}")) @function.around 8 | 9 | (method_declaration 10 | body: (_ 11 | "{" 12 | (_)* @function.inside 13 | "}")) @function.around 14 | 15 | (type_declaration 16 | (type_spec (struct_type (field_declaration_list ( 17 | "{" 18 | (_)* @class.inside 19 | "}")?)))) @class.around 20 | 21 | (type_declaration 22 | (type_spec (interface_type 23 | (_)* @class.inside))) @class.around 24 | 25 | (type_declaration) @class.around 26 | 27 | (comment)+ @comment.around 28 | -------------------------------------------------------------------------------- /src/templ.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use zed::settings::LspSettings; 3 | use zed_extension_api::{self as zed, LanguageServerId, Result}; 4 | 5 | struct TemplExtension { 6 | cached_binary_path: Option, 7 | } 8 | 9 | impl TemplExtension { 10 | fn language_server_binary_path( 11 | &mut self, 12 | language_server_id: &LanguageServerId, 13 | worktree: &zed::Worktree, 14 | ) -> Result { 15 | if let Some(path) = &self.cached_binary_path { 16 | if fs::metadata(path).map_or(false, |stat| stat.is_file()) { 17 | return Ok(path.clone()); 18 | } 19 | } 20 | 21 | if let Some(path) = worktree.which("templ") { 22 | self.cached_binary_path = Some(path.clone()); 23 | return Ok(path); 24 | } 25 | 26 | zed::set_language_server_installation_status( 27 | &language_server_id, 28 | &zed::LanguageServerInstallationStatus::CheckingForUpdate, 29 | ); 30 | let release = zed::latest_github_release( 31 | "a-h/templ", 32 | zed::GithubReleaseOptions { 33 | require_assets: true, 34 | pre_release: false, 35 | }, 36 | )?; 37 | 38 | let (platform, arch) = zed::current_platform(); 39 | let asset_name = format!( 40 | "templ_{os}_{arch}.tar.gz", 41 | os = match platform { 42 | zed::Os::Mac => "Darwin", 43 | zed::Os::Linux => "Linux", 44 | zed::Os::Windows => "Windows", 45 | }, 46 | arch = match arch { 47 | zed::Architecture::Aarch64 => "arm64", 48 | zed::Architecture::X86 => "i386", 49 | zed::Architecture::X8664 => "x86_64", 50 | } 51 | ); 52 | 53 | let asset = release 54 | .assets 55 | .iter() 56 | .find(|asset| asset.name == asset_name) 57 | .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; 58 | 59 | let version_dir = format!("templ-{}", release.version); 60 | let binary_path = format!("{version_dir}/templ"); 61 | 62 | if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { 63 | zed::set_language_server_installation_status( 64 | &language_server_id, 65 | &zed::LanguageServerInstallationStatus::Downloading, 66 | ); 67 | 68 | zed::download_file( 69 | &asset.download_url, 70 | &version_dir, 71 | match platform { 72 | zed::Os::Mac | zed::Os::Linux | zed::Os::Windows => { 73 | zed::DownloadedFileType::GzipTar 74 | } 75 | }, 76 | ) 77 | .map_err(|e| format!("failed to download file: {e}"))?; 78 | 79 | let entries = 80 | fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; 81 | for entry in entries { 82 | let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; 83 | if entry.file_name().to_str() != Some(&version_dir) { 84 | fs::remove_dir_all(&entry.path()).ok(); 85 | } 86 | } 87 | } 88 | 89 | self.cached_binary_path = Some(binary_path.clone()); 90 | Ok(binary_path) 91 | } 92 | } 93 | 94 | impl zed::Extension for TemplExtension { 95 | fn new() -> Self { 96 | Self { 97 | cached_binary_path: None, 98 | } 99 | } 100 | 101 | fn language_server_command( 102 | &mut self, 103 | language_server_id: &LanguageServerId, 104 | worktree: &zed::Worktree, 105 | ) -> Result { 106 | Ok(zed::Command { 107 | command: self.language_server_binary_path(language_server_id, worktree)?, 108 | // Pass the log argument to the language server to debug it 109 | // args: vec![ 110 | // "lsp".to_string(), 111 | // "-log".to_string(), 112 | // "/tmp/templ.txt".to_string(), 113 | // ], 114 | args: vec!["lsp".to_string()], 115 | env: Default::default(), 116 | }) 117 | } 118 | 119 | fn language_server_workspace_configuration( 120 | &mut self, 121 | server_id: &LanguageServerId, 122 | worktree: &zed::Worktree, 123 | ) -> Result> { 124 | let settings = LspSettings::for_worktree(server_id.as_ref(), worktree) 125 | .ok() 126 | .and_then(|lsp_settings| lsp_settings.settings.clone()) 127 | .unwrap_or_default(); 128 | Ok(Some(settings)) 129 | } 130 | } 131 | 132 | zed::register_extension!(TemplExtension); 133 | --------------------------------------------------------------------------------