diff --git a/Cargo.lock b/Cargo.lock index 25e81e4aa5..4e11986ff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5438,7 +5438,6 @@ dependencies = [ "tree-sitter-heex", "tree-sitter-jsdoc", "tree-sitter-json 0.20.0", - "tree-sitter-lua", "tree-sitter-markdown", "tree-sitter-nix", "tree-sitter-nu", @@ -10435,16 +10434,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "tree-sitter-lua" -version = "0.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d489873fd1a2fa6d5f04930bfc5c081c96f0c038c1437104518b5b842c69b282" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "tree-sitter-markdown" version = "0.0.1" @@ -12648,6 +12637,13 @@ dependencies = [ "zed_extension_api 0.0.4", ] +[[package]] +name = "zed_lua" +version = "0.0.1" +dependencies = [ + "zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "zed_php" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index a005b84814..96c9005a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ members = [ "extensions/gleam", "extensions/haskell", "extensions/html", + "extensions/lua", "extensions/php", "extensions/prisma", "extensions/purescript", @@ -323,7 +324,6 @@ tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex tree-sitter-html = "0.19.0" tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", ref = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } -tree-sitter-lua = "0.0.14" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "7dd29f9616822e5fc259f5b4ae6c4ded9a71a132" } diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index f7d219f1b6..df65e6ee31 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -8,67 +8,71 @@ use extension::ExtensionStore; use gpui::{Model, VisualContext}; use language::Buffer; use ui::{SharedString, ViewContext}; -use workspace::notifications::NotificationId; -use workspace::{notifications::simple_message_notification, Workspace}; +use workspace::{ + notifications::{simple_message_notification, NotificationId}, + Workspace, +}; + +const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[ + ("astro", &["astro"]), + ("beancount", &["beancount"]), + ("clojure", &["bb", "clj", "cljc", "cljs", "edn"]), + ("csharp", &["cs"]), + ("dart", &["dart"]), + ("dockerfile", &["Dockerfile"]), + ("elisp", &["el"]), + ("elm", &["elm"]), + ("erlang", &["erl", "hrl"]), + ("fish", &["fish"]), + ( + "git-firefly", + &[ + ".gitconfig", + ".gitignore", + "COMMIT_EDITMSG", + "EDIT_DESCRIPTION", + "MERGE_MSG", + "NOTES_EDITMSG", + "TAG_EDITMSG", + "git-rebase-todo", + ], + ), + ("gleam", &["gleam"]), + ("glsl", &["vert", "frag"]), + ("graphql", &["gql", "graphql"]), + ("haskell", &["hs"]), + ("html", &["htm", "html", "shtml"]), + ("java", &["java"]), + ("kotlin", &["kt"]), + ("latex", &["tex"]), + ("lua", &["lua"]), + ("make", &["Makefile"]), + ("nix", &["nix"]), + ("php", &["php"]), + ("prisma", &["prisma"]), + ("purescript", &["purs"]), + ("r", &["r", "R"]), + ("sql", &["sql"]), + ("svelte", &["svelte"]), + ("swift", &["swift"]), + ("templ", &["templ"]), + ("toml", &["Cargo.lock", "toml"]), + ("wgsl", &["wgsl"]), + ("zig", &["zig"]), +]; fn suggested_extensions() -> &'static HashMap<&'static str, Arc> { - static SUGGESTED: OnceLock>> = OnceLock::new(); - SUGGESTED.get_or_init(|| { - [ - ("astro", "astro"), - ("beancount", "beancount"), - ("clojure", "bb"), - ("clojure", "clj"), - ("clojure", "cljc"), - ("clojure", "cljs"), - ("clojure", "edn"), - ("csharp", "cs"), - ("dart", "dart"), - ("dockerfile", "Dockerfile"), - ("elisp", "el"), - ("elm", "elm"), - ("erlang", "erl"), - ("erlang", "hrl"), - ("fish", "fish"), - ("git-firefly", ".gitconfig"), - ("git-firefly", ".gitignore"), - ("git-firefly", "COMMIT_EDITMSG"), - ("git-firefly", "EDIT_DESCRIPTION"), - ("git-firefly", "MERGE_MSG"), - ("git-firefly", "NOTES_EDITMSG"), - ("git-firefly", "TAG_EDITMSG"), - ("git-firefly", "git-rebase-todo"), - ("gleam", "gleam"), - ("glsl", "vert"), - ("glsl", "frag"), - ("graphql", "gql"), - ("graphql", "graphql"), - ("haskell", "hs"), - ("html", "htm"), - ("html", "html"), - ("html", "shtml"), - ("java", "java"), - ("kotlin", "kt"), - ("latex", "tex"), - ("make", "Makefile"), - ("nix", "nix"), - ("php", "php"), - ("prisma", "prisma"), - ("purescript", "purs"), - ("r", "r"), - ("r", "R"), - ("sql", "sql"), - ("svelte", "svelte"), - ("swift", "swift"), - ("templ", "templ"), - ("toml", "Cargo.lock"), - ("toml", "toml"), - ("wgsl", "wgsl"), - ("zig", "zig"), - ] - .into_iter() - .map(|(name, file)| (file, name.into())) - .collect() + static SUGGESTIONS_BY_PATH_SUFFIX: OnceLock>> = OnceLock::new(); + SUGGESTIONS_BY_PATH_SUFFIX.get_or_init(|| { + SUGGESTIONS_BY_EXTENSION_ID + .into_iter() + .flat_map(|(name, path_suffixes)| { + let name = Arc::::from(*name); + path_suffixes + .into_iter() + .map(move |suffix| (*suffix, name.clone())) + }) + .collect() }) } diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 5eb5bd645c..a987e7f0dd 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -49,7 +49,6 @@ tree-sitter-hcl.workspace = true tree-sitter-heex.workspace = true tree-sitter-jsdoc.workspace = true tree-sitter-json.workspace = true -tree-sitter-lua.workspace = true tree-sitter-markdown.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 5290052b06..0a557b9f81 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -18,7 +18,6 @@ mod deno; mod elixir; mod go; mod json; -mod lua; mod nu; mod ocaml; mod python; @@ -69,7 +68,6 @@ pub fn init( ("heex", tree_sitter_heex::language()), ("jsdoc", tree_sitter_jsdoc::language()), ("json", tree_sitter_json::language()), - ("lua", tree_sitter_lua::language()), ("markdown", tree_sitter_markdown::language()), ("nix", tree_sitter_nix::language()), ("nu", tree_sitter_nu::language()), @@ -280,7 +278,6 @@ pub fn init( language!("scheme"); language!("racket"); language!("regex"); - language!("lua", vec![Arc::new(lua::LuaLspAdapter)]); language!( "yaml", vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))] diff --git a/crates/languages/src/lua.rs b/crates/languages/src/lua.rs deleted file mode 100644 index 38dacbd378..0000000000 --- a/crates/languages/src/lua.rs +++ /dev/null @@ -1,146 +0,0 @@ -use anyhow::{anyhow, bail, Result}; -use async_compression::futures::bufread::GzipDecoder; -use async_tar::Archive; -use async_trait::async_trait; -use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerName, LspAdapterDelegate}; -use lsp::LanguageServerBinary; -use smol::fs; -use std::{any::Any, env::consts, path::PathBuf}; -use util::{ - github::{latest_github_release, GitHubLspBinaryVersion}, - maybe, ResultExt, -}; - -#[derive(Copy, Clone)] -pub struct LuaLspAdapter; - -#[async_trait(?Send)] -impl super::LspAdapter for LuaLspAdapter { - fn name(&self) -> LanguageServerName { - LanguageServerName("lua-language-server".into()) - } - - async fn fetch_latest_server_version( - &self, - delegate: &dyn LspAdapterDelegate, - ) -> Result> { - let os = match consts::OS { - "macos" => "darwin", - "linux" => "linux", - "windows" => "win32", - other => bail!("Running on unsupported os: {other}"), - }; - let platform = match consts::ARCH { - "x86_64" => "x64", - "aarch64" => "arm64", - other => bail!("Running on unsupported platform: {other}"), - }; - let release = latest_github_release( - "LuaLS/lua-language-server", - true, - false, - delegate.http_client(), - ) - .await?; - let version = &release.tag_name; - let asset_name = format!("lua-language-server-{version}-{os}-{platform}.tar.gz"); - let asset = release - .assets - .iter() - .find(|asset| asset.name == asset_name) - .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; - let version = GitHubLspBinaryVersion { - name: release.tag_name, - url: asset.browser_download_url.clone(), - }; - Ok(Box::new(version) as Box<_>) - } - - async fn fetch_server_binary( - &self, - version: Box, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let version = version.downcast::().unwrap(); - - let binary_path = container_dir.join("bin/lua-language-server"); - - if fs::metadata(&binary_path).await.is_err() { - let mut response = delegate - .http_client() - .get(&version.url, Default::default(), true) - .await - .map_err(|err| anyhow!("error downloading release: {}", err))?; - let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); - let archive = Archive::new(decompressed_bytes); - archive.unpack(container_dir).await?; - } - - // todo("windows") - #[cfg(not(windows))] - { - fs::set_permissions( - &binary_path, - ::from_mode(0o755), - ) - .await?; - } - Ok(LanguageServerBinary { - path: binary_path, - env: None, - arguments: Vec::new(), - }) - } - - async fn cached_server_binary( - &self, - container_dir: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - get_cached_server_binary(container_dir).await - } - - async fn installation_test_binary( - &self, - container_dir: PathBuf, - ) -> Option { - get_cached_server_binary(container_dir) - .await - .map(|mut binary| { - binary.arguments = vec!["--version".into()]; - binary - }) - } -} - -async fn get_cached_server_binary(container_dir: PathBuf) -> Option { - maybe!(async { - let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name == "lua-language-server") - { - last_binary_path = Some(entry.path()); - } - } - - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - env: None, - arguments: Vec::new(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() -} diff --git a/extensions/lua/Cargo.toml b/extensions/lua/Cargo.toml new file mode 100644 index 0000000000..dfbe63d840 --- /dev/null +++ b/extensions/lua/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "zed_lua" +version = "0.0.1" +edition = "2021" +publish = false +license = "Apache-2.0" + +[lints] +workspace = true + +[lib] +path = "src/lua.rs" +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "0.0.6" diff --git a/extensions/lua/LICENSE-APACHE b/extensions/lua/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/extensions/lua/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/extensions/lua/extension.toml b/extensions/lua/extension.toml new file mode 100644 index 0000000000..370648d304 --- /dev/null +++ b/extensions/lua/extension.toml @@ -0,0 +1,15 @@ +id = "lua" +name = "Lua" +description = "Lua support." +version = "0.1.0" +schema_version = 1 +authors = ["Kaylee Simmons "] +repository = "https://github.com/zed-industries/zed" + +[language_servers.lua-language-server] +name = "LuaLS" +language = "Lua" + +[grammars.lua] +repository = "https://github.com/tree-sitter-grammars/tree-sitter-lua" +commit = "a24dab177e58c9c6832f96b9a73102a0cfbced4a" diff --git a/crates/languages/src/lua/brackets.scm b/extensions/lua/languages/lua/brackets.scm similarity index 100% rename from crates/languages/src/lua/brackets.scm rename to extensions/lua/languages/lua/brackets.scm diff --git a/crates/languages/src/lua/config.toml b/extensions/lua/languages/lua/config.toml similarity index 100% rename from crates/languages/src/lua/config.toml rename to extensions/lua/languages/lua/config.toml diff --git a/crates/languages/src/lua/embedding.scm b/extensions/lua/languages/lua/embedding.scm similarity index 100% rename from crates/languages/src/lua/embedding.scm rename to extensions/lua/languages/lua/embedding.scm diff --git a/crates/languages/src/lua/highlights.scm b/extensions/lua/languages/lua/highlights.scm similarity index 92% rename from crates/languages/src/lua/highlights.scm rename to extensions/lua/languages/lua/highlights.scm index f061bbf8f9..98e2c2eaff 100644 --- a/crates/languages/src/lua/highlights.scm +++ b/extensions/lua/languages/lua/highlights.scm @@ -150,9 +150,9 @@ ;; Tables -(field name: (identifier) @field) +(field name: (identifier) @property) -(dot_index_expression field: (identifier) @field) +(dot_index_expression field: (identifier) @property) (table_constructor [ @@ -176,7 +176,7 @@ (dot_index_expression field: (identifier) @function.definition) ]) -(method_index_expression method: (identifier) @method) +(method_index_expression method: (identifier) @function.method) (function_call (identifier) @function.builtin @@ -195,4 +195,4 @@ (number) @number -(string) @string \ No newline at end of file +(string) @string diff --git a/crates/languages/src/lua/indents.scm b/extensions/lua/languages/lua/indents.scm similarity index 100% rename from crates/languages/src/lua/indents.scm rename to extensions/lua/languages/lua/indents.scm diff --git a/crates/languages/src/lua/outline.scm b/extensions/lua/languages/lua/outline.scm similarity index 100% rename from crates/languages/src/lua/outline.scm rename to extensions/lua/languages/lua/outline.scm diff --git a/extensions/lua/src/lua.rs b/extensions/lua/src/lua.rs new file mode 100644 index 0000000000..776c8ee398 --- /dev/null +++ b/extensions/lua/src/lua.rs @@ -0,0 +1,158 @@ +use std::fs; +use zed::lsp::CompletionKind; +use zed::{CodeLabel, CodeLabelSpan, LanguageServerId}; +use zed_extension_api::{self as zed, Result}; + +struct LuaExtension { + cached_binary_path: Option, +} + +impl LuaExtension { + fn language_server_binary_path( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + if let Some(path) = &self.cached_binary_path { + if fs::metadata(path).map_or(false, |stat| stat.is_file()) { + return Ok(path.clone()); + } + } + + if let Some(path) = worktree.which("lua-language-server") { + self.cached_binary_path = Some(path.clone()); + return Ok(path); + } + + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + let release = zed::latest_github_release( + "LuaLS/lua-language-server", + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let (platform, arch) = zed::current_platform(); + let asset_name = format!( + "lua-language-server-{version}-{os}-{arch}.tar.gz", + version = release.version, + os = match platform { + zed::Os::Mac => "darwin", + zed::Os::Linux => "linux", + zed::Os::Windows => "win32", + }, + arch = match arch { + zed::Architecture::Aarch64 => "arm64", + zed::Architecture::X8664 => "x86_64", + zed::Architecture::X86 => return Err("unsupported platform x86".into()), + }, + ); + + 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!("lua-language-server-{}", release.version); + let binary_path = format!("{version_dir}/bin/lua-language-server"); + + if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &version_dir, + zed::DownloadedFileType::GzipTar, + ) + .map_err(|e| format!("failed to download file: {e}"))?; + + 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 LuaExtension { + fn new() -> Self { + Self { + cached_binary_path: None, + } + } + + fn language_server_command( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + Ok(zed::Command { + command: self.language_server_binary_path(language_server_id, worktree)?, + args: Default::default(), + env: Default::default(), + }) + } + + fn label_for_completion( + &self, + _language_server_id: &LanguageServerId, + completion: zed::lsp::Completion, + ) -> Option { + match completion.kind? { + CompletionKind::Method | CompletionKind::Function => { + let name_len = completion.label.find('(').unwrap_or(completion.label.len()); + Some(CodeLabel { + spans: vec![CodeLabelSpan::code_range(0..completion.label.len())], + filter_range: (0..name_len).into(), + code: completion.label, + }) + } + CompletionKind::Field => Some(CodeLabel { + spans: vec![CodeLabelSpan::literal( + completion.label.clone(), + Some("property".into()), + )], + filter_range: (0..completion.label.len()).into(), + code: Default::default(), + }), + _ => None, + } + } + + fn label_for_symbol( + &self, + _language_server_id: &LanguageServerId, + symbol: zed::lsp::Symbol, + ) -> Option { + let prefix = "let a = "; + let suffix = match symbol.kind { + zed::lsp::SymbolKind::Method => "()", + _ => "", + }; + let code = format!("{prefix}{}{suffix}", symbol.name); + Some(CodeLabel { + spans: vec![CodeLabelSpan::code_range( + prefix.len()..code.len() - suffix.len(), + )], + filter_range: (0..symbol.name.len()).into(), + code, + }) + } +} + +zed::register_extension!(LuaExtension);