Fix issues with extension API that come up when moving Svelte into an extension (#9611)

We're doing it. Svelte support is moving into an extension. This PR
fixes some issues that came up along the way.

Notes

* extensions need to be able to retrieve the path the `node` binary
installed by Zed
* previously we were silently swallowing any errors that occurred while
loading a grammar
* npm commands ran by extensions weren't run in the right directory
* Tree-sitter's WASM stdlib didn't support a C function (`strncmp`)
needed by the Svelte parser's external scanner
* the way that LSP installation status was reported was unnecessarily
complex

Release Notes:

- Removed built-in support for the Svelte and Gleam languages, because
full support for those languages is now available via extensions. These
extensions will be suggested for download when you open a `.svelte` or
`.gleam` file.

---------

Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-03-22 17:29:06 -07:00 committed by GitHub
parent 4459eacc98
commit 6ebe599c98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 1278 additions and 1223 deletions

View file

@ -8,9 +8,9 @@ license = "Apache-2.0"
[lints]
workspace = true
[dependencies]
zed_extension_api = { path = "../../crates/extension_api" }
[lib]
path = "src/gleam.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.4"

View file

@ -9,10 +9,6 @@ impl GleamExtension {
fn language_server_binary_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::Cached,
);
return Ok(path.clone());
}
}
@ -75,11 +71,6 @@ impl GleamExtension {
fs::remove_dir_all(&entry.path()).ok();
}
}
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::Downloaded,
);
}
self.cached_binary_path = Some(binary_path.clone());

3
extensions/svelte/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target
*.wasm
grammars

View file

@ -0,0 +1,16 @@
[package]
name = "zed_svelte"
version = "0.0.1"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/svelte.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.4"

View file

@ -0,0 +1,15 @@
id = "svelte"
name = "Svelte"
description = "Svelte support"
version = "0.0.1"
schema_version = 1
authors = []
repository = "https://github.com/zed-extensions/svelte"
[language_servers.svelte-language-server]
name = "Svelte Language Server"
language = "Svelte"
[grammars.svelte]
repository = "https://github.com/Himujjal/tree-sitter-svelte"
commit = "ea528fc9985aed8d93c9f438c185644a33d011af"

View file

@ -0,0 +1,22 @@
name = "Svelte"
grammar = "svelte"
path_suffixes = ["svelte"]
block_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, not_in = ["string", "comment"] },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "`", end = "`", close = true, newline = false, not_in = ["string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "svelte"
prettier_plugins = ["prettier-plugin-svelte"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,42 @@
; Special identifiers
;--------------------
; TODO:
(tag_name) @tag
(attribute_name) @property
(erroneous_end_tag_name) @keyword
(comment) @comment
[
(attribute_value)
(quoted_attribute_value)
] @string
[
(text)
(raw_text_expr)
] @none
[
(special_block_keyword)
(then)
(as)
] @keyword
[
"{"
"}"
] @punctuation.bracket
"=" @operator
[
"<"
">"
"</"
"/>"
"#"
":"
"/"
"@"
] @tag.delimiter

View file

@ -0,0 +1,8 @@
[
(element)
(if_statement)
(each_statement)
(await_statement)
(script_element)
(style_element)
] @indent

View file

@ -0,0 +1,32 @@
; injections.scm
; --------------
((script_element
(start_tag
(attribute
(quoted_attribute_value (attribute_value) @_language))?)
(raw_text) @content)
(#eq? @_language "")
(#set! "language" "javascript"))
((script_element
(start_tag
(attribute
(quoted_attribute_value (attribute_value) @_language)))
(raw_text) @content)
(#eq? @_language "ts")
(#set! "language" "typescript"))
((script_element
(start_tag
(attribute
(quoted_attribute_value (attribute_value) @_language)))
(raw_text) @content)
(#eq? @_language "typescript")
(#set! "language" "typescript"))
(style_element
(raw_text) @content
(#set! "language" "css"))
((raw_text_expr) @content
(#set! "language" "javascript"))

View file

@ -0,0 +1,7 @@
(comment) @comment
[
(raw_text)
(attribute_value)
(quoted_attribute_value)
] @string

View file

@ -0,0 +1,126 @@
use std::{env, fs};
use zed_extension_api::{self as zed, Result};
struct SvelteExtension {
did_find_server: bool,
}
const SERVER_PATH: &str = "node_modules/svelte-language-server/bin/server.js";
const PACKAGE_NAME: &str = "svelte-language-server";
impl SvelteExtension {
fn server_exists(&self) -> bool {
fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
}
fn server_script_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
let server_exists = self.server_exists();
if self.did_find_server && server_exists {
return Ok(SERVER_PATH.to_string());
}
zed::set_language_server_installation_status(
&config.name,
&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(
&config.name,
&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)?;
}
}
}
}
self.did_find_server = true;
Ok(SERVER_PATH.to_string())
}
}
impl zed::Extension for SvelteExtension {
fn new() -> Self {
Self {
did_find_server: false,
}
}
fn language_server_command(
&mut self,
config: zed::LanguageServerConfig,
_: &zed::Worktree,
) -> Result<zed::Command> {
let server_path = self.server_script_path(config)?;
Ok(zed::Command {
command: zed::node_binary_path()?,
args: vec![
env::current_dir()
.unwrap()
.join(&server_path)
.to_string_lossy()
.to_string(),
"--stdio".to_string(),
],
env: Default::default(),
})
}
fn language_server_initialization_options(
&mut self,
_: zed::LanguageServerConfig,
_: &zed::Worktree,
) -> Result<Option<String>> {
let config = r#"{
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false
},
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false
},
"propertyDeclarationTypes": {
"enabled": true
},
"functionLikeReturnType": {
"enabled": true
},
"enumMemberValues": {
"enabled": true
}
}
}"#;
Ok(Some(format!(
r#"{{
"provideFormatter": true,
"configuration": {{
"typescript": {config},
"javascript": {config}
}}
}}"#
)))
}
}
zed::register_extension!(SvelteExtension);

View file

@ -8,9 +8,9 @@ license = "Apache-2.0"
[lints]
workspace = true
[dependencies]
zed_extension_api = { path = "../../crates/extension_api" }
[lib]
path = "src/uiua.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.4"