Add initial support for defining language server adapters in WebAssembly-based extensions (#8645)

This PR adds **internal** ability to run arbitrary language servers via
WebAssembly extensions. The functionality isn't exposed yet - we're just
landing this in this early state because there have been a lot of
changes to the `LspAdapter` trait, and other language server logic.

## Next steps

* Currently, wasm extensions can only define how to *install* and run a
language server, they can't yet implement the other LSP adapter methods,
such as formatting completion labels and workspace symbols.
* We don't have an automatic way to install or develop these types of
extensions
* We don't have a way to package these types of extensions in our
extensions repo, to make them available via our extensions API.
* The Rust extension API crate, `zed-extension-api` has not yet been
published to crates.io, because we still consider the API a work in
progress.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Max Brunsfeld 2024-03-01 16:00:55 -08:00 committed by GitHub
parent f3f2225a8e
commit 268fa1cbaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 3714 additions and 1973 deletions

View file

@ -0,0 +1,13 @@
[package]
name = "zed_gleam"
version = "0.0.1"
edition = "2021"
[dependencies]
zed_extension_api = { path = "../../crates/extension_api" }
[lib]
path = "src/gleam.rs"
crate-type = ["cdylib"]
[package.metadata.component]

View file

@ -0,0 +1,13 @@
id = "gleam"
name = "Gleam"
description = "Gleam support for Zed"
version = "0.0.1"
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
[language_servers.gleam]
name = "Gleam LSP"
language = "Gleam"
[grammars.gleam]
repository = "https://github.com/gleam-lang/tree-sitter-gleam"
commit = "58b7cac8fc14c92b0677c542610d8738c373fa81"

View file

@ -0,0 +1,11 @@
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"] },
]

View file

@ -0,0 +1,130 @@
; Comments
(module_comment) @comment
(statement_comment) @comment
(comment) @comment
; Constants
(constant
name: (identifier) @constant)
; 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)$"))
; Variables
(identifier) @variable
(discard) @comment.unused
; 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

View file

@ -0,0 +1,3 @@
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -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

View file

@ -0,0 +1,11 @@
// Generated by `wit-bindgen` 0.16.0. DO NOT EDIT!
#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:zed_gleam"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 169] = [3, 0, 9, 122, 101, 100, 95, 103, 108, 101, 97, 109, 0, 97, 115, 109, 13, 0, 1, 0, 7, 40, 1, 65, 2, 1, 65, 0, 4, 1, 29, 99, 111, 109, 112, 111, 110, 101, 110, 116, 58, 122, 101, 100, 95, 103, 108, 101, 97, 109, 47, 122, 101, 100, 95, 103, 108, 101, 97, 109, 4, 0, 11, 15, 1, 0, 9, 122, 101, 100, 95, 103, 108, 101, 97, 109, 3, 0, 0, 0, 16, 12, 112, 97, 99, 107, 97, 103, 101, 45, 100, 111, 99, 115, 0, 123, 125, 0, 70, 9, 112, 114, 111, 100, 117, 99, 101, 114, 115, 1, 12, 112, 114, 111, 99, 101, 115, 115, 101, 100, 45, 98, 121, 2, 13, 119, 105, 116, 45, 99, 111, 109, 112, 111, 110, 101, 110, 116, 6, 48, 46, 49, 56, 46, 50, 16, 119, 105, 116, 45, 98, 105, 110, 100, 103, 101, 110, 45, 114, 117, 115, 116, 6, 48, 46, 49, 54, 46, 48];
#[inline(never)]
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
pub fn __link_section() {}

View file

@ -0,0 +1,91 @@
use zed_extension_api::{self as zed, Result};
struct GleamExtension {
cached_binary_path: Option<String>,
}
impl zed::Extension for GleamExtension {
fn new() -> Self {
Self {
cached_binary_path: None,
}
}
fn language_server_command(
&mut self,
config: zed::LanguageServerConfig,
_worktree: &zed::Worktree,
) -> Result<zed::Command> {
let binary_path = if let Some(path) = &self.cached_binary_path {
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::Cached,
);
path.clone()
} else {
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let release = zed::latest_github_release(
"gleam-lang/gleam",
zed::GithubReleaseOptions {
require_assets: true,
pre_release: false,
},
)?;
let (platform, arch) = zed::current_platform();
let asset_name = format!(
"gleam-{version}-{arch}-{os}.tar.gz",
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))?;
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::Downloading,
);
let version_dir = format!("gleam-{}", release.version);
zed::download_file(
&asset.download_url,
&version_dir,
zed::DownloadedFileType::GzipTar,
)
.map_err(|e| format!("failed to download file: {e}"))?;
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::Downloaded,
);
let binary_path = format!("{version_dir}/gleam");
self.cached_binary_path = Some(binary_path.clone());
binary_path
};
Ok(zed::Command {
command: binary_path,
args: vec!["lsp".to_string()],
env: Default::default(),
})
}
}
zed::register_extension!(GleamExtension);