From f444c433caa21d96f7d81171e43ce6bbd6555b08 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Sat, 29 Aug 2026 07:59:41 +0200 Subject: [PATCH] feat: remove command_api Zed now resolves bare executables against the child `PATH` on macOS before `posix_spawnp`. See zed-industries/zed#55672, so extension-side ruby/gem/bundle probes no longer need a compile-time workaround. Refs zed-industries/zed#57170 --- Cargo.toml | 4 - README.md | 24 ---- src/command_executor.rs | 2 - src/language_servers/language_server.rs | 174 ------------------------ src/ruby.rs | 17 --- 5 files changed, 221 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ef952f..8e37516 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,6 @@ license = "Apache-2.0" path = "src/ruby.rs" crate-type = ["cdylib"] -[features] -default = [] -command_api = [] - [dependencies] regex = "1.11.1" serde_json = "1.0" diff --git a/README.md b/README.md index cec4818..38a1209 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,3 @@ # Ruby extension for Zed [Documentation](https://zed.dev/docs/languages/ruby) - -## Command-free LSP build - -The default build does not run extension-side process commands for Ruby LSP -startup. It uses configured `lsp..binary.path` values first, then falls -back to `worktree.which`. If `use_bundler` is enabled, it launches through -`bundle exec ` without probing Bundler. - -```sh -cargo test -``` - -To enable the command API path for project gem detection and extension-managed -language server/debug gem installation, build with: - -```sh -cargo test --features command_api -``` - -This is not a replacement for fixing Zed's command spawning behavior: -https://github.com/zed-industries/zed/issues/57170. The command-free profile -expects `bundle` or the language server executable to be available from the -project environment. Debugging expects `rdbg` to be available from that same -environment. diff --git a/src/command_executor.rs b/src/command_executor.rs index e74cb85..8bcad66 100644 --- a/src/command_executor.rs +++ b/src/command_executor.rs @@ -26,11 +26,9 @@ pub trait CommandExecutor { /// An implementation of `CommandExecutor` that executes commands /// using the `zed_extension_api::Command`. -#[cfg(feature = "command_api")] #[derive(Clone)] pub struct RealCommandExecutor; -#[cfg(feature = "command_api")] impl CommandExecutor for RealCommandExecutor { fn execute( &self, diff --git a/src/language_servers/language_server.rs b/src/language_servers/language_server.rs index 0e15411..7706e7f 100644 --- a/src/language_servers/language_server.rs +++ b/src/language_servers/language_server.rs @@ -1,13 +1,11 @@ #[cfg(test)] use std::collections::HashMap; -#[cfg(feature = "command_api")] use crate::{ bundler::Bundler, command_executor::RealCommandExecutor, gemset::{versioned_gem_home, Gemset}, }; -#[cfg(feature = "command_api")] use std::path::PathBuf; use zed_extension_api::{self as zed}; @@ -32,8 +30,6 @@ pub trait WorktreeLike { fn shell_env(&self) -> Vec<(String, String)>; fn read_text_file(&self, path: &str) -> Result; fn lsp_binary_settings(&self, server_id: &str) -> Result, String>; - #[cfg(any(test, not(feature = "command_api")))] - fn use_bundler(&self, server_id: &str) -> Result, String>; fn which(&self, name: &str) -> Option; } @@ -60,16 +56,6 @@ impl WorktreeLike for zed::Worktree { } } - #[cfg(any(test, not(feature = "command_api")))] - fn use_bundler(&self, server_id: &str) -> Result, String> { - zed::settings::LspSettings::for_worktree(server_id, self).map(|lsp_settings| { - lsp_settings - .settings - .as_ref() - .and_then(|settings| settings["use_bundler"].as_bool()) - }) - } - fn which(&self, name: &str) -> Option { zed::Worktree::which(self, name) } @@ -81,7 +67,6 @@ pub struct FakeWorktree { shell_env: Vec<(String, String)>, files: HashMap>, lsp_binary_settings_map: HashMap, String>>, - use_bundler_map: HashMap, String>>, which_map: HashMap>, } @@ -93,7 +78,6 @@ impl FakeWorktree { shell_env: Vec::new(), files: HashMap::new(), lsp_binary_settings_map: HashMap::new(), - use_bundler_map: HashMap::new(), which_map: HashMap::new(), } } @@ -110,10 +94,6 @@ impl FakeWorktree { self.lsp_binary_settings_map.insert(server_id, settings); } - pub fn set_use_bundler(&mut self, server_id: String, value: Result, String>) { - self.use_bundler_map.insert(server_id, value); - } - pub fn set_which(&mut self, name: String, result: Option) { self.which_map.insert(name, result); } @@ -143,13 +123,6 @@ impl WorktreeLike for FakeWorktree { .unwrap_or(Ok(None)) } - fn use_bundler(&self, server_id: &str) -> Result, String> { - self.use_bundler_map - .get(server_id) - .cloned() - .unwrap_or(Ok(None)) - } - fn which(&self, name: &str) -> Option { self.which_map.get(name).cloned().flatten() } @@ -193,12 +166,6 @@ pub trait LanguageServer { language_server_id: &zed::LanguageServerId, worktree: &zed::Worktree, ) -> zed::Result { - #[cfg(not(feature = "command_api"))] - { - self.command_free_language_server_binary(language_server_id.as_ref(), worktree) - } - - #[cfg(feature = "command_api")] { let lsp_settings = zed::settings::LspSettings::for_worktree(language_server_id.as_ref(), worktree)?; @@ -252,56 +219,6 @@ pub trait LanguageServer { } } - #[cfg(any(test, not(feature = "command_api")))] - fn command_free_language_server_binary( - &self, - server_id: &str, - worktree: &T, - ) -> zed::Result { - if let Some(binary_settings) = worktree.lsp_binary_settings(server_id)? { - if let Some(path) = binary_settings.path { - return Ok(LanguageServerBinary { - path, - args: binary_settings.arguments, - env: Some(worktree.shell_env()), - }); - } - } - - let use_bundler = worktree - .use_bundler(server_id)? - .unwrap_or_else(Self::default_use_bundler); - - if use_bundler { - if let Some(bundle_path) = worktree.which("bundle") { - return Ok(LanguageServerBinary { - path: bundle_path, - args: Some( - vec!["exec".into(), Self::EXECUTABLE_NAME.into()] - .into_iter() - .chain(self.get_executable_args(worktree)) - .collect(), - ), - env: Some(worktree.shell_env()), - }); - } - } - - if let Some(path) = worktree.which(Self::EXECUTABLE_NAME) { - return Ok(LanguageServerBinary { - path, - args: Some(self.get_executable_args(worktree)), - env: Some(worktree.shell_env()), - }); - } - - Err(format!( - "Unable to find 'bundle' or '{}' command for {server_id}. Install one in the project environment or configure lsp.{server_id}.binary.path.", - Self::EXECUTABLE_NAME - )) - } - - #[cfg(feature = "command_api")] fn try_find_on_path_or_extension_gemset( &self, language_server_id: &zed::LanguageServerId, @@ -318,7 +235,6 @@ pub trait LanguageServer { } } - #[cfg(feature = "command_api")] fn extension_gemset_language_server_binary( &self, language_server_id: &zed::LanguageServerId, @@ -454,94 +370,4 @@ mod tests { let mock_worktree = FakeWorktree::new("/path/to/project".to_string()); assert_eq!(mock_worktree.shell_env(), Vec::<(String, String)>::new()); } - - #[test] - fn test_command_free_uses_bundle_exec_when_use_bundler_enabled() { - let test_server = TestServer::new(); - let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string()); - mock_worktree.set_use_bundler(TestServer::SERVER_ID.to_string(), Ok(Some(true))); - mock_worktree.set_which("bundle".to_string(), Some("/bin/bundle".to_string())); - - let binary = test_server - .command_free_language_server_binary(TestServer::SERVER_ID, &mock_worktree) - .expect("command-free resolver should find bundle"); - - assert_eq!(binary.path, "/bin/bundle"); - assert_eq!( - binary.args, - Some(vec![ - "exec".to_string(), - "test-exe".to_string(), - "--test-arg".to_string() - ]) - ); - } - - #[test] - fn test_command_free_falls_back_to_executable_when_bundle_missing() { - let test_server = TestServer::new(); - let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string()); - mock_worktree.set_use_bundler(TestServer::SERVER_ID.to_string(), Ok(Some(true))); - mock_worktree.set_which("bundle".to_string(), None); - mock_worktree.set_which("test-exe".to_string(), Some("/bin/test-exe".to_string())); - - let binary = test_server - .command_free_language_server_binary(TestServer::SERVER_ID, &mock_worktree) - .expect("command-free resolver should fall back to executable"); - - assert_eq!(binary.path, "/bin/test-exe"); - assert_eq!(binary.args, Some(vec!["--test-arg".to_string()])); - } - - #[test] - fn test_command_free_uses_configured_binary_before_bundler() { - let test_server = TestServer::new(); - let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string()); - mock_worktree.set_use_bundler(TestServer::SERVER_ID.to_string(), Ok(Some(true))); - mock_worktree.add_lsp_binary_setting( - TestServer::SERVER_ID.to_string(), - Ok(Some(super::LspBinarySettings { - path: Some("/custom/test-exe".to_string()), - arguments: Some(vec!["--custom".to_string()]), - })), - ); - - let binary = test_server - .command_free_language_server_binary(TestServer::SERVER_ID, &mock_worktree) - .expect("command-free resolver should use configured binary"); - - assert_eq!(binary.path, "/custom/test-exe"); - assert_eq!(binary.args, Some(vec!["--custom".to_string()])); - } - - #[test] - fn test_command_free_uses_path_lookup_when_use_bundler_disabled() { - let test_server = TestServer::new(); - let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string()); - mock_worktree.set_use_bundler(TestServer::SERVER_ID.to_string(), Ok(Some(false))); - mock_worktree.set_which("test-exe".to_string(), Some("/bin/test-exe".to_string())); - - let binary = test_server - .command_free_language_server_binary(TestServer::SERVER_ID, &mock_worktree) - .expect("command-free resolver should find server executable"); - - assert_eq!(binary.path, "/bin/test-exe"); - assert_eq!(binary.args, Some(vec!["--test-arg".to_string()])); - } - - #[test] - fn test_command_free_missing_executable_errors() { - let test_server = TestServer::new(); - let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string()); - mock_worktree.set_use_bundler(TestServer::SERVER_ID.to_string(), Ok(Some(true))); - mock_worktree.set_which("bundle".to_string(), None); - mock_worktree.set_which("test-exe".to_string(), None); - - let error = test_server - .command_free_language_server_binary(TestServer::SERVER_ID, &mock_worktree) - .expect_err("command-free resolver should fail when executable is missing"); - - assert!(error.contains("Unable to find 'bundle' or 'test-exe' command")); - assert!(error.contains("lsp.test-server.binary.path")); - } } diff --git a/src/ruby.rs b/src/ruby.rs index dbde9c1..e1acc65 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -1,20 +1,13 @@ -#[cfg(feature = "command_api")] mod bundler; -#[cfg(feature = "command_api")] mod command_executor; -#[cfg(feature = "command_api")] mod gemset; mod language_servers; use std::collections::HashMap; -#[cfg(feature = "command_api")] use std::path::PathBuf; -#[cfg(feature = "command_api")] use bundler::Bundler; -#[cfg(feature = "command_api")] use command_executor::RealCommandExecutor; -#[cfg(feature = "command_api")] use gemset::{versioned_gem_home, Gemset}; use language_servers::{ FuzzyRubyServer, Herb, Kanayago, LanguageServer, Rubocop, RubyLsp, Solargraph, Sorbet, Steep, @@ -149,7 +142,6 @@ impl zed::Extension for RubyExtension { _: Option, worktree: &Worktree, ) -> Result { - #[cfg(feature = "command_api")] let (command, mut arguments) = { let shell_env = worktree.shell_env(); let env_vars: Vec<(&str, &str)> = shell_env @@ -181,15 +173,6 @@ impl zed::Extension for RubyExtension { } }; - #[cfg(not(feature = "command_api"))] - let (command, mut arguments) = if let Some(path) = worktree.which(&adapter_name) { - (path, Vec::new()) - } else { - return Err(format!( - "Unable to find '{adapter_name}' command in the project environment" - )); - }; - let tcp_connection = config.tcp_connection.unwrap_or(TcpArgumentsTemplate { port: None, host: None,