diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1df33286ee..7cda2b4b5a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -313,6 +313,15 @@ impl Attach { } } +/// Determines what gets sent out as a workspace folders content +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WorkspaceFoldersContent { + /// Send out a single entry with the root of the workspace. + WorktreeRoot, + /// Send out a list of subproject roots. + SubprojectRoots, +} + /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application // e.g. to display a notification or fetch data from the web. #[async_trait] @@ -606,6 +615,13 @@ pub trait LspAdapter: 'static + Send + Sync { Attach::Shared } + /// Determines whether a language server supports workspace folders. + /// + /// And does not trip over itself in the process. + fn workspace_folders_content(&self) -> WorkspaceFoldersContent { + WorkspaceFoldersContent::SubprojectRoots + } + fn manifest_name(&self) -> Option { None } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index d1a90d7dbb..2b0e13f4be 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -867,7 +867,7 @@ impl LspLogView { BINARY = server.binary(), WORKSPACE_FOLDERS = server .workspace_folders() - .iter() + .into_iter() .filter_map(|path| path .to_file_path() .ok() diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index dc6996d399..ebdbd93248 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -4,13 +4,13 @@ use async_trait::async_trait; use collections::HashMap; use gpui::{App, Task}; use gpui::{AsyncApp, SharedString}; -use language::Toolchain; use language::ToolchainList; use language::ToolchainLister; use language::language_settings::language_settings; use language::{ContextLocation, LanguageToolchainStore}; use language::{ContextProvider, LspAdapter, LspAdapterDelegate}; use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery}; +use language::{Toolchain, WorkspaceFoldersContent}; use lsp::LanguageServerBinary; use lsp::LanguageServerName; use node_runtime::NodeRuntime; @@ -400,6 +400,9 @@ impl LspAdapter for PythonLspAdapter { fn manifest_name(&self) -> Option { Some(SharedString::new_static("pyproject.toml").into()) } + fn workspace_folders_content(&self) -> WorkspaceFoldersContent { + WorkspaceFoldersContent::WorktreeRoot + } } async fn get_cached_server_binary( @@ -1282,6 +1285,9 @@ impl LspAdapter for PyLspAdapter { fn manifest_name(&self) -> Option { Some(SharedString::new_static("pyproject.toml").into()) } + fn workspace_folders_content(&self) -> WorkspaceFoldersContent { + WorkspaceFoldersContent::WorktreeRoot + } } #[cfg(test)] diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 9978d7ebb1..ccb39ab8a2 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -29,7 +29,7 @@ use std::{ ffi::{OsStr, OsString}, fmt, io::Write, - ops::{Deref, DerefMut}, + ops::DerefMut, path::PathBuf, pin::Pin, sync::{ @@ -100,7 +100,7 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, server: Arc>>, - workspace_folders: Arc>>, + workspace_folders: Option>>>, root_uri: Url, } @@ -307,7 +307,7 @@ impl LanguageServer { binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option>, - workspace_folders: Arc>>, + workspace_folders: Option>>>, cx: &mut AsyncApp, ) -> Result { let working_dir = if root_path.is_dir() { @@ -381,7 +381,7 @@ impl LanguageServer { code_action_kinds: Option>, binary: LanguageServerBinary, root_uri: Url, - workspace_folders: Arc>>, + workspace_folders: Option>>>, cx: &mut AsyncApp, on_unhandled_notification: F, ) -> Self @@ -595,16 +595,26 @@ impl LanguageServer { } pub fn default_initialize_params(&self, pull_diagnostics: bool, cx: &App) -> InitializeParams { - let workspace_folders = self - .workspace_folders - .lock() - .iter() - .cloned() - .map(|uri| WorkspaceFolder { - name: Default::default(), - uri, - }) - .collect::>(); + let workspace_folders = self.workspace_folders.as_ref().map_or_else( + || { + vec![WorkspaceFolder { + name: Default::default(), + uri: self.root_uri.clone(), + }] + }, + |folders| { + folders + .lock() + .iter() + .cloned() + .map(|uri| WorkspaceFolder { + name: Default::default(), + uri, + }) + .collect() + }, + ); + #[allow(deprecated)] InitializeParams { process_id: None, @@ -1315,7 +1325,10 @@ impl LanguageServer { return; } - let is_new_folder = self.workspace_folders.lock().insert(uri.clone()); + let Some(workspace_folders) = self.workspace_folders.as_ref() else { + return; + }; + let is_new_folder = workspace_folders.lock().insert(uri.clone()); if is_new_folder { let params = DidChangeWorkspaceFoldersParams { event: WorkspaceFoldersChangeEvent { @@ -1345,7 +1358,10 @@ impl LanguageServer { { return; } - let was_removed = self.workspace_folders.lock().remove(&uri); + let Some(workspace_folders) = self.workspace_folders.as_ref() else { + return; + }; + let was_removed = workspace_folders.lock().remove(&uri); if was_removed { let params = DidChangeWorkspaceFoldersParams { event: WorkspaceFoldersChangeEvent { @@ -1360,7 +1376,10 @@ impl LanguageServer { } } pub fn set_workspace_folders(&self, folders: BTreeSet) { - let mut workspace_folders = self.workspace_folders.lock(); + let Some(workspace_folders) = self.workspace_folders.as_ref() else { + return; + }; + let mut workspace_folders = workspace_folders.lock(); let old_workspace_folders = std::mem::take(&mut *workspace_folders); let added: Vec<_> = folders @@ -1389,8 +1408,11 @@ impl LanguageServer { } } - pub fn workspace_folders(&self) -> impl Deref> + '_ { - self.workspace_folders.lock() + pub fn workspace_folders(&self) -> BTreeSet { + self.workspace_folders.as_ref().map_or_else( + || BTreeSet::from_iter([self.root_uri.clone()]), + |folders| folders.lock().clone(), + ) } pub fn register_buffer( @@ -1535,7 +1557,7 @@ impl FakeLanguageServer { None, binary.clone(), root, - workspace_folders.clone(), + Some(workspace_folders.clone()), cx, |_| {}, ); @@ -1554,7 +1576,7 @@ impl FakeLanguageServer { None, binary, Self::root_path(), - workspace_folders, + Some(workspace_folders), cx, move |msg| { notifications_tx diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 0cd375e0c5..3645839271 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -46,6 +46,7 @@ use language::{ DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName, LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, + WorkspaceFoldersContent, language_settings::{ FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings, }, @@ -217,6 +218,7 @@ impl LocalLspStore { let binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx); let pending_workspace_folders: Arc>> = Default::default(); + let pending_server = cx.spawn({ let adapter = adapter.clone(); let server_name = adapter.name.clone(); @@ -242,14 +244,18 @@ impl LocalLspStore { return Ok(server); } + let code_action_kinds = adapter.code_action_kinds(); lsp::LanguageServer::new( stderr_capture, server_id, server_name, binary, &root_path, - adapter.code_action_kinds(), - pending_workspace_folders, + code_action_kinds, + Some(pending_workspace_folders).filter(|_| { + adapter.adapter.workspace_folders_content() + == WorkspaceFoldersContent::SubprojectRoots + }), cx, ) } @@ -575,8 +581,7 @@ impl LocalLspStore { }; let root = server.workspace_folders(); Ok(Some( - root.iter() - .cloned() + root.into_iter() .map(|uri| WorkspaceFolder { uri, name: Default::default(),