lsp/python: Temporarily report just a singular workspace folder instead of all of the roots (#35243)

Temporarily fixes #29133

Co-authored-by: Cole <cole@zed.dev>

Release Notes:

- python: Zed now reports a slightly different set of workspace folders
for Python projects to work around quirks in handling of multi-lsp
projects with virtual environment. This behavior will be revisited in a
near future.

Co-authored-by: Cole <cole@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-07-29 02:10:32 +02:00 committed by GitHub
parent d2ef287791
commit e5269212ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 76 additions and 27 deletions

View file

@ -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 /// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
// e.g. to display a notification or fetch data from the web. // e.g. to display a notification or fetch data from the web.
#[async_trait] #[async_trait]
@ -606,6 +615,13 @@ pub trait LspAdapter: 'static + Send + Sync {
Attach::Shared 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<ManifestName> { fn manifest_name(&self) -> Option<ManifestName> {
None None
} }

View file

@ -867,7 +867,7 @@ impl LspLogView {
BINARY = server.binary(), BINARY = server.binary(),
WORKSPACE_FOLDERS = server WORKSPACE_FOLDERS = server
.workspace_folders() .workspace_folders()
.iter() .into_iter()
.filter_map(|path| path .filter_map(|path| path
.to_file_path() .to_file_path()
.ok() .ok()

View file

@ -4,13 +4,13 @@ use async_trait::async_trait;
use collections::HashMap; use collections::HashMap;
use gpui::{App, Task}; use gpui::{App, Task};
use gpui::{AsyncApp, SharedString}; use gpui::{AsyncApp, SharedString};
use language::Toolchain;
use language::ToolchainList; use language::ToolchainList;
use language::ToolchainLister; use language::ToolchainLister;
use language::language_settings::language_settings; use language::language_settings::language_settings;
use language::{ContextLocation, LanguageToolchainStore}; use language::{ContextLocation, LanguageToolchainStore};
use language::{ContextProvider, LspAdapter, LspAdapterDelegate}; use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery}; use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
use language::{Toolchain, WorkspaceFoldersContent};
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
use lsp::LanguageServerName; use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -400,6 +400,9 @@ impl LspAdapter for PythonLspAdapter {
fn manifest_name(&self) -> Option<ManifestName> { fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into()) Some(SharedString::new_static("pyproject.toml").into())
} }
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
} }
async fn get_cached_server_binary( async fn get_cached_server_binary(
@ -1282,6 +1285,9 @@ impl LspAdapter for PyLspAdapter {
fn manifest_name(&self) -> Option<ManifestName> { fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into()) Some(SharedString::new_static("pyproject.toml").into())
} }
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -29,7 +29,7 @@ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fmt, fmt,
io::Write, io::Write,
ops::{Deref, DerefMut}, ops::DerefMut,
path::PathBuf, path::PathBuf,
pin::Pin, pin::Pin,
sync::{ sync::{
@ -100,7 +100,7 @@ pub struct LanguageServer {
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>, io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
output_done_rx: Mutex<Option<barrier::Receiver>>, output_done_rx: Mutex<Option<barrier::Receiver>>,
server: Arc<Mutex<Option<Child>>>, server: Arc<Mutex<Option<Child>>>,
workspace_folders: Arc<Mutex<BTreeSet<Url>>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
root_uri: Url, root_uri: Url,
} }
@ -307,7 +307,7 @@ impl LanguageServer {
binary: LanguageServerBinary, binary: LanguageServerBinary,
root_path: &Path, root_path: &Path,
code_action_kinds: Option<Vec<CodeActionKind>>, code_action_kinds: Option<Vec<CodeActionKind>>,
workspace_folders: Arc<Mutex<BTreeSet<Url>>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> Result<Self> { ) -> Result<Self> {
let working_dir = if root_path.is_dir() { let working_dir = if root_path.is_dir() {
@ -381,7 +381,7 @@ impl LanguageServer {
code_action_kinds: Option<Vec<CodeActionKind>>, code_action_kinds: Option<Vec<CodeActionKind>>,
binary: LanguageServerBinary, binary: LanguageServerBinary,
root_uri: Url, root_uri: Url,
workspace_folders: Arc<Mutex<BTreeSet<Url>>>, workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
cx: &mut AsyncApp, cx: &mut AsyncApp,
on_unhandled_notification: F, on_unhandled_notification: F,
) -> Self ) -> Self
@ -595,16 +595,26 @@ impl LanguageServer {
} }
pub fn default_initialize_params(&self, pull_diagnostics: bool, cx: &App) -> InitializeParams { pub fn default_initialize_params(&self, pull_diagnostics: bool, cx: &App) -> InitializeParams {
let workspace_folders = self let workspace_folders = self.workspace_folders.as_ref().map_or_else(
.workspace_folders || {
.lock() vec![WorkspaceFolder {
.iter() name: Default::default(),
.cloned() uri: self.root_uri.clone(),
.map(|uri| WorkspaceFolder { }]
name: Default::default(), },
uri, |folders| {
}) folders
.collect::<Vec<_>>(); .lock()
.iter()
.cloned()
.map(|uri| WorkspaceFolder {
name: Default::default(),
uri,
})
.collect()
},
);
#[allow(deprecated)] #[allow(deprecated)]
InitializeParams { InitializeParams {
process_id: None, process_id: None,
@ -1315,7 +1325,10 @@ impl LanguageServer {
return; 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 { if is_new_folder {
let params = DidChangeWorkspaceFoldersParams { let params = DidChangeWorkspaceFoldersParams {
event: WorkspaceFoldersChangeEvent { event: WorkspaceFoldersChangeEvent {
@ -1345,7 +1358,10 @@ impl LanguageServer {
{ {
return; 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 { if was_removed {
let params = DidChangeWorkspaceFoldersParams { let params = DidChangeWorkspaceFoldersParams {
event: WorkspaceFoldersChangeEvent { event: WorkspaceFoldersChangeEvent {
@ -1360,7 +1376,10 @@ impl LanguageServer {
} }
} }
pub fn set_workspace_folders(&self, folders: BTreeSet<Url>) { pub fn set_workspace_folders(&self, folders: BTreeSet<Url>) {
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 old_workspace_folders = std::mem::take(&mut *workspace_folders);
let added: Vec<_> = folders let added: Vec<_> = folders
@ -1389,8 +1408,11 @@ impl LanguageServer {
} }
} }
pub fn workspace_folders(&self) -> impl Deref<Target = BTreeSet<Url>> + '_ { pub fn workspace_folders(&self) -> BTreeSet<Url> {
self.workspace_folders.lock() self.workspace_folders.as_ref().map_or_else(
|| BTreeSet::from_iter([self.root_uri.clone()]),
|folders| folders.lock().clone(),
)
} }
pub fn register_buffer( pub fn register_buffer(
@ -1535,7 +1557,7 @@ impl FakeLanguageServer {
None, None,
binary.clone(), binary.clone(),
root, root,
workspace_folders.clone(), Some(workspace_folders.clone()),
cx, cx,
|_| {}, |_| {},
); );
@ -1554,7 +1576,7 @@ impl FakeLanguageServer {
None, None,
binary, binary,
Self::root_path(), Self::root_path(),
workspace_folders, Some(workspace_folders),
cx, cx,
move |msg| { move |msg| {
notifications_tx notifications_tx

View file

@ -46,6 +46,7 @@ use language::{
DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName,
LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch,
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
WorkspaceFoldersContent,
language_settings::{ language_settings::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, 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 binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx);
let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default(); let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
let pending_server = cx.spawn({ let pending_server = cx.spawn({
let adapter = adapter.clone(); let adapter = adapter.clone();
let server_name = adapter.name.clone(); let server_name = adapter.name.clone();
@ -242,14 +244,18 @@ impl LocalLspStore {
return Ok(server); return Ok(server);
} }
let code_action_kinds = adapter.code_action_kinds();
lsp::LanguageServer::new( lsp::LanguageServer::new(
stderr_capture, stderr_capture,
server_id, server_id,
server_name, server_name,
binary, binary,
&root_path, &root_path,
adapter.code_action_kinds(), code_action_kinds,
pending_workspace_folders, Some(pending_workspace_folders).filter(|_| {
adapter.adapter.workspace_folders_content()
== WorkspaceFoldersContent::SubprojectRoots
}),
cx, cx,
) )
} }
@ -575,8 +581,7 @@ impl LocalLspStore {
}; };
let root = server.workspace_folders(); let root = server.workspace_folders();
Ok(Some( Ok(Some(
root.iter() root.into_iter()
.cloned()
.map(|uri| WorkspaceFolder { .map(|uri| WorkspaceFolder {
uri, uri,
name: Default::default(), name: Default::default(),