├── .gitignore ├── .zed └── settings.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── extension.toml ├── languages └── dockerfile │ ├── config.toml │ ├── highlights.scm │ └── injections.scm └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | grammars 3 | *.wasm 4 | -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "languages": { 3 | "TOML": { 4 | "preferred_line_length": 100, 5 | "formatter": "prettier" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.81" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.5.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 16 | 17 | [[package]] 18 | name = "equivalent" 19 | version = "1.0.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 22 | 23 | [[package]] 24 | name = "hashbrown" 25 | version = "0.14.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 28 | 29 | [[package]] 30 | name = "heck" 31 | version = "0.4.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 34 | dependencies = [ 35 | "unicode-segmentation", 36 | ] 37 | 38 | [[package]] 39 | name = "id-arena" 40 | version = "2.2.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 43 | 44 | [[package]] 45 | name = "indexmap" 46 | version = "2.2.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 49 | dependencies = [ 50 | "equivalent", 51 | "hashbrown", 52 | "serde", 53 | ] 54 | 55 | [[package]] 56 | name = "itoa" 57 | version = "1.0.11" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 60 | 61 | [[package]] 62 | name = "leb128" 63 | version = "0.2.5" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 66 | 67 | [[package]] 68 | name = "log" 69 | version = "0.4.21" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 72 | 73 | [[package]] 74 | name = "proc-macro2" 75 | version = "1.0.79" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 78 | dependencies = [ 79 | "unicode-ident", 80 | ] 81 | 82 | [[package]] 83 | name = "quote" 84 | version = "1.0.35" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 87 | dependencies = [ 88 | "proc-macro2", 89 | ] 90 | 91 | [[package]] 92 | name = "ryu" 93 | version = "1.0.17" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 96 | 97 | [[package]] 98 | name = "semver" 99 | version = "1.0.22" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 102 | 103 | [[package]] 104 | name = "serde" 105 | version = "1.0.197" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 108 | dependencies = [ 109 | "serde_derive", 110 | ] 111 | 112 | [[package]] 113 | name = "serde_derive" 114 | version = "1.0.197" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 117 | dependencies = [ 118 | "proc-macro2", 119 | "quote", 120 | "syn", 121 | ] 122 | 123 | [[package]] 124 | name = "serde_json" 125 | version = "1.0.115" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" 128 | dependencies = [ 129 | "itoa", 130 | "ryu", 131 | "serde", 132 | ] 133 | 134 | [[package]] 135 | name = "smallvec" 136 | version = "1.13.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 139 | 140 | [[package]] 141 | name = "spdx" 142 | version = "0.10.4" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" 145 | dependencies = [ 146 | "smallvec", 147 | ] 148 | 149 | [[package]] 150 | name = "syn" 151 | version = "2.0.55" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" 154 | dependencies = [ 155 | "proc-macro2", 156 | "quote", 157 | "unicode-ident", 158 | ] 159 | 160 | [[package]] 161 | name = "unicode-ident" 162 | version = "1.0.12" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 165 | 166 | [[package]] 167 | name = "unicode-segmentation" 168 | version = "1.11.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 171 | 172 | [[package]] 173 | name = "unicode-xid" 174 | version = "0.2.4" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 177 | 178 | [[package]] 179 | name = "wasm-encoder" 180 | version = "0.201.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" 183 | dependencies = [ 184 | "leb128", 185 | ] 186 | 187 | [[package]] 188 | name = "wasm-metadata" 189 | version = "0.201.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" 192 | dependencies = [ 193 | "anyhow", 194 | "indexmap", 195 | "serde", 196 | "serde_derive", 197 | "serde_json", 198 | "spdx", 199 | "wasm-encoder", 200 | "wasmparser", 201 | ] 202 | 203 | [[package]] 204 | name = "wasmparser" 205 | version = "0.201.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" 208 | dependencies = [ 209 | "bitflags", 210 | "indexmap", 211 | "semver", 212 | ] 213 | 214 | [[package]] 215 | name = "wit-bindgen" 216 | version = "0.22.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27" 219 | dependencies = [ 220 | "bitflags", 221 | "wit-bindgen-rt", 222 | "wit-bindgen-rust-macro", 223 | ] 224 | 225 | [[package]] 226 | name = "wit-bindgen-core" 227 | version = "0.22.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "e85e72719ffbccf279359ad071497e47eb0675fe22106dea4ed2d8a7fcb60ba4" 230 | dependencies = [ 231 | "anyhow", 232 | "wit-parser", 233 | ] 234 | 235 | [[package]] 236 | name = "wit-bindgen-rt" 237 | version = "0.22.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64" 240 | 241 | [[package]] 242 | name = "wit-bindgen-rust" 243 | version = "0.22.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" 246 | dependencies = [ 247 | "anyhow", 248 | "heck", 249 | "indexmap", 250 | "wasm-metadata", 251 | "wit-bindgen-core", 252 | "wit-component", 253 | ] 254 | 255 | [[package]] 256 | name = "wit-bindgen-rust-macro" 257 | version = "0.22.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "d376d3ae5850526dfd00d937faea0d81a06fa18f7ac1e26f386d760f241a8f4b" 260 | dependencies = [ 261 | "anyhow", 262 | "proc-macro2", 263 | "quote", 264 | "syn", 265 | "wit-bindgen-core", 266 | "wit-bindgen-rust", 267 | ] 268 | 269 | [[package]] 270 | name = "wit-component" 271 | version = "0.201.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" 274 | dependencies = [ 275 | "anyhow", 276 | "bitflags", 277 | "indexmap", 278 | "log", 279 | "serde", 280 | "serde_derive", 281 | "serde_json", 282 | "wasm-encoder", 283 | "wasm-metadata", 284 | "wasmparser", 285 | "wit-parser", 286 | ] 287 | 288 | [[package]] 289 | name = "wit-parser" 290 | version = "0.201.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" 293 | dependencies = [ 294 | "anyhow", 295 | "id-arena", 296 | "indexmap", 297 | "log", 298 | "semver", 299 | "serde", 300 | "serde_derive", 301 | "serde_json", 302 | "unicode-xid", 303 | "wasmparser", 304 | ] 305 | 306 | [[package]] 307 | name = "zed_dockerfile" 308 | version = "0.0.2" 309 | dependencies = [ 310 | "zed_extension_api", 311 | ] 312 | 313 | [[package]] 314 | name = "zed_extension_api" 315 | version = "0.0.4" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "d5c51cad4152bb5eb35b20dccdcbfb36f48d8952a2ed2d3e25b70361007d953b" 318 | dependencies = [ 319 | "wit-bindgen", 320 | ] 321 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zed_dockerfile" 3 | version = "0.0.2" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | zed_extension_api = "0.0.4" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dockerfile Zed Extension 2 | 3 | - Tree Sitter: [tree-sitter-dockerfile](https://github.com/camdencheek/tree-sitter-dockerfile) 4 | - Language Server: [dockerfile-language-server](https://github.com/rcjsuen/dockerfile-language-server) 5 | 6 | ## Configuration 7 | 8 | To support matching filenames other than `Dockerfile` you can add [`file_types`](https://zed.dev/docs/configuring-zed#file-types) to your Zed project or user settings: 9 | 10 | ```json 11 | { 12 | "file_types": { 13 | "Dockerfile": [ "Dockerfile.*" ] 14 | } 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /extension.toml: -------------------------------------------------------------------------------- 1 | id = "dockerfile" 2 | name = "Dockerfile" 3 | description = "Dockerfile support." 4 | version = "0.0.5" 5 | schema_version = 1 6 | authors = ["d1y ", "joshmeads "] 7 | repository = "https://github.com/zed-extensions/dockerfile" 8 | 9 | [language_servers.dockerfile-language-server] 10 | name = "Dockerfile Language Server" 11 | language = "Dockerfile" 12 | 13 | [grammars.dockerfile] 14 | repository = "https://github.com/camdencheek/tree-sitter-dockerfile" 15 | commit = "868e44ce378deb68aac902a9db68ff82d2299dd0" 16 | -------------------------------------------------------------------------------- /languages/dockerfile/config.toml: -------------------------------------------------------------------------------- 1 | name = "Dockerfile" 2 | grammar = "dockerfile" 3 | path_suffixes = ["Dockerfile", "Containerfile", "dockerfile"] 4 | line_comments = ["# "] 5 | brackets = [ 6 | { start = "{", end = "}", close = true, newline = true }, 7 | { start = "[", end = "]", close = true, newline = true }, 8 | { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, 9 | ] 10 | -------------------------------------------------------------------------------- /languages/dockerfile/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Dockerfile instructions set taken from: 2 | ; https://docs.docker.com/engine/reference/builder/#overview 3 | ; https://github.com/camdencheek/tree-sitter-dockerfile/blob/main/queries/highlights.scm 4 | [ 5 | "FROM" 6 | "AS" 7 | "RUN" 8 | "CMD" 9 | "LABEL" 10 | "EXPOSE" 11 | "ENV" 12 | "ADD" 13 | "COPY" 14 | "ENTRYPOINT" 15 | "VOLUME" 16 | "USER" 17 | "WORKDIR" 18 | "ARG" 19 | "ONBUILD" 20 | "STOPSIGNAL" 21 | "HEALTHCHECK" 22 | "SHELL" 23 | "MAINTAINER" 24 | "CROSS_BUILD" 25 | (heredoc_marker) 26 | (heredoc_end) 27 | ] @keyword 28 | 29 | [ 30 | ":" 31 | "@" 32 | ] @operator 33 | 34 | (comment) @comment 35 | 36 | 37 | (image_spec 38 | (image_tag 39 | ":" @punctuation.special) 40 | (image_digest 41 | "@" @punctuation.special)) 42 | 43 | [ 44 | (double_quoted_string) 45 | (single_quoted_string) 46 | (json_string) 47 | (heredoc_line) 48 | ] @string 49 | 50 | (expansion 51 | [ 52 | "$" 53 | "{" 54 | "}" 55 | ] @punctuation.special 56 | ) @none 57 | 58 | ((variable) @constant 59 | (#match? @constant "^[A-Z][A-Z_0-9]*$")) 60 | -------------------------------------------------------------------------------- /languages/dockerfile/injections.scm: -------------------------------------------------------------------------------- 1 | ; We need impl this 2 | ; ((comment) @injection.content 3 | ; (#set! injection.language "comment")) 4 | 5 | ((shell_command) @content 6 | (#set! "language" "bash")) 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | use zed_extension_api::{self as zed, Result}; 3 | 4 | const SERVER_PATH: &str = "node_modules/dockerfile-language-server-nodejs/bin/docker-langserver"; 5 | const PACKAGE_NAME: &str = "dockerfile-language-server-nodejs"; 6 | 7 | struct DockerfileExtension { 8 | did_find_server: bool, 9 | } 10 | 11 | impl DockerfileExtension { 12 | fn server_exists(&self) -> bool { 13 | fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file()) 14 | } 15 | 16 | fn server_script_path(&mut self, config: zed::LanguageServerConfig) -> Result { 17 | let server_exists = self.server_exists(); 18 | if self.did_find_server && server_exists { 19 | return Ok(SERVER_PATH.to_string()); 20 | } 21 | 22 | zed::set_language_server_installation_status( 23 | &config.name, 24 | &zed::LanguageServerInstallationStatus::CheckingForUpdate, 25 | ); 26 | let version = zed::npm_package_latest_version(PACKAGE_NAME)?; 27 | 28 | if !server_exists 29 | || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version) 30 | { 31 | zed::set_language_server_installation_status( 32 | &config.name, 33 | &zed::LanguageServerInstallationStatus::Downloading, 34 | ); 35 | let result = zed::npm_install_package(PACKAGE_NAME, &version); 36 | match result { 37 | Ok(()) => { 38 | if !self.server_exists() { 39 | Err(format!( 40 | "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'", 41 | ))?; 42 | } 43 | } 44 | Err(error) => { 45 | if !self.server_exists() { 46 | Err(error)?; 47 | } 48 | } 49 | } 50 | } 51 | 52 | self.did_find_server = true; 53 | Ok(SERVER_PATH.to_string()) 54 | } 55 | } 56 | 57 | impl zed::Extension for DockerfileExtension { 58 | fn new() -> Self { 59 | Self { 60 | did_find_server: false, 61 | } 62 | } 63 | 64 | fn language_server_command( 65 | &mut self, 66 | config: zed::LanguageServerConfig, 67 | _worktree: &zed::Worktree, 68 | ) -> Result { 69 | let server_path = self.server_script_path(config)?; 70 | Ok(zed::Command { 71 | command: zed::node_binary_path()?, 72 | args: vec![ 73 | zed_ext::sanitize_windows_path(env::current_dir().unwrap()) 74 | .join(&server_path) 75 | .to_string_lossy() 76 | .to_string(), 77 | "--stdio".to_string(), 78 | ], 79 | env: Default::default(), 80 | }) 81 | } 82 | } 83 | 84 | zed::register_extension!(DockerfileExtension); 85 | 86 | mod zed_ext { 87 | /// Sanitizes the given path to remove the leading `/` on Windows. 88 | /// 89 | /// On macOS and Linux this is a no-op. 90 | /// 91 | /// This is a workaround for https://github.com/bytecodealliance/wasmtime/issues/10415. 92 | pub fn sanitize_windows_path(path: std::path::PathBuf) -> std::path::PathBuf { 93 | use zed_extension_api::{current_platform, Os}; 94 | 95 | let (os, _arch) = current_platform(); 96 | match os { 97 | Os::Mac | Os::Linux => path, 98 | Os::Windows => path 99 | .to_string_lossy() 100 | .to_string() 101 | .trim_start_matches('/') 102 | .into(), 103 | } 104 | } 105 | } 106 | --------------------------------------------------------------------------------