From df3050dac1318dabee2f85fce227c8c7902d659e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 29 Mar 2024 16:38:27 -0400 Subject: [PATCH] Extract C# support into an extension (#9971) This PR extracts C# support into an extension and removes the built-in C# support from Zed. Tested using a Nix shell: ``` nix-shell -p dotnet-sdk omnisharp-roslyn ``` Release Notes: - Removed built-in support for C#, in favor of making it available as an extension. The C# extension will be suggested for download when you open a `.cs` file. --- Cargo.lock | 17 +- Cargo.toml | 2 +- crates/extensions_ui/src/extension_suggest.rs | 1 + crates/languages/Cargo.toml | 1 - crates/languages/src/csharp.rs | 148 ------------------ crates/languages/src/lib.rs | 3 - extensions/csharp/Cargo.toml | 16 ++ extensions/csharp/LICENSE-APACHE | 1 + extensions/csharp/extension.toml | 15 ++ .../csharp/languages}/csharp/config.toml | 0 .../csharp/languages}/csharp/highlights.scm | 0 .../csharp/languages}/csharp/injections.scm | 0 .../csharp/languages}/csharp/outline.scm | 0 extensions/csharp/src/csharp.rs | 116 ++++++++++++++ 14 files changed, 157 insertions(+), 163 deletions(-) delete mode 100644 crates/languages/src/csharp.rs create mode 100644 extensions/csharp/Cargo.toml create mode 120000 extensions/csharp/LICENSE-APACHE create mode 100644 extensions/csharp/extension.toml rename {crates/languages/src => extensions/csharp/languages}/csharp/config.toml (100%) rename {crates/languages/src => extensions/csharp/languages}/csharp/highlights.scm (100%) rename {crates/languages/src => extensions/csharp/languages}/csharp/injections.scm (100%) rename {crates/languages/src => extensions/csharp/languages}/csharp/outline.scm (100%) create mode 100644 extensions/csharp/src/csharp.rs diff --git a/Cargo.lock b/Cargo.lock index 865965ea7c..868f5ee1c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5435,7 +5435,6 @@ dependencies = [ "tree-sitter", "tree-sitter-bash", "tree-sitter-c", - "tree-sitter-c-sharp", "tree-sitter-clojure", "tree-sitter-cpp", "tree-sitter-css", @@ -10365,15 +10364,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "tree-sitter-c-sharp" -version = "0.20.0" -source = "git+https://github.com/tree-sitter/tree-sitter-c-sharp?rev=dd5e59721a5f8dae34604060833902b882023aaf#dd5e59721a5f8dae34604060833902b882023aaf" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "tree-sitter-clojure" version = "0.0.9" @@ -12667,6 +12657,13 @@ dependencies = [ "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "zed_csharp" +version = "0.0.1" +dependencies = [ + "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "zed_extension_api" version = "0.0.4" diff --git a/Cargo.toml b/Cargo.toml index db4ea2e7b0..485daad4b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ members = [ "crates/zed_actions", "extensions/astro", + "extensions/csharp", "extensions/gleam", "extensions/haskell", "extensions/php", @@ -297,7 +298,6 @@ tree-sitter = { version = "0.20", features = ["wasm"] } tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" } tree-sitter-c = "0.20.1" tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts" } -tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" } tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" } tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } tree-sitter-dart = { git = "https://github.com/agent3bood/tree-sitter-dart", rev = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7" } diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 704c0584c8..f000368352 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -16,6 +16,7 @@ fn suggested_extensions() -> &'static HashMap<&'static str, Arc> { [ ("astro", "astro"), ("beancount", "beancount"), + ("csharp", "cs"), ("dockerfile", "Dockerfile"), ("elisp", "el"), ("fish", "fish"), diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index be06d2266f..0ecbc2d3fe 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -37,7 +37,6 @@ smol.workspace = true task.workspace = true toml.workspace = true tree-sitter-bash.workspace = true -tree-sitter-c-sharp.workspace = true tree-sitter-c.workspace = true tree-sitter-clojure.workspace = true tree-sitter-cpp.workspace = true diff --git a/crates/languages/src/csharp.rs b/crates/languages/src/csharp.rs deleted file mode 100644 index 82fec8468a..0000000000 --- a/crates/languages/src/csharp.rs +++ /dev/null @@ -1,148 +0,0 @@ -use anyhow::{anyhow, Context, 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::env::consts::ARCH; -use std::ffi::OsString; -use std::{any::Any, path::PathBuf}; -use util::{github::latest_github_release, maybe}; -use util::{github::GitHubLspBinaryVersion, ResultExt}; - -pub struct OmniSharpAdapter; - -#[async_trait(?Send)] -impl super::LspAdapter for OmniSharpAdapter { - fn name(&self) -> LanguageServerName { - LanguageServerName("OmniSharp".into()) - } - - async fn fetch_latest_server_version( - &self, - delegate: &dyn LspAdapterDelegate, - ) -> Result> { - let mapped_arch = match ARCH { - "aarch64" => Some("arm64"), - "x86_64" => Some("x64"), - _ => None, - }; - - match mapped_arch { - None => Ok(Box::new(())), - Some(arch) => { - let release = latest_github_release( - "OmniSharp/omnisharp-roslyn", - true, - false, - delegate.http_client(), - ) - .await?; - let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", arch); - 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("omnisharp"); - - if fs::metadata(&binary_path).await.is_err() { - let mut response = delegate - .http_client() - .get(&version.url, Default::default(), true) - .await - .context("error downloading release")?; - 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: server_binary_arguments(), - }) - } - - 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!["--help".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 == "omnisharp") - { - last_binary_path = Some(entry.path()); - } - } - - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - env: None, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() -} - -fn server_binary_arguments() -> Vec { - vec!["-lsp".into()] -} diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 90e3f181b8..d772ee7f21 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -13,7 +13,6 @@ use self::{deno::DenoSettings, elixir::ElixirSettings}; mod c; mod clojure; -mod csharp; mod css; mod dart; mod deno; @@ -60,7 +59,6 @@ pub fn init( languages.register_native_grammars([ ("bash", tree_sitter_bash::language()), ("c", tree_sitter_c::language()), - ("c_sharp", tree_sitter_c_sharp::language()), ("clojure", tree_sitter_clojure::language()), ("cpp", tree_sitter_cpp::language()), ("css", tree_sitter_css::language()), @@ -164,7 +162,6 @@ pub fn init( language!("c", vec![Arc::new(c::CLspAdapter) as Arc]); language!("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]); language!("cpp", vec![Arc::new(c::CLspAdapter)]); - language!("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]); language!( "css", vec![ diff --git a/extensions/csharp/Cargo.toml b/extensions/csharp/Cargo.toml new file mode 100644 index 0000000000..c5ff9868c6 --- /dev/null +++ b/extensions/csharp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "zed_csharp" +version = "0.0.1" +edition = "2021" +publish = false +license = "Apache-2.0" + +[lints] +workspace = true + +[lib] +path = "src/csharp.rs" +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "0.0.4" diff --git a/extensions/csharp/LICENSE-APACHE b/extensions/csharp/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/extensions/csharp/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/extensions/csharp/extension.toml b/extensions/csharp/extension.toml new file mode 100644 index 0000000000..31c9d328c0 --- /dev/null +++ b/extensions/csharp/extension.toml @@ -0,0 +1,15 @@ +id = "csharp" +name = "C#" +description = "C# support." +version = "0.0.1" +schema_version = 1 +authors = ["fminkowski "] +repository = "https://github.com/zed-industries/zed" + +[language_servers.omnisharp] +name = "OmniSharp" +language = "CSharp" + +[grammars.c_sharp] +repository = "https://github.com/tree-sitter/tree-sitter-c-sharp" +commit = "dd5e59721a5f8dae34604060833902b882023aaf" diff --git a/crates/languages/src/csharp/config.toml b/extensions/csharp/languages/csharp/config.toml similarity index 100% rename from crates/languages/src/csharp/config.toml rename to extensions/csharp/languages/csharp/config.toml diff --git a/crates/languages/src/csharp/highlights.scm b/extensions/csharp/languages/csharp/highlights.scm similarity index 100% rename from crates/languages/src/csharp/highlights.scm rename to extensions/csharp/languages/csharp/highlights.scm diff --git a/crates/languages/src/csharp/injections.scm b/extensions/csharp/languages/csharp/injections.scm similarity index 100% rename from crates/languages/src/csharp/injections.scm rename to extensions/csharp/languages/csharp/injections.scm diff --git a/crates/languages/src/csharp/outline.scm b/extensions/csharp/languages/csharp/outline.scm similarity index 100% rename from crates/languages/src/csharp/outline.scm rename to extensions/csharp/languages/csharp/outline.scm diff --git a/extensions/csharp/src/csharp.rs b/extensions/csharp/src/csharp.rs new file mode 100644 index 0000000000..72a2e69aee --- /dev/null +++ b/extensions/csharp/src/csharp.rs @@ -0,0 +1,116 @@ +use std::fs; +use zed_extension_api::{self as zed, Result}; + +struct CsharpExtension { + cached_binary_path: Option, +} + +impl CsharpExtension { + fn language_server_binary_path( + &mut self, + config: zed::LanguageServerConfig, + 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("OmniSharp") { + self.cached_binary_path = Some(path.clone()); + return Ok(path); + } + + zed::set_language_server_installation_status( + &config.name, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + let release = zed::latest_github_release( + "OmniSharp/omnisharp-roslyn", + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let (platform, arch) = zed::current_platform(); + let asset_name = format!( + "omnisharp-{os}-{arch}-net6.0.{extension}", + os = match platform { + zed::Os::Mac => "osx", + zed::Os::Linux => "linux", + zed::Os::Windows => "win", + }, + arch = match arch { + zed::Architecture::Aarch64 => "arm64", + zed::Architecture::X86 => "x86", + zed::Architecture::X8664 => "x64", + }, + extension = match platform { + zed::Os::Mac | zed::Os::Linux => "tar.gz", + zed::Os::Windows => "zip", + } + ); + + 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!("omnisharp-{}", release.version); + let binary_path = format!("{version_dir}/OmniSharp"); + + if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + zed::set_language_server_installation_status( + &config.name, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &version_dir, + match platform { + zed::Os::Mac | zed::Os::Linux => zed::DownloadedFileType::GzipTar, + zed::Os::Windows => zed::DownloadedFileType::Zip, + }, + ) + .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 CsharpExtension { + fn new() -> Self { + Self { + cached_binary_path: None, + } + } + + fn language_server_command( + &mut self, + config: zed::LanguageServerConfig, + worktree: &zed::Worktree, + ) -> Result { + Ok(zed::Command { + command: self.language_server_binary_path(config, worktree)?, + args: vec!["-lsp".to_string()], + env: Default::default(), + }) + } +} + +zed::register_extension!(CsharpExtension);