Detailed changes
@@ -0,0 +1,16 @@
+[package]
+name = "zed_glsl"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/glsl.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.1.0"
@@ -0,0 +1 @@
+../../LICENSE-APACHE
@@ -0,0 +1,15 @@
+id = "glsl"
+name = "GLSL"
+description = "GLSL support."
+version = "0.1.0"
+schema_version = 1
+authors = ["Mikayla Maki <mikayla@zed.dev>"]
+repository = "https://github.com/zed-industries/zed"
+
+[language_servers.glsl_analyzer]
+name = "GLSL Analyzer LSP"
+language = "GLSL"
+
+[grammars.glsl]
+repository = "https://github.com/theHamsta/tree-sitter-glsl"
+commit = "31064ce53385150f894a6c72d61b94076adf640a"
@@ -0,0 +1,20 @@
+name = "GLSL"
+grammar = "glsl"
+path_suffixes = [
+ # Traditional rasterization pipeline shaders
+ "vert", "frag", "tesc", "tese", "geom",
+ # Compute shaders
+ "comp",
+ # Ray tracing pipeline shaders
+ "rgen", "rint", "rahit", "rchit", "rmiss", "rcall",
+ # Other
+ "glsl"
+ ]
+first_line_pattern = '^#version \d+'
+line_comments = ["// "]
+block_comment = { start = "/* ", prefix = "* ", end = "*/", tab_size = 1 }
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+]
@@ -0,0 +1,117 @@
+"break" @keyword
+"case" @keyword
+"const" @keyword
+"continue" @keyword
+"default" @keyword
+"do" @keyword
+"else" @keyword
+"enum" @keyword
+"extern" @keyword
+"for" @keyword
+"if" @keyword
+"inline" @keyword
+"return" @keyword
+"sizeof" @keyword
+"static" @keyword
+"struct" @keyword
+"switch" @keyword
+"typedef" @keyword
+"union" @keyword
+"volatile" @keyword
+"while" @keyword
+
+"#define" @keyword
+"#elif" @keyword
+"#else" @keyword
+"#endif" @keyword
+"#if" @keyword
+"#ifdef" @keyword
+"#ifndef" @keyword
+"#include" @keyword
+(preproc_directive) @keyword
+
+"--" @operator
+"-" @operator
+"-=" @operator
+"->" @operator
+"=" @operator
+"!=" @operator
+"*" @operator
+"&" @operator
+"&&" @operator
+"+" @operator
+"++" @operator
+"+=" @operator
+"<" @operator
+"==" @operator
+">" @operator
+"||" @operator
+
+"." @delimiter
+";" @delimiter
+
+(string_literal) @string
+(system_lib_string) @string
+
+(null) @constant
+(number_literal) @number
+(char_literal) @number
+
+(identifier) @variable
+
+(field_identifier) @property
+(statement_identifier) @label
+(type_identifier) @type
+(primitive_type) @type
+(sized_type_specifier) @type
+
+(call_expression
+ function: (identifier) @function)
+(call_expression
+ function: (field_expression
+ field: (field_identifier) @function))
+(function_declarator
+ declarator: (identifier) @function)
+(preproc_function_def
+ name: (identifier) @function.special)
+
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]*$"))
+
+(comment) @comment
+
+[
+ "in"
+ "out"
+ "inout"
+ "uniform"
+ "shared"
+ "layout"
+ "attribute"
+ "varying"
+ "buffer"
+ "coherent"
+ "readonly"
+ "writeonly"
+ "precision"
+ "highp"
+ "mediump"
+ "lowp"
+ "centroid"
+ "sample"
+ "patch"
+ "smooth"
+ "flat"
+ "noperspective"
+ "invariant"
+ "precise"
+] @type.qualifier
+
+"subroutine" @keyword.function
+
+(extension_storage_class) @storageclass
+
+(
+ (identifier) @variable.builtin
+ (#match? @variable.builtin "^gl_")
+)
@@ -0,0 +1,131 @@
+use std::fs;
+use zed::settings::LspSettings;
+use zed_extension_api::{self as zed, LanguageServerId, Result, serde_json};
+
+struct GlslExtension {
+ cached_binary_path: Option<String>,
+}
+
+impl GlslExtension {
+ fn language_server_binary_path(
+ &mut self,
+ language_server_id: &LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<String> {
+ if let Some(path) = worktree.which("glsl_analyzer") {
+ return Ok(path);
+ }
+
+ if let Some(path) = &self.cached_binary_path
+ && fs::metadata(path).is_ok_and(|stat| stat.is_file())
+ {
+ return Ok(path.clone());
+ }
+
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::CheckingForUpdate,
+ );
+ let release = zed::latest_github_release(
+ "nolanderc/glsl_analyzer",
+ zed::GithubReleaseOptions {
+ require_assets: true,
+ pre_release: false,
+ },
+ )?;
+
+ let (platform, arch) = zed::current_platform();
+ let asset_name = format!(
+ "{arch}-{os}.zip",
+ arch = match arch {
+ zed::Architecture::Aarch64 => "aarch64",
+ zed::Architecture::X86 => "x86",
+ zed::Architecture::X8664 => "x86_64",
+ },
+ os = match platform {
+ zed::Os::Mac => "macos",
+ zed::Os::Linux => "linux-musl",
+ zed::Os::Windows => "windows",
+ }
+ );
+
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
+
+ let version_dir = format!("glsl_analyzer-{}", release.version);
+ fs::create_dir_all(&version_dir)
+ .map_err(|err| format!("failed to create directory '{version_dir}': {err}"))?;
+ let binary_path = format!("{version_dir}/bin/glsl_analyzer");
+
+ if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) {
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::Downloading,
+ );
+
+ zed::download_file(
+ &asset.download_url,
+ &version_dir,
+ match platform {
+ zed::Os::Mac | zed::Os::Linux => zed::DownloadedFileType::Zip,
+ zed::Os::Windows => zed::DownloadedFileType::Zip,
+ },
+ )
+ .map_err(|e| format!("failed to download file: {e}"))?;
+
+ zed::make_file_executable(&binary_path)?;
+
+ let entries =
+ fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
+ for entry in entries {
+ let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
+ if entry.file_name().to_str() != Some(&version_dir) {
+ fs::remove_dir_all(entry.path()).ok();
+ }
+ }
+ }
+
+ self.cached_binary_path = Some(binary_path.clone());
+ Ok(binary_path)
+ }
+}
+
+impl zed::Extension for GlslExtension {
+ fn new() -> Self {
+ Self {
+ cached_binary_path: None,
+ }
+ }
+
+ fn language_server_command(
+ &mut self,
+ language_server_id: &zed::LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ Ok(zed::Command {
+ command: self.language_server_binary_path(language_server_id, worktree)?,
+ args: vec![],
+ env: Default::default(),
+ })
+ }
+
+ fn language_server_workspace_configuration(
+ &mut self,
+ _language_server_id: &zed::LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<Option<serde_json::Value>> {
+ let settings = LspSettings::for_worktree("glsl_analyzer", worktree)
+ .ok()
+ .and_then(|lsp_settings| lsp_settings.settings)
+ .unwrap_or_default();
+
+ Ok(Some(serde_json::json!({
+ "glsl_analyzer": settings
+ })))
+ }
+}
+
+zed::register_extension!(GlslExtension);
@@ -0,0 +1,16 @@
+[package]
+name = "zed_html"
+version = "0.3.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/html.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.7.0"
@@ -0,0 +1 @@
+../../LICENSE-APACHE
@@ -0,0 +1,19 @@
+id = "html"
+name = "HTML"
+description = "HTML support."
+version = "0.3.0"
+schema_version = 1
+authors = ["Isaac Clayton <slightknack@gmail.com>"]
+repository = "https://github.com/zed-industries/zed"
+
+[language_servers.vscode-html-language-server]
+name = "vscode-html-language-server"
+language = "HTML"
+
+[language_servers.vscode-html-language-server.language_ids]
+"HTML" = "html"
+"CSS" = "css"
+
+[grammars.html]
+repository = "https://github.com/tree-sitter/tree-sitter-html"
+commit = "bfa075d83c6b97cd48440b3829ab8d24a2319809"
@@ -0,0 +1,5 @@
+("<" @open "/>" @close)
+("</" @open ">" @close)
+("<" @open ">" @close)
+(("\"" @open "\"" @close) (#set! rainbow.exclude))
+((element (start_tag) @open (end_tag) @close) (#set! newline.only) (#set! rainbow.exclude))
@@ -0,0 +1,19 @@
+name = "HTML"
+grammar = "html"
+path_suffixes = ["html", "htm", "shtml"]
+autoclose_before = ">})"
+block_comment = { start = "<!--", prefix = "", end = "-->", tab_size = 0 }
+wrap_characters = { start_prefix = "<", start_suffix = ">", end_prefix = "</", end_suffix = ">" }
+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 = ["comment", "string"] },
+ { start = "<", end = ">", close = false, newline = true, not_in = ["comment", "string"] },
+ { start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
+]
+completion_query_characters = ["-"]
+prettier_parser_name = "html"
+
+[overrides.default]
+linked_edit_characters = ["-"]
@@ -0,0 +1,19 @@
+(tag_name) @tag
+(doctype) @tag.doctype
+(attribute_name) @attribute
+[
+ "\""
+ "'"
+ (attribute_value)
+] @string
+(comment) @comment
+
+"=" @punctuation.delimiter.html
+
+[
+ "<"
+ ">"
+ "<!"
+ "</"
+ "/>"
+] @punctuation.bracket.html
@@ -0,0 +1,6 @@
+(start_tag ">" @end) @indent
+(self_closing_tag "/>" @end) @indent
+
+(element
+ (start_tag) @start
+ (end_tag)? @end) @indent
@@ -0,0 +1,21 @@
+((comment) @injection.content
+ (#set! injection.language "comment")
+)
+
+(script_element
+ (raw_text) @injection.content
+ (#set! injection.language "javascript"))
+
+(style_element
+ (raw_text) @injection.content
+ (#set! injection.language "css"))
+
+(attribute
+ (attribute_name) @_attribute_name (#match? @_attribute_name "^style$")
+ (quoted_attribute_value (attribute_value) @injection.content)
+ (#set! injection.language "css"))
+
+(attribute
+ (attribute_name) @_attribute_name (#match? @_attribute_name "^on[a-z]+$")
+ (quoted_attribute_value (attribute_value) @injection.content)
+ (#set! injection.language "javascript"))
@@ -0,0 +1,5 @@
+(comment) @annotation
+
+(element
+ (start_tag
+ (tag_name) @name)) @item
@@ -0,0 +1,7 @@
+(comment) @comment
+(quoted_attribute_value) @string
+
+[
+ (start_tag)
+ (end_tag)
+] @default
@@ -0,0 +1,115 @@
+use std::{env, fs};
+use zed::settings::LspSettings;
+use zed_extension_api::{self as zed, LanguageServerId, Result, serde_json::json};
+
+const BINARY_NAME: &str = "vscode-html-language-server";
+const SERVER_PATH: &str =
+ "node_modules/@zed-industries/vscode-langservers-extracted/bin/vscode-html-language-server";
+const PACKAGE_NAME: &str = "@zed-industries/vscode-langservers-extracted";
+
+struct HtmlExtension {
+ cached_binary_path: Option<String>,
+}
+
+impl HtmlExtension {
+ fn server_exists(&self) -> bool {
+ fs::metadata(SERVER_PATH).is_ok_and(|stat| stat.is_file())
+ }
+
+ fn server_script_path(&mut self, language_server_id: &LanguageServerId) -> Result<String> {
+ let server_exists = self.server_exists();
+ if self.cached_binary_path.is_some() && server_exists {
+ return Ok(SERVER_PATH.to_string());
+ }
+
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::CheckingForUpdate,
+ );
+ let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
+
+ if !server_exists
+ || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
+ {
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::Downloading,
+ );
+ let result = zed::npm_install_package(PACKAGE_NAME, &version);
+ match result {
+ Ok(()) => {
+ if !self.server_exists() {
+ Err(format!(
+ "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
+ ))?;
+ }
+ }
+ Err(error) => {
+ if !self.server_exists() {
+ Err(error)?;
+ }
+ }
+ }
+ }
+ Ok(SERVER_PATH.to_string())
+ }
+}
+
+impl zed::Extension for HtmlExtension {
+ fn new() -> Self {
+ Self {
+ cached_binary_path: None,
+ }
+ }
+
+ fn language_server_command(
+ &mut self,
+ language_server_id: &LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ let server_path = if let Some(path) = worktree.which(BINARY_NAME) {
+ return Ok(zed::Command {
+ command: path,
+ args: vec!["--stdio".to_string()],
+ env: Default::default(),
+ });
+ } else {
+ let server_path = self.server_script_path(language_server_id)?;
+ env::current_dir()
+ .unwrap()
+ .join(&server_path)
+ .to_string_lossy()
+ .to_string()
+ };
+ self.cached_binary_path = Some(server_path.clone());
+
+ Ok(zed::Command {
+ command: zed::node_binary_path()?,
+ args: vec![server_path, "--stdio".to_string()],
+ env: Default::default(),
+ })
+ }
+
+ fn language_server_workspace_configuration(
+ &mut self,
+ server_id: &LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<Option<zed::serde_json::Value>> {
+ let settings = LspSettings::for_worktree(server_id.as_ref(), worktree)
+ .ok()
+ .and_then(|lsp_settings| lsp_settings.settings)
+ .unwrap_or_default();
+ Ok(Some(settings))
+ }
+
+ fn language_server_initialization_options(
+ &mut self,
+ _server_id: &LanguageServerId,
+ _worktree: &zed_extension_api::Worktree,
+ ) -> Result<Option<zed_extension_api::serde_json::Value>> {
+ let initialization_options = json!({"provideFormatter": true });
+ Ok(Some(initialization_options))
+ }
+}
+
+zed::register_extension!(HtmlExtension);
@@ -0,0 +1,16 @@
+[package]
+name = "zed_proto"
+version = "0.3.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/proto.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.7.0"
@@ -0,0 +1 @@
+../../LICENSE-APACHE
@@ -0,0 +1,24 @@
+id = "proto"
+name = "Proto"
+description = "Protocol Buffers support."
+version = "0.3.0"
+schema_version = 1
+authors = ["Zed Industries <support@zed.dev>"]
+repository = "https://github.com/zed-industries/zed"
+
+[grammars.proto]
+repository = "https://github.com/coder3101/tree-sitter-proto"
+commit = "a6caac94b5aa36b322b5b70040d5b67132f109d0"
+
+
+[language_servers.buf]
+name = "Buf"
+languages = ["Proto"]
+
+[language_servers.protobuf-language-server]
+name = "Protobuf Language Server"
+languages = ["Proto"]
+
+[language_servers.protols]
+name = "Protols"
+languages = ["Proto"]
@@ -0,0 +1,13 @@
+name = "Proto"
+grammar = "proto"
+path_suffixes = ["proto"]
+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 = ["comment", "string"] },
+ { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
+]
@@ -0,0 +1,61 @@
+[
+ "syntax"
+ "package"
+ "option"
+ "optional"
+ "import"
+ "service"
+ "rpc"
+ "returns"
+ "message"
+ "enum"
+ "oneof"
+ "repeated"
+ "reserved"
+ "to"
+] @keyword
+
+[
+ (key_type)
+ (type)
+ (message_name)
+ (enum_name)
+ (service_name)
+ (rpc_name)
+ (message_or_enum_type)
+] @type
+
+(enum_field
+ (identifier) @constant)
+
+[
+ (string)
+ "\"proto3\""
+] @string
+
+(int_lit) @number
+
+[
+ (true)
+ (false)
+] @boolean
+
+(comment) @comment
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "<"
+ ">"
+] @punctuation.bracket
+
+[
+ ";"
+ ","
+] @punctuation.delimiter
+
+"=" @operator
@@ -0,0 +1,3 @@
+(_ "{" "}" @end) @indent
+(_ "[" "]" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,19 @@
+(message
+ "message" @context
+ (message_name
+ (identifier) @name)) @item
+
+(service
+ "service" @context
+ (service_name
+ (identifier) @name)) @item
+
+(rpc
+ "rpc" @context
+ (rpc_name
+ (identifier) @name)) @item
+
+(enum
+ "enum" @context
+ (enum_name
+ (identifier) @name)) @item
@@ -0,0 +1,18 @@
+(message (message_body
+ "{"
+ (_)* @class.inside
+ "}")) @class.around
+(enum (enum_body
+ "{"
+ (_)* @class.inside
+ "}")) @class.around
+(service
+ "service"
+ (_)
+ "{"
+ (_)* @class.inside
+ "}") @class.around
+
+(rpc) @function.around
+
+(comment)+ @comment.around
@@ -0,0 +1,8 @@
+mod buf;
+mod protobuf_language_server;
+mod protols;
+mod util;
+
+pub(crate) use buf::*;
+pub(crate) use protobuf_language_server::*;
+pub(crate) use protols::*;
@@ -0,0 +1,114 @@
+use std::fs;
+
+use zed_extension_api::{
+ self as zed, Architecture, DownloadedFileType, GithubReleaseOptions, Os, Result,
+ settings::LspSettings,
+};
+
+use crate::language_servers::util;
+
+pub(crate) struct BufLsp {
+ cached_binary_path: Option<String>,
+}
+
+impl BufLsp {
+ pub(crate) const SERVER_NAME: &str = "buf";
+
+ pub(crate) fn new() -> Self {
+ BufLsp {
+ cached_binary_path: None,
+ }
+ }
+
+ pub(crate) fn language_server_binary(
+ &mut self,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ let binary_settings = LspSettings::for_worktree(Self::SERVER_NAME, worktree)
+ .ok()
+ .and_then(|lsp_settings| lsp_settings.binary);
+
+ let args = binary_settings
+ .as_ref()
+ .and_then(|binary_settings| binary_settings.arguments.clone())
+ .unwrap_or_else(|| ["lsp", "serve"].map(ToOwned::to_owned).into());
+
+ if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ });
+ } else if let Some(path) = self.cached_binary_path.clone() {
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ });
+ } else if let Some(path) = worktree.which(Self::SERVER_NAME) {
+ self.cached_binary_path = Some(path.clone());
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ });
+ }
+
+ let latest_release = zed::latest_github_release(
+ "bufbuild/buf",
+ GithubReleaseOptions {
+ require_assets: true,
+ pre_release: false,
+ },
+ )?;
+
+ let (os, arch) = zed::current_platform();
+
+ let release_suffix = match (os, arch) {
+ (Os::Mac, Architecture::Aarch64) => "Darwin-arm64",
+ (Os::Mac, Architecture::X8664) => "Darwin-x86_64",
+ (Os::Linux, Architecture::Aarch64) => "Linux-aarch64",
+ (Os::Linux, Architecture::X8664) => "Linux-x86_64",
+ (Os::Windows, Architecture::Aarch64) => "Windows-arm64.exe",
+ (Os::Windows, Architecture::X8664) => "Windows-x86_64.exe",
+ _ => {
+ return Err("Platform and architecture not supported by buf CLI".to_string());
+ }
+ };
+
+ let release_name = format!("buf-{release_suffix}");
+
+ let version_dir = format!("{}-{}", Self::SERVER_NAME, latest_release.version);
+ fs::create_dir_all(&version_dir).map_err(|_| "Could not create directory")?;
+
+ let binary_path = format!("{version_dir}/buf");
+
+ let download_target = latest_release
+ .assets
+ .into_iter()
+ .find(|asset| asset.name == release_name)
+ .ok_or_else(|| {
+ format!(
+ "Could not find asset with name {} in buf CLI release",
+ &release_name
+ )
+ })?;
+
+ zed::download_file(
+ &download_target.download_url,
+ &binary_path,
+ DownloadedFileType::Uncompressed,
+ )?;
+ zed::make_file_executable(&binary_path)?;
+
+ util::remove_outdated_versions(Self::SERVER_NAME, &version_dir)?;
+
+ self.cached_binary_path = Some(binary_path.clone());
+
+ Ok(zed::Command {
+ command: binary_path,
+ args,
+ env: Default::default(),
+ })
+ }
+}
@@ -0,0 +1,52 @@
+use zed_extension_api::{self as zed, Result, settings::LspSettings};
+
+pub(crate) struct ProtobufLanguageServer {
+ cached_binary_path: Option<String>,
+}
+
+impl ProtobufLanguageServer {
+ pub(crate) const SERVER_NAME: &str = "protobuf-language-server";
+
+ pub(crate) fn new() -> Self {
+ ProtobufLanguageServer {
+ cached_binary_path: None,
+ }
+ }
+
+ pub(crate) fn language_server_binary(
+ &mut self,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ let binary_settings = LspSettings::for_worktree(Self::SERVER_NAME, worktree)
+ .ok()
+ .and_then(|lsp_settings| lsp_settings.binary);
+
+ let args = binary_settings
+ .as_ref()
+ .and_then(|binary_settings| binary_settings.arguments.clone())
+ .unwrap_or_else(|| vec!["-logs".into(), "".into()]);
+
+ if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
+ Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ })
+ } else if let Some(path) = self.cached_binary_path.clone() {
+ Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ })
+ } else if let Some(path) = worktree.which(Self::SERVER_NAME) {
+ self.cached_binary_path = Some(path.clone());
+ Ok(zed::Command {
+ command: path,
+ args,
+ env: Default::default(),
+ })
+ } else {
+ Err(format!("{} not found in PATH", Self::SERVER_NAME))
+ }
+ }
+}
@@ -0,0 +1,113 @@
+use zed_extension_api::{
+ self as zed, Architecture, DownloadedFileType, GithubReleaseOptions, Os, Result,
+ settings::LspSettings,
+};
+
+use crate::language_servers::util;
+
+pub(crate) struct ProtoLs {
+ cached_binary_path: Option<String>,
+}
+
+impl ProtoLs {
+ pub(crate) const SERVER_NAME: &str = "protols";
+
+ pub(crate) fn new() -> Self {
+ ProtoLs {
+ cached_binary_path: None,
+ }
+ }
+
+ pub(crate) fn language_server_binary(
+ &mut self,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ let binary_settings = LspSettings::for_worktree(Self::SERVER_NAME, worktree)
+ .ok()
+ .and_then(|lsp_settings| lsp_settings.binary);
+
+ let args = binary_settings
+ .as_ref()
+ .and_then(|binary_settings| binary_settings.arguments.clone())
+ .unwrap_or_default();
+
+ let env = worktree.shell_env();
+
+ if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env,
+ });
+ } else if let Some(path) = self.cached_binary_path.clone() {
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env,
+ });
+ } else if let Some(path) = worktree.which(Self::SERVER_NAME) {
+ self.cached_binary_path = Some(path.clone());
+ return Ok(zed::Command {
+ command: path,
+ args,
+ env,
+ });
+ }
+
+ let latest_release = zed::latest_github_release(
+ "coder3101/protols",
+ GithubReleaseOptions {
+ require_assets: true,
+ pre_release: false,
+ },
+ )?;
+
+ let (os, arch) = zed::current_platform();
+
+ let release_suffix = match (os, arch) {
+ (Os::Mac, Architecture::Aarch64) => "aarch64-apple-darwin.tar.gz",
+ (Os::Mac, Architecture::X8664) => "x86_64-apple-darwin.tar.gz",
+ (Os::Linux, Architecture::Aarch64) => "aarch64-unknown-linux-gnu.tar.gz",
+ (Os::Linux, Architecture::X8664) => "x86_64-unknown-linux-gnu.tar.gz",
+ (Os::Windows, Architecture::X8664) => "x86_64-pc-windows-msvc.zip",
+ _ => {
+ return Err("Platform and architecture not supported by Protols".to_string());
+ }
+ };
+
+ let release_name = format!("protols-{release_suffix}");
+
+ let file_type = if os == Os::Windows {
+ DownloadedFileType::Zip
+ } else {
+ DownloadedFileType::GzipTar
+ };
+
+ let version_dir = format!("{}-{}", Self::SERVER_NAME, latest_release.version);
+ let binary_path = format!("{version_dir}/protols");
+
+ let download_target = latest_release
+ .assets
+ .into_iter()
+ .find(|asset| asset.name == release_name)
+ .ok_or_else(|| {
+ format!(
+ "Could not find asset with name {} in Protols release",
+ &release_name
+ )
+ })?;
+
+ zed::download_file(&download_target.download_url, &version_dir, file_type)?;
+ zed::make_file_executable(&binary_path)?;
+
+ util::remove_outdated_versions(Self::SERVER_NAME, &version_dir)?;
+
+ self.cached_binary_path = Some(binary_path.clone());
+
+ Ok(zed::Command {
+ command: binary_path,
+ args,
+ env,
+ })
+ }
+}
@@ -0,0 +1,19 @@
+use std::fs;
+
+use zed_extension_api::Result;
+
+pub(super) fn remove_outdated_versions(
+ language_server_id: &'static str,
+ version_dir: &str,
+) -> Result<()> {
+ let entries = fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
+ for entry in entries {
+ let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
+ if entry.file_name().to_str().is_none_or(|file_name| {
+ file_name.starts_with(language_server_id) && file_name != version_dir
+ }) {
+ fs::remove_dir_all(entry.path()).ok();
+ }
+ }
+ Ok(())
+}
@@ -0,0 +1,66 @@
+use zed_extension_api::{self as zed, Result, settings::LspSettings};
+
+use crate::language_servers::{BufLsp, ProtoLs, ProtobufLanguageServer};
+
+mod language_servers;
+
+struct ProtobufExtension {
+ protobuf_language_server: Option<ProtobufLanguageServer>,
+ protols: Option<ProtoLs>,
+ buf_lsp: Option<BufLsp>,
+}
+
+impl zed::Extension for ProtobufExtension {
+ fn new() -> Self {
+ Self {
+ protobuf_language_server: None,
+ protols: None,
+ buf_lsp: None,
+ }
+ }
+
+ fn language_server_command(
+ &mut self,
+ language_server_id: &zed_extension_api::LanguageServerId,
+ worktree: &zed_extension_api::Worktree,
+ ) -> zed_extension_api::Result<zed_extension_api::Command> {
+ match language_server_id.as_ref() {
+ ProtobufLanguageServer::SERVER_NAME => self
+ .protobuf_language_server
+ .get_or_insert_with(ProtobufLanguageServer::new)
+ .language_server_binary(worktree),
+
+ ProtoLs::SERVER_NAME => self
+ .protols
+ .get_or_insert_with(ProtoLs::new)
+ .language_server_binary(worktree),
+
+ BufLsp::SERVER_NAME => self
+ .buf_lsp
+ .get_or_insert_with(BufLsp::new)
+ .language_server_binary(worktree),
+
+ _ => Err(format!("Unknown language server ID {}", language_server_id)),
+ }
+ }
+
+ fn language_server_workspace_configuration(
+ &mut self,
+ server_id: &zed::LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<Option<zed::serde_json::Value>> {
+ LspSettings::for_worktree(server_id.as_ref(), worktree)
+ .map(|lsp_settings| lsp_settings.settings)
+ }
+
+ fn language_server_initialization_options(
+ &mut self,
+ server_id: &zed::LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<Option<zed_extension_api::serde_json::Value>> {
+ LspSettings::for_worktree(server_id.as_ref(), worktree)
+ .map(|lsp_settings| lsp_settings.initialization_options)
+ }
+}
+
+zed::register_extension!(ProtobufExtension);
@@ -0,0 +1,16 @@
+[package]
+name = "slash_commands_example"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/slash_commands_example.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.1.0"
@@ -0,0 +1 @@
+../../LICENSE-APACHE
@@ -0,0 +1,84 @@
+# Slash Commands Example Extension
+
+This is an example extension showcasing how to write slash commands.
+
+See: [Extensions: Slash Commands](https://zed.dev/docs/extensions/slash-commands) in the Zed Docs.
+
+## Pre-requisites
+
+[Install Rust Toolchain](https://www.rust-lang.org/tools/install):
+
+```sh
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+```
+
+## Setup
+
+```sh
+git clone https://github.com/zed-industries/zed.git
+cp -RL zed/extensions/slash-commands-example .
+
+cd slash-commands-example/
+
+# Update Cargo.toml to make it standalone
+cat > Cargo.toml << EOF
+[package]
+name = "slash_commands_example"
+version = "0.1.0"
+edition = "2021"
+license = "Apache-2.0"
+
+[lib]
+path = "src/slash_commands_example.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.1.0"
+EOF
+
+curl -O https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-APACHE
+echo "# Zed Slash Commands Example Extension" > README.md
+echo "Cargo.lock" > .gitignore
+echo "target/" >> .gitignore
+echo "*.wasm" >> .gitignore
+
+git init
+git add .
+git commit -m "Initial commit"
+
+cd ..
+mv slash-commands-example MY-SUPER-COOL-ZED-EXTENSION
+zed $_
+```
+
+## Installation
+
+1. Open the command palette (`cmd-shift-p` or `ctrl-shift-p`).
+2. Launch `zed: install dev extension`
+3. Select the extension folder created above
+
+## Test
+
+Open the assistant and type `/echo` and `/pick-one` at the beginning of a line.
+
+## Customization
+
+Open the `extensions.toml` file and set the `id`, `name`, `description`, `authors` and `repository` fields.
+
+Rename `slash-commands-example.rs` you'll also have to update `Cargo.toml`
+
+## Rebuild
+
+Rebuild to see these changes reflected:
+
+1. Open Zed Extensions (`cmd-shift-x` or `ctrl-shift-x`).
+2. Click `Rebuild` next to your Dev Extension (formerly "Slash Command Example")
+
+## Troubleshooting / Logs
+
+- [zed.dev docs: Troubleshooting](https://zed.dev/docs/troubleshooting)
+
+## Documentation
+
+- [zed.dev docs: Extensions: Developing Extensions](https://zed.dev/docs/extensions/developing-extensions)
+- [zed.dev docs: Extensions: Slash Commands](https://zed.dev/docs/extensions/slash-commands)
@@ -0,0 +1,15 @@
+id = "slash-commands-example"
+name = "Slash Commands Example"
+description = "An example extension showcasing slash commands."
+version = "0.1.0"
+schema_version = 1
+authors = ["Zed Industries <hi@zed.dev>"]
+repository = "https://github.com/zed-industries/zed"
+
+[slash_commands.echo]
+description = "echoes the provided input"
+requires_argument = true
+
+[slash_commands.pick-one]
+description = "pick one of three options"
+requires_argument = true
@@ -0,0 +1,90 @@
+use zed_extension_api::{
+ self as zed, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput,
+ SlashCommandOutputSection, Worktree,
+};
+
+struct SlashCommandsExampleExtension;
+
+impl zed::Extension for SlashCommandsExampleExtension {
+ fn new() -> Self {
+ SlashCommandsExampleExtension
+ }
+
+ fn complete_slash_command_argument(
+ &self,
+ command: SlashCommand,
+ _args: Vec<String>,
+ ) -> Result<Vec<zed_extension_api::SlashCommandArgumentCompletion>, String> {
+ match command.name.as_str() {
+ "echo" => Ok(vec![]),
+ "pick-one" => Ok(vec![
+ SlashCommandArgumentCompletion {
+ label: "Option One".to_string(),
+ new_text: "option-1".to_string(),
+ run_command: true,
+ },
+ SlashCommandArgumentCompletion {
+ label: "Option Two".to_string(),
+ new_text: "option-2".to_string(),
+ run_command: true,
+ },
+ SlashCommandArgumentCompletion {
+ label: "Option Three".to_string(),
+ new_text: "option-3".to_string(),
+ run_command: true,
+ },
+ ]),
+ command => Err(format!("unknown slash command: \"{command}\"")),
+ }
+ }
+
+ fn run_slash_command(
+ &self,
+ command: SlashCommand,
+ args: Vec<String>,
+ _worktree: Option<&Worktree>,
+ ) -> Result<SlashCommandOutput, String> {
+ match command.name.as_str() {
+ "echo" => {
+ if args.is_empty() {
+ return Err("nothing to echo".to_string());
+ }
+
+ let text = args.join(" ");
+
+ Ok(SlashCommandOutput {
+ sections: vec![SlashCommandOutputSection {
+ range: (0..text.len()).into(),
+ label: "Echo".to_string(),
+ }],
+ text,
+ })
+ }
+ "pick-one" => {
+ let Some(selection) = args.first() else {
+ return Err("no option selected".to_string());
+ };
+
+ match selection.as_str() {
+ "option-1" | "option-2" | "option-3" => {}
+ invalid_option => {
+ return Err(format!("{invalid_option} is not a valid option"));
+ }
+ }
+
+ let text = format!("You chose {selection}.");
+
+ Ok(SlashCommandOutput {
+ sections: vec![SlashCommandOutputSection {
+ range: (0..text.len()).into(),
+ label: format!("Pick One: {selection}"),
+ }],
+ text,
+ })
+ }
+ command => Err(format!("unknown slash command: \"{command}\"")),
+ }
+ }
+}
+
+zed::register_extension!(SlashCommandsExampleExtension);
@@ -0,0 +1,16 @@
+[package]
+name = "zed_test_extension"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/test_extension.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = { path = "../../crates/extension_api" }
@@ -0,0 +1 @@
+../../LICENSE-APACHE
@@ -0,0 +1,5 @@
+# Test Extension
+
+This is a test extension that we use in the tests for the `extension` crate.
+
+Originally based off the Gleam extension.
@@ -0,0 +1,25 @@
+id = "test-extension"
+name = "Test Extension"
+description = "An extension for use in tests."
+version = "0.1.0"
+schema_version = 1
+authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
+repository = "https://github.com/zed-industries/zed"
+
+[language_servers.gleam]
+name = "Gleam LSP"
+language = "Gleam"
+
+[grammars.gleam]
+repository = "https://github.com/gleam-lang/tree-sitter-gleam"
+commit = "8432ffe32ccd360534837256747beb5b1c82fca1"
+
+[[capabilities]]
+kind = "process:exec"
+command = "echo"
+args = ["hello from a child process!"]
+
+[[capabilities]]
+kind = "process:exec"
+command = "cmd"
+args = ["/C", "echo", "hello from a child process!"]
@@ -0,0 +1,12 @@
+name = "Gleam"
+grammar = "gleam"
+path_suffixes = ["gleam"]
+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", "comment"] },
+]
+tab_size = 2
@@ -0,0 +1,130 @@
+; Comments
+(module_comment) @comment
+(statement_comment) @comment
+(comment) @comment
+
+; Constants
+(constant
+ name: (identifier) @constant)
+
+; Variables
+(identifier) @variable
+(discard) @comment.unused
+
+; Modules
+(module) @module
+(import alias: (identifier) @module)
+(remote_type_identifier
+ module: (identifier) @module)
+(remote_constructor_name
+ module: (identifier) @module)
+((field_access
+ record: (identifier) @module
+ field: (label) @function)
+ (#is-not? local))
+
+; Functions
+(unqualified_import (identifier) @function)
+(unqualified_import "type" (type_identifier) @type)
+(unqualified_import (type_identifier) @constructor)
+(function
+ name: (identifier) @function)
+(external_function
+ name: (identifier) @function)
+(function_parameter
+ name: (identifier) @variable.parameter)
+((function_call
+ function: (identifier) @function)
+ (#is-not? local))
+((binary_expression
+ operator: "|>"
+ right: (identifier) @function)
+ (#is-not? local))
+
+; "Properties"
+; Assumed to be intended to refer to a name for a field; something that comes
+; before ":" or after "."
+; e.g. record field names, tuple indices, names for named arguments, etc
+(label) @property
+(tuple_access
+ index: (integer) @property)
+
+; Attributes
+(attribute
+ "@" @attribute
+ name: (identifier) @attribute)
+
+(attribute_value (identifier) @constant)
+
+; Type names
+(remote_type_identifier) @type
+(type_identifier) @type
+
+; Data constructors
+(constructor_name) @constructor
+
+; Literals
+(string) @string
+((escape_sequence) @warning
+ ; Deprecated in v0.33.0-rc2:
+ (#eq? @warning "\\e"))
+(escape_sequence) @string.escape
+(bit_string_segment_option) @function.builtin
+(integer) @number
+(float) @number
+
+; Reserved identifiers
+; TODO: when tree-sitter supports `#any-of?` in the Rust bindings,
+; refactor this to use `#any-of?` rather than `#match?`
+((identifier) @warning
+ (#match? @warning "^(auto|delegate|derive|else|implement|macro|test|echo)$"))
+
+; Keywords
+[
+ (visibility_modifier) ; "pub"
+ (opacity_modifier) ; "opaque"
+ "as"
+ "assert"
+ "case"
+ "const"
+ ; DEPRECATED: 'external' was removed in v0.30.
+ "external"
+ "fn"
+ "if"
+ "import"
+ "let"
+ "panic"
+ "todo"
+ "type"
+ "use"
+] @keyword
+
+; Operators
+(binary_expression
+ operator: _ @operator)
+(boolean_negation "!" @operator)
+(integer_negation "-" @operator)
+
+; Punctuation
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "<<"
+ ">>"
+] @punctuation.bracket
+[
+ "."
+ ","
+ ;; Controversial -- maybe some are operators?
+ ":"
+ "#"
+ "="
+ "->"
+ ".."
+ "-"
+ "<-"
+] @punctuation.delimiter
@@ -0,0 +1,3 @@
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,31 @@
+(external_type
+ (visibility_modifier)? @context
+ "type" @context
+ (type_name) @name) @item
+
+(type_definition
+ (visibility_modifier)? @context
+ (opacity_modifier)? @context
+ "type" @context
+ (type_name) @name) @item
+
+(data_constructor
+ (constructor_name) @name) @item
+
+(data_constructor_argument
+ (label) @name) @item
+
+(type_alias
+ (visibility_modifier)? @context
+ "type" @context
+ (type_name) @name) @item
+
+(function
+ (visibility_modifier)? @context
+ "fn" @context
+ name: (_) @name) @item
+
+(constant
+ (visibility_modifier)? @context
+ "const" @context
+ name: (_) @name) @item
@@ -0,0 +1,209 @@
+use std::fs;
+use zed::lsp::CompletionKind;
+use zed::{CodeLabel, CodeLabelSpan, LanguageServerId};
+use zed_extension_api::process::Command;
+use zed_extension_api::{self as zed, Result};
+
+struct TestExtension {
+ cached_binary_path: Option<String>,
+}
+
+impl TestExtension {
+ fn language_server_binary_path(
+ &mut self,
+ language_server_id: &LanguageServerId,
+ _worktree: &zed::Worktree,
+ ) -> Result<String> {
+ let (platform, arch) = zed::current_platform();
+
+ let current_dir = std::env::current_dir().unwrap();
+ println!("current_dir: {}", current_dir.display());
+ assert_eq!(
+ current_dir.file_name().unwrap().to_str().unwrap(),
+ "test-extension"
+ );
+
+ fs::create_dir_all(current_dir.join("dir-created-with-abs-path")).unwrap();
+ fs::create_dir_all("./dir-created-with-rel-path").unwrap();
+ fs::write("file-created-with-rel-path", b"contents 1").unwrap();
+ fs::write(
+ current_dir.join("file-created-with-abs-path"),
+ b"contents 2",
+ )
+ .unwrap();
+ assert_eq!(
+ fs::read("file-created-with-rel-path").unwrap(),
+ b"contents 1"
+ );
+ assert_eq!(
+ fs::read("file-created-with-abs-path").unwrap(),
+ b"contents 2"
+ );
+
+ let command = match platform {
+ zed::Os::Linux | zed::Os::Mac => Command::new("echo"),
+ zed::Os::Windows => Command::new("cmd").args(["/C", "echo"]),
+ };
+ let output = command.arg("hello from a child process!").output()?;
+ println!(
+ "command output: {}",
+ String::from_utf8_lossy(&output.stdout).trim()
+ );
+
+ if let Some(path) = &self.cached_binary_path
+ && fs::metadata(path).is_ok_and(|stat| stat.is_file())
+ {
+ return Ok(path.clone());
+ }
+
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::CheckingForUpdate,
+ );
+ let release = zed::latest_github_release(
+ "gleam-lang/gleam",
+ zed::GithubReleaseOptions {
+ require_assets: true,
+ pre_release: false,
+ },
+ )?;
+
+ let ext = "tar.gz";
+ let download_type = zed::DownloadedFileType::GzipTar;
+
+ // Do this if you want to actually run this extension -
+ // the actual asset is a .zip. But the integration test is simpler
+ // if every platform uses .tar.gz.
+ //
+ // ext = "zip";
+ // download_type = zed::DownloadedFileType::Zip;
+
+ let asset_name = format!(
+ "gleam-{version}-{arch}-{os}.{ext}",
+ version = release.version,
+ arch = match arch {
+ zed::Architecture::Aarch64 => "aarch64",
+ zed::Architecture::X86 => "x86",
+ zed::Architecture::X8664 => "x86_64",
+ },
+ os = match platform {
+ zed::Os::Mac => "apple-darwin",
+ zed::Os::Linux => "unknown-linux-musl",
+ zed::Os::Windows => "pc-windows-msvc",
+ },
+ );
+
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
+
+ let version_dir = format!("gleam-{}", release.version);
+ let binary_path = format!("{version_dir}/gleam");
+
+ if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) {
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::Downloading,
+ );
+
+ zed::download_file(&asset.download_url, &version_dir, download_type)
+ .map_err(|e| format!("failed to download file: {e}"))?;
+
+ zed::set_language_server_installation_status(
+ language_server_id,
+ &zed::LanguageServerInstallationStatus::None,
+ );
+
+ let entries =
+ fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
+ for entry in entries {
+ let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
+ let filename = entry.file_name();
+ let filename = filename.to_str().unwrap();
+ if filename.starts_with("gleam-") && filename != version_dir {
+ fs::remove_dir_all(entry.path()).ok();
+ }
+ }
+ }
+
+ self.cached_binary_path = Some(binary_path.clone());
+ Ok(binary_path)
+ }
+}
+
+impl zed::Extension for TestExtension {
+ fn new() -> Self {
+ Self {
+ cached_binary_path: None,
+ }
+ }
+
+ fn language_server_command(
+ &mut self,
+ language_server_id: &LanguageServerId,
+ worktree: &zed::Worktree,
+ ) -> Result<zed::Command> {
+ Ok(zed::Command {
+ command: self.language_server_binary_path(language_server_id, worktree)?,
+ args: vec!["lsp".to_string()],
+ env: Default::default(),
+ })
+ }
+
+ fn label_for_completion(
+ &self,
+ _language_server_id: &LanguageServerId,
+ completion: zed::lsp::Completion,
+ ) -> Option<zed::CodeLabel> {
+ let name = &completion.label;
+ let ty = strip_newlines_from_detail(&completion.detail?);
+ let let_binding = "let a";
+ let colon = ": ";
+ let assignment = " = ";
+ let call = match completion.kind? {
+ CompletionKind::Function | CompletionKind::Constructor => "()",
+ _ => "",
+ };
+ let code = format!("{let_binding}{colon}{ty}{assignment}{name}{call}");
+
+ Some(CodeLabel {
+ spans: vec![
+ CodeLabelSpan::code_range({
+ let start = let_binding.len() + colon.len() + ty.len() + assignment.len();
+ start..start + name.len()
+ }),
+ CodeLabelSpan::code_range({
+ let start = let_binding.len();
+ start..start + colon.len()
+ }),
+ CodeLabelSpan::code_range({
+ let start = let_binding.len() + colon.len();
+ start..start + ty.len()
+ }),
+ ],
+ filter_range: (0..name.len()).into(),
+ code,
+ })
+ }
+}
+
+zed::register_extension!(TestExtension);
+
+/// Removes newlines from the completion detail.
+///
+/// The Gleam LSP can return types containing newlines, which causes formatting
+/// issues within the Zed completions menu.
+fn strip_newlines_from_detail(detail: &str) -> String {
+ let without_newlines = detail
+ .replace("->\n ", "-> ")
+ .replace("\n ", "")
+ .replace(",\n", "");
+
+ let comma_delimited_parts = without_newlines.split(',');
+ comma_delimited_parts
+ .map(|part| part.trim())
+ .collect::<Vec<_>>()
+ .join(", ")
+}
@@ -0,0 +1,52 @@
+# Generated from xtask::workflows::extensions::bump_version within the Zed repository.
+# Rebuild with `cargo xtask workflows`.
+name: extensions::bump_version
+on:
+ pull_request:
+ types:
+ - labeled
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - .github/**
+ workflow_dispatch: {}
+jobs:
+ determine_bump_type:
+ runs-on: namespace-profile-16x32-ubuntu-2204
+ steps:
+ - id: get-bump-type
+ name: extensions::bump_version::get_bump_type
+ run: |
+ if [ "$HAS_MAJOR_LABEL" = "true" ]; then
+ bump_type="major"
+ elif [ "$HAS_MINOR_LABEL" = "true" ]; then
+ bump_type="minor"
+ else
+ bump_type="patch"
+ fi
+ echo "bump_type=$bump_type" >> $GITHUB_OUTPUT
+ shell: bash -euxo pipefail {0}
+ env:
+ HAS_MAJOR_LABEL: |-
+ ${{ (github.event.action == 'labeled' && github.event.label.name == 'major') ||
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'major')) }}
+ HAS_MINOR_LABEL: |-
+ ${{ (github.event.action == 'labeled' && github.event.label.name == 'minor') ||
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'minor')) }}
+ outputs:
+ bump_type: ${{ steps.get-bump-type.outputs.bump_type }}
+ call_bump_version:
+ needs:
+ - determine_bump_type
+ if: github.event.action != 'labeled' || needs.determine_bump_type.outputs.bump_type != 'patch'
+ uses: zed-industries/zed/.github/workflows/extension_bump.yml@main
+ secrets:
+ app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
+ app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
+ with:
+ bump-type: ${{ needs.determine_bump_type.outputs.bump_type }}
+ force-bump: true
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}labels
+ cancel-in-progress: true
@@ -0,0 +1,13 @@
+# Generated from xtask::workflows::extensions::release_version within the Zed repository.
+# Rebuild with `cargo xtask workflows`.
+name: extensions::release_version
+on:
+ push:
+ tags:
+ - v**
+jobs:
+ call_release_version:
+ uses: zed-industries/zed/.github/workflows/extension_release.yml@main
+ secrets:
+ app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
+ app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
@@ -0,0 +1,16 @@
+# Generated from xtask::workflows::extensions::run_tests within the Zed repository.
+# Rebuild with `cargo xtask workflows`.
+name: extensions::run_tests
+on:
+ pull_request:
+ branches:
+ - '**'
+ push:
+ branches:
+ - main
+jobs:
+ call_extension_tests:
+ uses: zed-industries/zed/.github/workflows/extension_tests.yml@main
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}pr
+ cancel-in-progress: true