diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 95fe1312183a3412509375050b1e1ff67642ef3e..e8f5c2e3c26be5597c229dfbbc0411d0ca837043 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -89,7 +89,7 @@ pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { let py_lsp_adapter = Arc::new(python::PyLspAdapter::new()); let python_context_provider = Arc::new(python::PythonContextProvider); let python_lsp_adapter = Arc::new(python::PythonLspAdapter::new(node.clone())); - let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new()); + let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new(node.clone())); let python_toolchain_provider = Arc::new(python::PythonToolchainProvider); let rust_context_provider = Arc::new(rust::RustContextProvider); let rust_lsp_adapter = Arc::new(rust::RustLspAdapter); diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 2a8f087e1f5b1a03be36b7e49d2ca79fc13ff988..4d109cf15ba34e72d6ad287eed38b4cfddc8c494 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -29,7 +29,6 @@ use std::str::FromStr; use std::{ any::Any, borrow::Cow, - ffi::OsString, fmt::Write, path::{Path, PathBuf}, sync::Arc, @@ -63,9 +62,6 @@ impl ManifestProvider for PyprojectTomlManifestProvider { } } -const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; -const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; - enum TestRunner { UNITTEST, PYTEST, @@ -83,10 +79,6 @@ impl FromStr for TestRunner { } } -fn server_binary_arguments(server_path: &Path) -> Vec { - vec![server_path.into(), "--stdio".into()] -} - /// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. /// Where `XX` is the sorting category, `YYYY` is based on most recent usage, /// and `name` is the symbol name itself. @@ -105,10 +97,29 @@ pub struct PythonLspAdapter { impl PythonLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright"); + const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; + const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; pub fn new(node: NodeRuntime) -> Self { PythonLspAdapter { node } } + + async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, + ) -> Option { + let server_path = container_dir.join(Self::SERVER_PATH); + if server_path.exists() { + Some(LanguageServerBinary { + path: node.binary_path().await.log_err()?, + env: None, + arguments: vec![server_path.into(), "--stdio".into()], + }) + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } + } } #[async_trait(?Send)] @@ -155,13 +166,13 @@ impl LspAdapter for PythonLspAdapter { .await .log_err()??; - let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); + let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH); let env = delegate.shell_env().await; Some(LanguageServerBinary { path: node, env: Some(env), - arguments: server_binary_arguments(&path), + arguments: vec![path.into(), "--stdio".into()], }) } } @@ -185,7 +196,7 @@ impl LspAdapter for PythonLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Result { let latest_version = latest_version.downcast::().unwrap(); - let server_path = container_dir.join(SERVER_PATH); + let server_path = container_dir.join(Self::SERVER_PATH); self.node .npm_install_packages( @@ -198,7 +209,7 @@ impl LspAdapter for PythonLspAdapter { Ok(LanguageServerBinary { path: self.node.binary_path().await?, env: Some(env), - arguments: server_binary_arguments(&server_path), + arguments: vec![server_path.into(), "--stdio".into()], }) } @@ -209,7 +220,7 @@ impl LspAdapter for PythonLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Option { let version = version.downcast_ref::().unwrap(); - let server_path = container_dir.join(SERVER_PATH); + let server_path = container_dir.join(Self::SERVER_PATH); let should_install_language_server = self .node @@ -228,7 +239,7 @@ impl LspAdapter for PythonLspAdapter { Some(LanguageServerBinary { path: self.node.binary_path().await.ok()?, env: Some(env), - arguments: server_binary_arguments(&server_path), + arguments: vec![server_path.into(), "--stdio".into()], }) } } @@ -238,7 +249,7 @@ impl LspAdapter for PythonLspAdapter { container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option { - let mut binary = get_cached_server_binary(container_dir, &self.node).await?; + let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?; binary.env = Some(delegate.shell_env().await); Some(binary) } @@ -392,23 +403,6 @@ impl LspAdapter for PythonLspAdapter { } } -async fn get_cached_server_binary( - container_dir: PathBuf, - node: &NodeRuntime, -) -> Option { - let server_path = container_dir.join(SERVER_PATH); - if server_path.exists() { - Some(LanguageServerBinary { - path: node.binary_path().await.log_err()?, - env: None, - arguments: server_binary_arguments(&server_path), - }) - } else { - log::error!("missing executable in directory {:?}", server_path); - None - } -} - pub(crate) struct PythonContextProvider; const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName = @@ -1340,64 +1334,34 @@ impl LspAdapter for PyLspAdapter { } pub(crate) struct BasedPyrightLspAdapter { - python_venv_base: OnceCell, String>>, + node: NodeRuntime, } impl BasedPyrightLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("basedpyright"); const BINARY_NAME: &'static str = "basedpyright-langserver"; + const SERVER_PATH: &str = "node_modules/basedpyright/langserver.index.js"; + const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "basedpyright/langserver.index.js"; - pub(crate) fn new() -> Self { - Self { - python_venv_base: OnceCell::new(), - } - } - - async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result> { - let python_path = Self::find_base_python(delegate) - .await - .context("Could not find Python installation for basedpyright")?; - let work_dir = delegate - .language_server_download_dir(&Self::SERVER_NAME) - .await - .context("Could not get working directory for basedpyright")?; - let mut path = PathBuf::from(work_dir.as_ref()); - path.push("basedpyright-venv"); - if !path.exists() { - util::command::new_smol_command(python_path) - .arg("-m") - .arg("venv") - .arg("basedpyright-venv") - .current_dir(work_dir) - .spawn() - .context("spawning child")? - .output() - .await - .context("getting child output")?; - } - - Ok(path.into()) + pub(crate) fn new(node: NodeRuntime) -> Self { + BasedPyrightLspAdapter { node } } - // Find "baseline", user python version from which we'll create our own venv. - async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option { - for path in ["python3", "python"] { - if let Some(path) = delegate.which(path.as_ref()).await { - return Some(path); - } - } - None - } - - async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result, String> { - self.python_venv_base - .get_or_init(move || async move { - Self::ensure_venv(delegate) - .await - .map_err(|e| format!("{e}")) + async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, + ) -> Option { + let server_path = container_dir.join(Self::SERVER_PATH); + if server_path.exists() { + Some(LanguageServerBinary { + path: node.binary_path().await.log_err()?, + env: None, + arguments: vec![server_path.into(), "--stdio".into()], }) - .await - .clone() + } else { + log::error!("missing executable in directory {:?}", server_path); + None + } } } @@ -1407,101 +1371,131 @@ impl LspAdapter for BasedPyrightLspAdapter { Self::SERVER_NAME } - async fn initialization_options( - self: Arc, - _: &dyn Fs, - _: &Arc, - ) -> Result> { - // Provide minimal initialization options - // Virtual environment configuration will be handled through workspace configuration - Ok(Some(json!({ - "python": { - "analysis": { - "autoSearchPaths": true, - "useLibraryCodeForTypes": true, - "autoImportCompletions": true - } - } - }))) + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: &AsyncApp, + ) -> Result> { + Ok(Box::new( + self.node + .npm_package_latest_version(Self::SERVER_NAME.as_ref()) + .await?, + ) as Box<_>) } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, - toolchain: Option, + _: Option, _: &AsyncApp, ) -> Option { - if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await { + if let Some(path) = delegate.which(Self::BINARY_NAME.as_ref()).await { let env = delegate.shell_env().await; Some(LanguageServerBinary { - path: bin, + path, env: Some(env), arguments: vec!["--stdio".into()], }) } else { - let path = Path::new(toolchain?.path.as_ref()) - .parent()? - .join(Self::BINARY_NAME); - path.exists().then(|| LanguageServerBinary { - path, - arguments: vec!["--stdio".into()], - env: None, + // TODO shouldn't this be self.node.binary_path()? + 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(Self::NODE_MODULE_RELATIVE_SERVER_PATH); + + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: node, + env: Some(env), + arguments: vec![path.into(), "--stdio".into()], }) } } - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new(()) as Box<_>) - } - async fn fetch_server_binary( &self, - _latest_version: Box, - _container_dir: PathBuf, + latest_version: Box, + container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { - let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; - let pip_path = venv.join(BINARY_DIR).join("pip3"); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("basedpyright") - .arg("-U") - .output() - .await? - .status - .success(), - "basedpyright installation failed" - ); - let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - delegate - .which(path.as_os_str()) - .await - .context("basedpyright installation was incomplete")?; + let latest_version = latest_version.downcast::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + + self.node + .npm_install_packages( + &container_dir, + &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], + ) + .await?; + + let env = delegate.shell_env().await; Ok(LanguageServerBinary { - path, - env: None, - arguments: vec!["--stdio".into()], + path: self.node.binary_path().await?, + env: Some(env), + arguments: vec![server_path.into(), "--stdio".into()], }) } + async fn check_if_version_installed( + &self, + version: &(dyn 'static + Send + Any), + container_dir: &PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let version = version.downcast_ref::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + + let should_install_language_server = self + .node + .should_install_npm_package( + Self::SERVER_NAME.as_ref(), + &server_path, + container_dir, + VersionStrategy::Latest(version), + ) + .await; + + if should_install_language_server { + None + } else { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: self.node.binary_path().await.ok()?, + env: Some(env), + arguments: vec![server_path.into(), "--stdio".into()], + }) + } + } + async fn cached_server_binary( &self, - _container_dir: PathBuf, + container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Option { - let venv = self.base_venv(delegate).await.ok()?; - let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - delegate.which(path.as_os_str()).await?; - Some(LanguageServerBinary { - path, - env: None, - arguments: vec!["--stdio".into()], - }) + let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?; + binary.env = Some(delegate.shell_env().await); + Some(binary) + } + + async fn initialization_options( + self: Arc, + _: &dyn Fs, + _: &Arc, + ) -> Result> { + // Provide minimal initialization options + // Virtual environment configuration will be handled through workspace configuration + Ok(Some(json!({ + "python": { + "analysis": { + "autoSearchPaths": true, + "useLibraryCodeForTypes": true, + "autoImportCompletions": true + } + } + }))) } async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {