diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4c75ef4eeb..fad799da19 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -313,6 +313,10 @@ pub trait LspAdapterDelegate: Send + Sync { fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus); async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option>; + async fn npm_package_installed_version( + &self, + package_name: &str, + ) -> Result>; async fn which(&self, command: &OsStr) -> Option; async fn shell_env(&self) -> HashMap; async fn read_text_file(&self, path: PathBuf) -> Result; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 964abf42b5..4b5fe3d277 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -20,6 +20,7 @@ use task::{TaskTemplate, TaskTemplates, VariableName}; use util::ResultExt; const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; +const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] @@ -43,6 +44,26 @@ impl LspAdapter for PythonLspAdapter { Self::SERVER_NAME.clone() } + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + _: &AsyncAppContext, + ) -> Option { + let node = delegate.which("node".as_ref()).await?; + let (node_modules_path, _) = delegate + .npm_package_installed_version(Self::SERVER_NAME.as_ref()) + .await + .log_err()??; + + let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); + + Some(LanguageServerBinary { + path: node, + env: None, + arguments: server_binary_arguments(&path), + }) + } + async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 9507eb7536..0f0512c65e 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -177,6 +177,7 @@ impl NodeRuntime { "5000", ]); + // This is also wrong because the directory is wrong. self.run_npm_subcommand(directory, "install", &arguments) .await?; Ok(()) @@ -576,7 +577,7 @@ impl NodeRuntimeTrait for SystemNodeRuntime { } } -async fn read_package_installed_version( +pub async fn read_package_installed_version( node_module_directory: PathBuf, name: &str, ) -> Result> { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 21d5de53e6..bef57bafb4 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -48,6 +48,7 @@ use lsp::{ LspRequestFuture, MessageActionItem, MessageType, OneOf, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams, WorkspaceFolder, }; +use node_runtime::read_package_installed_version; use parking_lot::{Mutex, RwLock}; use postage::watch; use rand::prelude::*; @@ -7801,6 +7802,44 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { task.await.unwrap_or_default() } + async fn npm_package_installed_version( + &self, + package_name: &str, + ) -> Result> { + let local_package_directory = self.worktree_root_path(); + let node_modules_directory = local_package_directory.join("node_modules"); + + if let Some(version) = + read_package_installed_version(node_modules_directory.clone(), package_name).await? + { + return Ok(Some((node_modules_directory, version))); + } + let Some(npm) = self.which("npm".as_ref()).await else { + log::warn!( + "Failed to find npm executable for {:?}", + local_package_directory + ); + return Ok(None); + }; + + let env = self.shell_env().await; + let output = smol::process::Command::new(&npm) + .args(["root", "-g"]) + .envs(env) + .current_dir(local_package_directory) + .output() + .await?; + let global_node_modules = + PathBuf::from(String::from_utf8_lossy(&output.stdout).to_string()); + + if let Some(version) = + read_package_installed_version(global_node_modules.clone(), package_name).await? + { + return Ok(Some((global_node_modules, version))); + } + return Ok(None); + } + #[cfg(not(target_os = "windows"))] async fn which(&self, command: &OsStr) -> Option { let worktree_abs_path = self.worktree.abs_path(); @@ -7883,6 +7922,13 @@ impl LspAdapterDelegate for SshLspAdapterDelegate { .ok(); } + async fn npm_package_installed_version( + &self, + _package_name: &str, + ) -> Result> { + Ok(None) + } + fn http_client(&self) -> Arc { Arc::new(BlockedHttpClient) }