lsp: Identify language servers by their configuration (#35270)

- **WIP: reorganize dispositions**
- **Introduce a LocalToolchainStore trait and use it for LspAdapter
methods**

Closes #35782
Closes #27331

Release Notes:

- Python: Improved propagation of a selected virtual environment into
the LSP configuration. This should the make all language-related
features such as Go to definition or Find all references more reliable.

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-08-18 11:43:52 +02:00 committed by GitHub
parent 42ffa8900a
commit b8a106632f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1037 additions and 1085 deletions

View file

@ -1571,6 +1571,7 @@ impl Buffer {
diagnostics: diagnostics.iter().cloned().collect(),
lamport_timestamp,
};
self.apply_diagnostic_update(server_id, diagnostics, lamport_timestamp, cx);
self.send_operation(op, true, cx);
}

View file

@ -44,6 +44,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_json::Value;
use settings::WorktreeId;
use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{
any::Any,
ffi::OsStr,
@ -59,7 +60,6 @@ use std::{
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
},
};
use std::{num::NonZeroU32, sync::OnceLock};
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
pub use task_context::{ContextLocation, ContextProvider, RunnableRange};
@ -67,7 +67,9 @@ pub use text_diff::{
DiffOptions, apply_diff_patch, line_diff, text_diff, text_diff_with_options, unified_diff,
};
use theme::SyntaxTheme;
pub use toolchain::{LanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister};
pub use toolchain::{
LanguageToolchainStore, LocalLanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister,
};
use tree_sitter::{self, Query, QueryCursor, WasmStore, wasmtime};
use util::serde::default_true;
@ -165,7 +167,6 @@ pub struct CachedLspAdapter {
pub adapter: Arc<dyn LspAdapter>,
pub reinstall_attempt_count: AtomicU64,
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
manifest_name: OnceLock<Option<ManifestName>>,
}
impl Debug for CachedLspAdapter {
@ -201,7 +202,6 @@ impl CachedLspAdapter {
adapter,
cached_binary: Default::default(),
reinstall_attempt_count: AtomicU64::new(0),
manifest_name: Default::default(),
})
}
@ -212,7 +212,7 @@ impl CachedLspAdapter {
pub async fn get_language_server_command(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
@ -281,12 +281,6 @@ impl CachedLspAdapter {
.cloned()
.unwrap_or_else(|| language_name.lsp_id())
}
pub fn manifest_name(&self) -> Option<ManifestName> {
self.manifest_name
.get_or_init(|| self.adapter.manifest_name())
.clone()
}
}
/// Determines what gets sent out as a workspace folders content
@ -327,7 +321,7 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
cx: &'a mut AsyncApp,
@ -402,7 +396,7 @@ pub trait LspAdapter: 'static + Send + Sync {
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
None
@ -535,7 +529,7 @@ pub trait LspAdapter: 'static + Send + Sync {
self: Arc<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_cx: &mut AsyncApp,
) -> Result<Value> {
Ok(serde_json::json!({}))
@ -555,7 +549,6 @@ pub trait LspAdapter: 'static + Send + Sync {
_target_language_server_id: LanguageServerName,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncApp,
) -> Result<Option<Value>> {
Ok(None)
@ -594,10 +587,6 @@ pub trait LspAdapter: 'static + Send + Sync {
WorkspaceFoldersContent::SubprojectRoots
}
fn manifest_name(&self) -> Option<ManifestName> {
None
}
/// Method only implemented by the default JSON language server adapter.
/// Used to provide dynamic reloading of the JSON schemas used to
/// provide autocompletion and diagnostics in Zed setting and keybind
@ -1108,6 +1097,7 @@ pub struct Language {
pub(crate) grammar: Option<Arc<Grammar>>,
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
pub(crate) toolchain: Option<Arc<dyn ToolchainLister>>,
pub(crate) manifest_name: Option<ManifestName>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
@ -1318,6 +1308,7 @@ impl Language {
}),
context_provider: None,
toolchain: None,
manifest_name: None,
}
}
@ -1331,6 +1322,10 @@ impl Language {
self
}
pub fn with_manifest(mut self, name: Option<ManifestName>) -> Self {
self.manifest_name = name;
self
}
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
if let Some(query) = queries.highlights {
self = self
@ -1764,6 +1759,9 @@ impl Language {
pub fn name(&self) -> LanguageName {
self.config.name.clone()
}
pub fn manifest(&self) -> Option<&ManifestName> {
self.manifest_name.as_ref()
}
pub fn code_fence_block_name(&self) -> Arc<str> {
self.config
@ -2209,7 +2207,7 @@ impl LspAdapter for FakeLspAdapter {
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
Some(self.language_server_binary.clone())
@ -2218,7 +2216,7 @@ impl LspAdapter for FakeLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncApp,

View file

@ -1,6 +1,6 @@
use crate::{
CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, PLAIN_TEXT, ToolchainLister,
LanguageServerName, LspAdapter, ManifestName, PLAIN_TEXT, ToolchainLister,
language_settings::{
AllLanguageSettingsContent, LanguageSettingsContent, all_language_settings,
},
@ -172,6 +172,7 @@ pub struct AvailableLanguage {
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
loaded: bool,
manifest_name: Option<ManifestName>,
}
impl AvailableLanguage {
@ -259,6 +260,7 @@ pub struct LoadedLanguage {
pub queries: LanguageQueries,
pub context_provider: Option<Arc<dyn ContextProvider>>,
pub toolchain_provider: Option<Arc<dyn ToolchainLister>>,
pub manifest_name: Option<ManifestName>,
}
impl LanguageRegistry {
@ -349,12 +351,14 @@ impl LanguageRegistry {
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
None,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: Default::default(),
toolchain_provider: None,
context_provider: None,
manifest_name: None,
})
}),
)
@ -487,6 +491,7 @@ impl LanguageRegistry {
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
manifest_name: Option<ManifestName>,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
let state = &mut *self.state.write();
@ -496,6 +501,7 @@ impl LanguageRegistry {
existing_language.grammar = grammar_name;
existing_language.matcher = matcher;
existing_language.load = load;
existing_language.manifest_name = manifest_name;
return;
}
}
@ -508,6 +514,7 @@ impl LanguageRegistry {
load,
hidden,
loaded: false,
manifest_name,
});
state.version += 1;
state.reload_count += 1;
@ -575,6 +582,7 @@ impl LanguageRegistry {
grammar: language.config.grammar.clone(),
matcher: language.config.matcher.clone(),
hidden: language.config.hidden,
manifest_name: None,
load: Arc::new(|| Err(anyhow!("already loaded"))),
loaded: true,
});
@ -914,10 +922,12 @@ impl LanguageRegistry {
Language::new_with_id(id, loaded_language.config, grammar)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider)
.with_manifest(loaded_language.manifest_name)
.with_queries(loaded_language.queries)
} else {
Ok(Language::new_with_id(id, loaded_language.config, None)
.with_context_provider(loaded_language.context_provider)
.with_manifest(loaded_language.manifest_name)
.with_toolchain_lister(loaded_language.toolchain_provider))
}
}

View file

@ -12,6 +12,12 @@ impl Borrow<SharedString> for ManifestName {
}
}
impl Borrow<str> for ManifestName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<SharedString> for ManifestName {
fn from(value: SharedString) -> Self {
Self(value)

View file

@ -17,7 +17,7 @@ use settings::WorktreeId;
use crate::{LanguageName, ManifestName};
/// Represents a single toolchain.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq)]
pub struct Toolchain {
/// User-facing label
pub name: SharedString,
@ -27,6 +27,14 @@ pub struct Toolchain {
pub as_json: serde_json::Value,
}
impl std::hash::Hash for Toolchain {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.path.hash(state);
self.language_name.hash(state);
}
}
impl PartialEq for Toolchain {
fn eq(&self, other: &Self) -> bool {
// Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
@ -64,6 +72,29 @@ pub trait LanguageToolchainStore: Send + Sync + 'static {
) -> Option<Toolchain>;
}
pub trait LocalLanguageToolchainStore: Send + Sync + 'static {
fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
relative_path: &Arc<Path>,
language_name: LanguageName,
cx: &mut AsyncApp,
) -> Option<Toolchain>;
}
#[async_trait(?Send )]
impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
async fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
relative_path: Arc<Path>,
language_name: LanguageName,
cx: &mut AsyncApp,
) -> Option<Toolchain> {
self.active_toolchain(worktree_id, &relative_path, language_name, cx)
}
}
type DefaultIndex = usize;
#[derive(Default, Clone)]
pub struct ToolchainList {