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

@ -16022,38 +16022,24 @@ impl Editor {
cx.spawn_in(window, async move |editor, cx| {
let location_task = editor.update(cx, |_, cx| {
project.update(cx, |project, cx| {
let language_server_name = project
.language_server_statuses(cx)
.find(|(id, _)| server_id == *id)
.map(|(_, status)| status.name.clone());
language_server_name.map(|language_server_name| {
project.open_local_buffer_via_lsp(
lsp_location.uri.clone(),
server_id,
language_server_name,
cx,
)
})
project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
})
})?;
let location = match location_task {
Some(task) => Some({
let target_buffer_handle = task.await.context("open local buffer")?;
let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
let target_start = target_buffer
.clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
let target_end = target_buffer
.clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end)
})?;
Location {
buffer: target_buffer_handle,
range,
}
}),
None => None,
};
let location = Some({
let target_buffer_handle = location_task.await.context("open local buffer")?;
let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
let target_start = target_buffer
.clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
let target_end = target_buffer
.clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end)
})?;
Location {
buffer: target_buffer_handle,
range,
}
});
Ok(location)
})
}

View file

@ -1275,6 +1275,7 @@ impl ExtensionStore {
queries,
context_provider,
toolchain_provider: None,
manifest_name: None,
})
}),
);

View file

@ -163,6 +163,7 @@ impl HeadlessExtensionStore {
queries: LanguageQueries::default(),
context_provider: None,
toolchain_provider: None,
manifest_name: None,
})
}),
);

View file

@ -938,7 +938,7 @@ impl ExtensionImports for WasmState {
binary: settings.binary.map(|binary| settings::CommandSettings {
path: binary.path,
arguments: binary.arguments,
env: binary.env,
env: binary.env.map(|env| env.into_iter().collect()),
}),
settings: settings.settings,
initialization_options: settings.initialization_options,

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 {

View file

@ -12,8 +12,8 @@ use fs::Fs;
use futures::{Future, FutureExt, future::join_all};
use gpui::{App, AppContext, AsyncApp, Task};
use language::{
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore,
LspAdapter, LspAdapterDelegate,
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LspAdapter, LspAdapterDelegate,
Toolchain,
};
use lsp::{
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
@ -159,7 +159,7 @@ impl LspAdapter for ExtensionLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncApp,
@ -288,7 +288,7 @@ impl LspAdapter for ExtensionLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_cx: &mut AsyncApp,
) -> Result<Value> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
@ -336,7 +336,7 @@ impl LspAdapter for ExtensionLspAdapter {
target_language_server_id: LanguageServerName,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncApp,
) -> Result<Option<serde_json::Value>> {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;

View file

@ -52,7 +52,7 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
self.language_registry
.register_language(language, grammar, matcher, hidden, load);
.register_language(language, grammar, matcher, hidden, None, load);
}
fn remove_languages(

View file

@ -28,7 +28,7 @@ impl super::LspAdapter for CLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -2,7 +2,7 @@ use anyhow::{Context as _, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -43,7 +43,7 @@ impl LspAdapter for CssLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate
@ -144,7 +144,7 @@ impl LspAdapter for CssLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<serde_json::Value> {
let mut default_config = json!({

View file

@ -75,7 +75,7 @@ impl super::LspAdapter for GoLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -8,8 +8,8 @@ use futures::StreamExt;
use gpui::{App, AsyncApp, Task};
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
use language::{
ContextProvider, LanguageName, LanguageRegistry, LanguageToolchainStore, LocalFile as _,
LspAdapter, LspAdapterDelegate,
ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter,
LspAdapterDelegate, Toolchain,
};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
@ -303,7 +303,7 @@ impl LspAdapter for JsonLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate
@ -404,7 +404,7 @@ impl LspAdapter for JsonLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let mut config = self.get_or_init_workspace_config(cx).await?;
@ -529,7 +529,7 @@ impl LspAdapter for NodeVersionAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -1,6 +1,6 @@
use anyhow::Context as _;
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{App, UpdateGlobal};
use gpui::{App, SharedString, UpdateGlobal};
use node_runtime::NodeRuntime;
use python::PyprojectTomlManifestProvider;
use rust::CargoManifestProvider;
@ -177,11 +177,13 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
adapters: vec![python_lsp_adapter.clone(), py_lsp_adapter.clone()],
context: Some(python_context_provider),
toolchain: Some(python_toolchain_provider),
manifest_name: Some(SharedString::new_static("pyproject.toml").into()),
},
LanguageInfo {
name: "rust",
adapters: vec![rust_lsp_adapter],
context: Some(rust_context_provider),
manifest_name: Some(SharedString::new_static("Cargo.toml").into()),
..Default::default()
},
LanguageInfo {
@ -234,6 +236,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
registration.adapters,
registration.context,
registration.toolchain,
registration.manifest_name,
);
}
@ -340,7 +343,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
Arc::from(PyprojectTomlManifestProvider),
];
for provider in manifest_providers {
project::ManifestProviders::global(cx).register(provider);
project::ManifestProvidersStore::global(cx).register(provider);
}
}
@ -350,6 +353,7 @@ struct LanguageInfo {
adapters: Vec<Arc<dyn LspAdapter>>,
context: Option<Arc<dyn ContextProvider>>,
toolchain: Option<Arc<dyn ToolchainLister>>,
manifest_name: Option<ManifestName>,
}
fn register_language(
@ -358,6 +362,7 @@ fn register_language(
adapters: Vec<Arc<dyn LspAdapter>>,
context: Option<Arc<dyn ContextProvider>>,
toolchain: Option<Arc<dyn ToolchainLister>>,
manifest_name: Option<ManifestName>,
) {
let config = load_config(name);
for adapter in adapters {
@ -368,12 +373,14 @@ fn register_language(
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
manifest_name.clone(),
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries(name),
context_provider: context.clone(),
toolchain_provider: toolchain.clone(),
manifest_name: manifest_name.clone(),
})
}),
);

View file

@ -127,7 +127,7 @@ impl LspAdapter for PythonLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await {
@ -319,17 +319,9 @@ impl LspAdapter for PythonLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -397,9 +389,7 @@ impl LspAdapter for PythonLspAdapter {
user_settings
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
@ -1046,8 +1036,8 @@ impl LspAdapter for PyLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &AsyncApp,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(pylsp_bin) = delegate.which(Self::SERVER_NAME.as_ref()).await {
let env = delegate.shell_env().await;
@ -1057,14 +1047,7 @@ impl LspAdapter for PyLspAdapter {
arguments: vec![],
})
} else {
let venv = toolchains
.active_toolchain(
delegate.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
&mut cx.clone(),
)
.await?;
let venv = toolchain?;
let pylsp_path = Path::new(venv.path.as_ref()).parent()?.join("pylsp");
pylsp_path.exists().then(|| LanguageServerBinary {
path: venv.path.to_string().into(),
@ -1211,17 +1194,9 @@ impl LspAdapter for PyLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -1282,9 +1257,6 @@ impl LspAdapter for PyLspAdapter {
user_settings
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
@ -1377,8 +1349,8 @@ impl LspAdapter for BasedPyrightLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &AsyncApp,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await {
let env = delegate.shell_env().await;
@ -1388,15 +1360,7 @@ impl LspAdapter for BasedPyrightLspAdapter {
arguments: vec!["--stdio".into()],
})
} else {
let venv = toolchains
.active_toolchain(
delegate.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
&mut cx.clone(),
)
.await?;
let path = Path::new(venv.path.as_ref())
let path = Path::new(toolchain?.path.as_ref())
.parent()?
.join(Self::BINARY_NAME);
path.exists().then(|| LanguageServerBinary {
@ -1543,17 +1507,9 @@ impl LspAdapter for BasedPyrightLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -1622,10 +1578,6 @@ impl LspAdapter for BasedPyrightLspAdapter {
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}

View file

@ -109,14 +109,10 @@ impl LspAdapter for RustLspAdapter {
SERVER_NAME.clone()
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("Cargo.toml").into())
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which("rust-analyzer".as_ref()).await?;

View file

@ -3,7 +3,7 @@ use async_trait::async_trait;
use collections::HashMap;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -50,7 +50,7 @@ impl LspAdapter for TailwindLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
@ -155,7 +155,7 @@ impl LspAdapter for TailwindLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let mut tailwind_user_settings = cx.update(|cx| {

View file

@ -7,7 +7,7 @@ use gpui::{App, AppContext, AsyncApp, Task};
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
use language::{
ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter,
LspAdapterDelegate,
LspAdapterDelegate, Toolchain,
};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
@ -722,7 +722,7 @@ impl LspAdapter for TypeScriptLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let override_options = cx.update(|cx| {
@ -822,7 +822,7 @@ impl LspAdapter for EsLintLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let workspace_root = delegate.worktree_root_path();

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use async_trait::async_trait;
use collections::HashMap;
use gpui::AsyncApp;
use language::{LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -86,7 +86,7 @@ impl LspAdapter for VtslsLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let env = delegate.shell_env().await;
@ -211,7 +211,7 @@ impl LspAdapter for VtslsLspAdapter {
self: Arc<Self>,
fs: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let tsdk_path = Self::tsdk_path(fs, delegate).await;

View file

@ -2,9 +2,7 @@ use anyhow::{Context as _, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{
LanguageToolchainStore, LspAdapter, LspAdapterDelegate, language_settings::AllLanguageSettings,
};
use language::{LspAdapter, LspAdapterDelegate, Toolchain, language_settings::AllLanguageSettings};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -57,7 +55,7 @@ impl LspAdapter for YamlLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
@ -135,7 +133,7 @@ impl LspAdapter for YamlLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let location = SettingsLocation {

View file

@ -500,13 +500,12 @@ impl LspCommand for PerformRename {
mut cx: AsyncApp,
) -> Result<ProjectTransaction> {
if let Some(edit) = message {
let (lsp_adapter, lsp_server) =
let (_, lsp_server) =
language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
LocalLspStore::deserialize_workspace_edit(
lsp_store,
edit,
self.push_to_history,
lsp_adapter,
lsp_server,
&mut cx,
)
@ -1116,18 +1115,12 @@ pub async fn location_links_from_lsp(
}
}
let (lsp_adapter, language_server) =
language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
let (_, language_server) = language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
let mut definitions = Vec::new();
for (origin_range, target_uri, target_range) in unresolved_links {
let target_buffer_handle = lsp_store
.update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp(
target_uri,
language_server.server_id(),
lsp_adapter.name.clone(),
cx,
)
this.open_local_buffer_via_lsp(target_uri, language_server.server_id(), cx)
})?
.await?;
@ -1172,8 +1165,7 @@ pub async fn location_link_from_lsp(
server_id: LanguageServerId,
cx: &mut AsyncApp,
) -> Result<LocationLink> {
let (lsp_adapter, language_server) =
language_server_for_buffer(&lsp_store, &buffer, server_id, cx)?;
let (_, language_server) = language_server_for_buffer(&lsp_store, &buffer, server_id, cx)?;
let (origin_range, target_uri, target_range) = (
link.origin_selection_range,
@ -1183,12 +1175,7 @@ pub async fn location_link_from_lsp(
let target_buffer_handle = lsp_store
.update(cx, |lsp_store, cx| {
lsp_store.open_local_buffer_via_lsp(
target_uri,
language_server.server_id(),
lsp_adapter.name.clone(),
cx,
)
lsp_store.open_local_buffer_via_lsp(target_uri, language_server.server_id(), cx)
})?
.await?;
@ -1326,7 +1313,7 @@ impl LspCommand for GetReferences {
mut cx: AsyncApp,
) -> Result<Vec<Location>> {
let mut references = Vec::new();
let (lsp_adapter, language_server) =
let (_, language_server) =
language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
if let Some(locations) = locations {
@ -1336,7 +1323,6 @@ impl LspCommand for GetReferences {
lsp_store.open_local_buffer_via_lsp(
lsp_location.uri,
language_server.server_id(),
lsp_adapter.name.clone(),
cx,
)
})?

File diff suppressed because it is too large Load diff

View file

@ -7,18 +7,12 @@ mod manifest_store;
mod path_trie;
mod server_tree;
use std::{
borrow::Borrow,
collections::{BTreeMap, hash_map::Entry},
ops::ControlFlow,
path::Path,
sync::Arc,
};
use std::{borrow::Borrow, collections::hash_map::Entry, ops::ControlFlow, path::Path, sync::Arc};
use collections::HashMap;
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription};
use gpui::{App, AppContext as _, Context, Entity, Subscription};
use language::{ManifestDelegate, ManifestName, ManifestQuery};
pub use manifest_store::ManifestProviders;
pub use manifest_store::ManifestProvidersStore;
use path_trie::{LabelPresence, RootPathTrie, TriePath};
use settings::{SettingsStore, WorktreeId};
use worktree::{Event as WorktreeEvent, Snapshot, Worktree};
@ -28,9 +22,7 @@ use crate::{
worktree_store::{WorktreeStore, WorktreeStoreEvent},
};
pub(crate) use server_tree::{
AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition,
};
pub(crate) use server_tree::{LanguageServerTree, LanguageServerTreeNode, LaunchDisposition};
struct WorktreeRoots {
roots: RootPathTrie<ManifestName>,
@ -81,14 +73,6 @@ pub struct ManifestTree {
_subscriptions: [Subscription; 2],
}
#[derive(PartialEq)]
pub(crate) enum ManifestTreeEvent {
WorktreeRemoved(WorktreeId),
Cleared,
}
impl EventEmitter<ManifestTreeEvent> for ManifestTree {}
impl ManifestTree {
pub fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self {
@ -101,30 +85,28 @@ impl ManifestTree {
worktree_roots.roots = RootPathTrie::new();
})
}
cx.emit(ManifestTreeEvent::Cleared);
}),
],
worktree_store,
})
}
pub(crate) fn root_for_path(
&mut self,
ProjectPath { worktree_id, path }: ProjectPath,
manifests: &mut dyn Iterator<Item = ManifestName>,
delegate: Arc<dyn ManifestDelegate>,
ProjectPath { worktree_id, path }: &ProjectPath,
manifest_name: &ManifestName,
delegate: &Arc<dyn ManifestDelegate>,
cx: &mut App,
) -> BTreeMap<ManifestName, ProjectPath> {
debug_assert_eq!(delegate.worktree_id(), worktree_id);
let mut roots = BTreeMap::from_iter(
manifests.map(|manifest| (manifest, (None, LabelPresence::KnownAbsent))),
);
let worktree_roots = match self.root_points.entry(worktree_id) {
) -> Option<ProjectPath> {
debug_assert_eq!(delegate.worktree_id(), *worktree_id);
let (mut marked_path, mut current_presence) = (None, LabelPresence::KnownAbsent);
let worktree_roots = match self.root_points.entry(*worktree_id) {
Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
Entry::Vacant(vacant_entry) => {
let Some(worktree) = self
.worktree_store
.read(cx)
.worktree_for_id(worktree_id, cx)
.worktree_for_id(*worktree_id, cx)
else {
return Default::default();
};
@ -133,16 +115,16 @@ impl ManifestTree {
}
};
let key = TriePath::from(&*path);
let key = TriePath::from(&**path);
worktree_roots.read_with(cx, |this, _| {
this.roots.walk(&key, &mut |path, labels| {
for (label, presence) in labels {
if let Some((marked_path, current_presence)) = roots.get_mut(label) {
if *current_presence > *presence {
if label == manifest_name {
if current_presence > *presence {
debug_assert!(false, "RootPathTrie precondition violation; while walking the tree label presence is only allowed to increase");
}
*marked_path = Some(ProjectPath {worktree_id, path: path.clone()});
*current_presence = *presence;
marked_path = Some(ProjectPath {worktree_id: *worktree_id, path: path.clone()});
current_presence = *presence;
}
}
@ -150,12 +132,9 @@ impl ManifestTree {
});
});
for (manifest_name, (root_path, presence)) in &mut roots {
if *presence == LabelPresence::Present {
continue;
}
let depth = root_path
if current_presence == LabelPresence::KnownAbsent {
// Some part of the path is unexplored.
let depth = marked_path
.as_ref()
.map(|root_path| {
path.strip_prefix(&root_path.path)
@ -165,13 +144,10 @@ impl ManifestTree {
})
.unwrap_or_else(|| path.components().count() + 1);
if depth > 0 {
let Some(provider) = ManifestProviders::global(cx).get(manifest_name.borrow())
else {
log::warn!("Manifest provider `{}` not found", manifest_name.as_ref());
continue;
};
if depth > 0
&& let Some(provider) =
ManifestProvidersStore::global(cx).get(manifest_name.borrow())
{
let root = provider.search(ManifestQuery {
path: path.clone(),
depth,
@ -182,9 +158,9 @@ impl ManifestTree {
let root = TriePath::from(&*known_root);
this.roots
.insert(&root, manifest_name.clone(), LabelPresence::Present);
*presence = LabelPresence::Present;
*root_path = Some(ProjectPath {
worktree_id,
current_presence = LabelPresence::Present;
marked_path = Some(ProjectPath {
worktree_id: *worktree_id,
path: known_root,
});
}),
@ -195,25 +171,35 @@ impl ManifestTree {
}
}
}
roots
.into_iter()
.filter_map(|(k, (path, presence))| {
let path = path?;
presence.eq(&LabelPresence::Present).then(|| (k, path))
})
.collect()
marked_path.filter(|_| current_presence.eq(&LabelPresence::Present))
}
pub(crate) fn root_for_path_or_worktree_root(
&mut self,
project_path: &ProjectPath,
manifest_name: Option<&ManifestName>,
delegate: &Arc<dyn ManifestDelegate>,
cx: &mut App,
) -> ProjectPath {
let worktree_id = project_path.worktree_id;
// Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
manifest_name
.and_then(|manifest_name| self.root_for_path(project_path, manifest_name, delegate, cx))
.unwrap_or_else(|| ProjectPath {
worktree_id,
path: Arc::from(Path::new("")),
})
}
fn on_worktree_store_event(
&mut self,
_: Entity<WorktreeStore>,
evt: &WorktreeStoreEvent,
cx: &mut Context<Self>,
_: &mut Context<Self>,
) {
match evt {
WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
self.root_points.remove(&worktree_id);
cx.emit(ManifestTreeEvent::WorktreeRemoved(*worktree_id));
}
_ => {}
}
@ -223,6 +209,7 @@ impl ManifestTree {
pub(crate) struct ManifestQueryDelegate {
worktree: Snapshot,
}
impl ManifestQueryDelegate {
pub fn new(worktree: Snapshot) -> Self {
Self { worktree }

View file

@ -1,4 +1,4 @@
use collections::HashMap;
use collections::{HashMap, HashSet};
use gpui::{App, Global, SharedString};
use parking_lot::RwLock;
use std::{ops::Deref, sync::Arc};
@ -11,13 +11,13 @@ struct ManifestProvidersState {
}
#[derive(Clone, Default)]
pub struct ManifestProviders(Arc<RwLock<ManifestProvidersState>>);
pub struct ManifestProvidersStore(Arc<RwLock<ManifestProvidersState>>);
#[derive(Default)]
struct GlobalManifestProvider(ManifestProviders);
struct GlobalManifestProvider(ManifestProvidersStore);
impl Deref for GlobalManifestProvider {
type Target = ManifestProviders;
type Target = ManifestProvidersStore;
fn deref(&self) -> &Self::Target {
&self.0
@ -26,7 +26,7 @@ impl Deref for GlobalManifestProvider {
impl Global for GlobalManifestProvider {}
impl ManifestProviders {
impl ManifestProvidersStore {
/// Returns the global [`ManifestStore`].
///
/// Inserts a default [`ManifestStore`] if one does not yet exist.
@ -45,4 +45,7 @@ impl ManifestProviders {
pub(super) fn get(&self, name: &SharedString) -> Option<Arc<dyn ManifestProvider>> {
self.0.read().providers.get(name).cloned()
}
pub(crate) fn manifest_file_names(&self) -> HashSet<ManifestName> {
self.0.read().providers.keys().cloned().collect()
}
}

View file

@ -4,8 +4,7 @@
//!
//! ## RPC
//! LSP Tree is transparent to RPC peers; when clients ask host to spawn a new language server, the host will perform LSP Tree lookup for provided path; it may decide
//! to reuse existing language server. The client maintains it's own LSP Tree that is a subset of host LSP Tree. Done this way, the client does not need to
//! ask about suitable language server for each path it interacts with; it can resolve most of the queries locally.
//! to reuse existing language server.
use std::{
collections::{BTreeMap, BTreeSet},
@ -14,20 +13,23 @@ use std::{
};
use collections::IndexMap;
use gpui::{App, AppContext as _, Entity, Subscription};
use gpui::{App, Entity};
use language::{
CachedLspAdapter, LanguageName, LanguageRegistry, ManifestDelegate,
CachedLspAdapter, LanguageName, LanguageRegistry, ManifestDelegate, ManifestName, Toolchain,
language_settings::AllLanguageSettings,
};
use lsp::LanguageServerName;
use settings::{Settings, SettingsLocation, WorktreeId};
use std::sync::OnceLock;
use crate::{LanguageServerId, ProjectPath, project_settings::LspSettings};
use crate::{
LanguageServerId, ProjectPath, project_settings::LspSettings,
toolchain_store::LocalToolchainStore,
};
use super::{ManifestTree, ManifestTreeEvent};
use super::ManifestTree;
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub(crate) struct ServersForWorktree {
pub(crate) roots: BTreeMap<
Arc<Path>,
@ -39,7 +41,7 @@ pub struct LanguageServerTree {
manifest_tree: Entity<ManifestTree>,
pub(crate) instances: BTreeMap<WorktreeId, ServersForWorktree>,
languages: Arc<LanguageRegistry>,
_subscriptions: Subscription,
toolchains: Entity<LocalToolchainStore>,
}
/// A node in language server tree represents either:
@ -49,22 +51,15 @@ pub struct LanguageServerTree {
pub struct LanguageServerTreeNode(Weak<InnerTreeNode>);
/// Describes a request to launch a language server.
#[derive(Debug)]
pub(crate) struct LaunchDisposition<'a> {
pub(crate) server_name: &'a LanguageServerName,
#[derive(Clone, Debug)]
pub(crate) struct LaunchDisposition {
pub(crate) server_name: LanguageServerName,
/// Path to the root directory of a subproject.
pub(crate) path: ProjectPath,
pub(crate) settings: Arc<LspSettings>,
pub(crate) toolchain: Option<Toolchain>,
}
impl<'a> From<&'a InnerTreeNode> for LaunchDisposition<'a> {
fn from(value: &'a InnerTreeNode) -> Self {
LaunchDisposition {
server_name: &value.name,
path: value.path.clone(),
settings: value.settings.clone(),
}
}
}
impl LanguageServerTreeNode {
/// Returns a language server ID for this node if there is one.
/// Returns None if this node has not been initialized yet or it is no longer in the tree.
@ -76,19 +71,17 @@ impl LanguageServerTreeNode {
/// May return None if the node no longer belongs to the server tree it was created in.
pub(crate) fn server_id_or_init(
&self,
init: impl FnOnce(LaunchDisposition) -> LanguageServerId,
init: impl FnOnce(&Arc<LaunchDisposition>) -> LanguageServerId,
) -> Option<LanguageServerId> {
let this = self.0.upgrade()?;
Some(
*this
.id
.get_or_init(|| init(LaunchDisposition::from(&*this))),
)
Some(*this.id.get_or_init(|| init(&this.disposition)))
}
/// Returns a language server name as the language server adapter would return.
pub fn name(&self) -> Option<LanguageServerName> {
self.0.upgrade().map(|node| node.name.clone())
self.0
.upgrade()
.map(|node| node.disposition.server_name.clone())
}
}
@ -101,160 +94,149 @@ impl From<Weak<InnerTreeNode>> for LanguageServerTreeNode {
#[derive(Debug)]
pub struct InnerTreeNode {
id: OnceLock<LanguageServerId>,
name: LanguageServerName,
path: ProjectPath,
settings: Arc<LspSettings>,
disposition: Arc<LaunchDisposition>,
}
impl InnerTreeNode {
fn new(
name: LanguageServerName,
server_name: LanguageServerName,
path: ProjectPath,
settings: impl Into<Arc<LspSettings>>,
settings: LspSettings,
toolchain: Option<Toolchain>,
) -> Self {
InnerTreeNode {
id: Default::default(),
name,
path,
settings: settings.into(),
disposition: Arc::new(LaunchDisposition {
server_name,
path,
settings: settings.into(),
toolchain,
}),
}
}
}
/// Determines how the list of adapters to query should be constructed.
pub(crate) enum AdapterQuery<'a> {
/// Search for roots of all adapters associated with a given language name.
/// Layman: Look for all project roots along the queried path that have any
/// language server associated with this language running.
Language(&'a LanguageName),
/// Search for roots of adapter with a given name.
/// Layman: Look for all project roots along the queried path that have this server running.
Adapter(&'a LanguageServerName),
}
impl LanguageServerTree {
pub(crate) fn new(
manifest_tree: Entity<ManifestTree>,
languages: Arc<LanguageRegistry>,
cx: &mut App,
) -> Entity<Self> {
cx.new(|cx| Self {
_subscriptions: cx.subscribe(&manifest_tree, |_: &mut Self, _, event, _| {
if event == &ManifestTreeEvent::Cleared {}
}),
toolchains: Entity<LocalToolchainStore>,
) -> Self {
Self {
manifest_tree,
instances: Default::default(),
languages,
})
toolchains,
}
}
/// Get all initialized language server IDs for a given path.
pub(crate) fn get<'a>(
&'a self,
path: ProjectPath,
language_name: LanguageName,
manifest_name: Option<&ManifestName>,
delegate: &Arc<dyn ManifestDelegate>,
cx: &mut App,
) -> impl Iterator<Item = LanguageServerId> + 'a {
let manifest_location = self.manifest_location_for_path(&path, manifest_name, delegate, cx);
let adapters = self.adapters_for_language(&manifest_location, &language_name, cx);
self.get_with_adapters(manifest_location, adapters)
}
/// Get all language server root points for a given path and language; the language servers might already be initialized at a given path.
pub(crate) fn get<'a>(
pub(crate) fn walk<'a>(
&'a mut self,
path: ProjectPath,
query: AdapterQuery<'_>,
delegate: Arc<dyn ManifestDelegate>,
cx: &mut App,
language_name: LanguageName,
manifest_name: Option<&ManifestName>,
delegate: &Arc<dyn ManifestDelegate>,
cx: &'a mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let settings_location = SettingsLocation {
worktree_id: path.worktree_id,
path: &path.path,
};
let adapters = match query {
AdapterQuery::Language(language_name) => {
self.adapters_for_language(settings_location, language_name, cx)
}
AdapterQuery::Adapter(language_server_name) => {
IndexMap::from_iter(self.adapter_for_name(language_server_name).map(|adapter| {
(
let manifest_location = self.manifest_location_for_path(&path, manifest_name, delegate, cx);
let adapters = self.adapters_for_language(&manifest_location, &language_name, cx);
self.init_with_adapters(manifest_location, language_name, adapters, cx)
}
fn init_with_adapters<'a>(
&'a mut self,
root_path: ProjectPath,
language_name: LanguageName,
adapters: IndexMap<LanguageServerName, (LspSettings, Arc<CachedLspAdapter>)>,
cx: &'a App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
adapters.into_iter().map(move |(_, (settings, adapter))| {
let root_path = root_path.clone();
let inner_node = self
.instances
.entry(root_path.worktree_id)
.or_default()
.roots
.entry(root_path.path.clone())
.or_default()
.entry(adapter.name());
let (node, languages) = inner_node.or_insert_with(|| {
let toolchain = self.toolchains.read(cx).active_toolchain(
root_path.worktree_id,
&root_path.path,
language_name.clone(),
);
(
Arc::new(InnerTreeNode::new(
adapter.name(),
(LspSettings::default(), BTreeSet::new(), adapter),
)
}))
}
};
self.get_with_adapters(path, adapters, delegate, cx)
root_path.clone(),
settings.clone(),
toolchain,
)),
Default::default(),
)
});
languages.insert(language_name.clone());
Arc::downgrade(&node).into()
})
}
fn get_with_adapters<'a>(
&'a mut self,
path: ProjectPath,
adapters: IndexMap<
LanguageServerName,
(LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>),
>,
delegate: Arc<dyn ManifestDelegate>,
cx: &mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let worktree_id = path.worktree_id;
let mut manifest_to_adapters = BTreeMap::default();
for (_, _, adapter) in adapters.values() {
if let Some(manifest_name) = adapter.manifest_name() {
manifest_to_adapters
.entry(manifest_name)
.or_insert_with(Vec::default)
.push(adapter.clone());
}
}
let roots = self.manifest_tree.update(cx, |this, cx| {
this.root_for_path(
path,
&mut manifest_to_adapters.keys().cloned(),
delegate,
cx,
)
});
let root_path = std::cell::LazyCell::new(move || ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
});
adapters
.into_iter()
.map(move |(_, (settings, new_languages, adapter))| {
// Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
let root_path = adapter
.manifest_name()
.and_then(|name| roots.get(&name))
.cloned()
.unwrap_or_else(|| root_path.clone());
let inner_node = self
.instances
.entry(root_path.worktree_id)
.or_default()
.roots
.entry(root_path.path.clone())
.or_default()
.entry(adapter.name());
let (node, languages) = inner_node.or_insert_with(|| {
(
Arc::new(InnerTreeNode::new(
adapter.name(),
root_path.clone(),
settings.clone(),
)),
Default::default(),
)
});
languages.extend(new_languages.iter().cloned());
Arc::downgrade(&node).into()
})
&'a self,
root_path: ProjectPath,
adapters: IndexMap<LanguageServerName, (LspSettings, Arc<CachedLspAdapter>)>,
) -> impl Iterator<Item = LanguageServerId> + 'a {
adapters.into_iter().filter_map(move |(_, (_, adapter))| {
let root_path = root_path.clone();
let inner_node = self
.instances
.get(&root_path.worktree_id)?
.roots
.get(&root_path.path)?
.get(&adapter.name())?;
inner_node.0.id.get().copied()
})
}
fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
self.languages.adapter_for_name(name)
fn manifest_location_for_path(
&self,
path: &ProjectPath,
manifest_name: Option<&ManifestName>,
delegate: &Arc<dyn ManifestDelegate>,
cx: &mut App,
) -> ProjectPath {
// Find out what the root location of our subproject is.
// That's where we'll look for language settings (that include a set of language servers).
self.manifest_tree.update(cx, |this, cx| {
this.root_for_path_or_worktree_root(path, manifest_name, delegate, cx)
})
}
fn adapters_for_language(
&self,
settings_location: SettingsLocation,
manifest_location: &ProjectPath,
language_name: &LanguageName,
cx: &App,
) -> IndexMap<LanguageServerName, (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>)>
{
) -> IndexMap<LanguageServerName, (LspSettings, Arc<CachedLspAdapter>)> {
let settings_location = SettingsLocation {
worktree_id: manifest_location.worktree_id,
path: &manifest_location.path,
};
let settings = AllLanguageSettings::get(Some(settings_location), cx).language(
Some(settings_location),
Some(language_name),
@ -295,14 +277,7 @@ impl LanguageServerTree {
)
.cloned()
.unwrap_or_default();
Some((
adapter.name(),
(
adapter_settings,
BTreeSet::from_iter([language_name.clone()]),
adapter,
),
))
Some((adapter.name(), (adapter_settings, adapter)))
})
.collect::<IndexMap<_, _>>();
// After starting all the language servers, reorder them to reflect the desired order
@ -315,17 +290,23 @@ impl LanguageServerTree {
&language_name,
adapters_with_settings
.values()
.map(|(_, _, adapter)| adapter.clone())
.map(|(_, adapter)| adapter.clone())
.collect(),
);
adapters_with_settings
}
// Rebasing a tree:
// - Clears it out
// - Provides you with the indirect access to the old tree while you're reinitializing a new one (by querying it).
pub(crate) fn rebase(&mut self) -> ServerTreeRebase<'_> {
/// Server Tree is built up incrementally via queries for distinct paths of the worktree.
/// Results of these queries have to be invalidated when data used to build the tree changes.
///
/// The environment of a server tree is a set of all user settings.
/// Rebasing a tree means invalidating it and building up a new one while reusing the old tree where applicable.
/// We want to reuse the old tree in order to preserve as many of the running language servers as possible.
/// E.g. if the user disables one of their language servers for Python, we don't want to shut down any language servers unaffected by this settings change.
///
/// Thus, [`ServerTreeRebase`] mimics the interface of a [`ServerTree`], except that it tries to find a matching language server in the old tree before handing out an uninitialized node.
pub(crate) fn rebase(&mut self) -> ServerTreeRebase {
ServerTreeRebase::new(self)
}
@ -354,16 +335,16 @@ impl LanguageServerTree {
.roots
.entry(Arc::from(Path::new("")))
.or_default()
.entry(node.name.clone())
.entry(node.disposition.server_name.clone())
.or_insert_with(|| (node, BTreeSet::new()))
.1
.insert(language_name);
}
}
pub(crate) struct ServerTreeRebase<'a> {
pub(crate) struct ServerTreeRebase {
old_contents: BTreeMap<WorktreeId, ServersForWorktree>,
new_tree: &'a mut LanguageServerTree,
new_tree: LanguageServerTree,
/// All server IDs seen in the old tree.
all_server_ids: BTreeMap<LanguageServerId, LanguageServerName>,
/// Server IDs we've preserved for a new iteration of the tree. `all_server_ids - rebased_server_ids` is the
@ -371,9 +352,9 @@ pub(crate) struct ServerTreeRebase<'a> {
rebased_server_ids: BTreeSet<LanguageServerId>,
}
impl<'tree> ServerTreeRebase<'tree> {
fn new(new_tree: &'tree mut LanguageServerTree) -> Self {
let old_contents = std::mem::take(&mut new_tree.instances);
impl ServerTreeRebase {
fn new(old_tree: &LanguageServerTree) -> Self {
let old_contents = old_tree.instances.clone();
let all_server_ids = old_contents
.values()
.flat_map(|nodes| {
@ -384,69 +365,68 @@ impl<'tree> ServerTreeRebase<'tree> {
.id
.get()
.copied()
.map(|id| (id, server.0.name.clone()))
.map(|id| (id, server.0.disposition.server_name.clone()))
})
})
})
.collect();
let new_tree = LanguageServerTree::new(
old_tree.manifest_tree.clone(),
old_tree.languages.clone(),
old_tree.toolchains.clone(),
);
Self {
old_contents,
new_tree,
all_server_ids,
new_tree,
rebased_server_ids: BTreeSet::new(),
}
}
pub(crate) fn get<'a>(
pub(crate) fn walk<'a>(
&'a mut self,
path: ProjectPath,
query: AdapterQuery<'_>,
language_name: LanguageName,
manifest_name: Option<&ManifestName>,
delegate: Arc<dyn ManifestDelegate>,
cx: &mut App,
cx: &'a mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let settings_location = SettingsLocation {
worktree_id: path.worktree_id,
path: &path.path,
};
let adapters = match query {
AdapterQuery::Language(language_name) => {
self.new_tree
.adapters_for_language(settings_location, language_name, cx)
}
AdapterQuery::Adapter(language_server_name) => {
IndexMap::from_iter(self.new_tree.adapter_for_name(language_server_name).map(
|adapter| {
(
adapter.name(),
(LspSettings::default(), BTreeSet::new(), adapter),
)
},
))
}
};
let manifest =
self.new_tree
.manifest_location_for_path(&path, manifest_name, &delegate, cx);
let adapters = self
.new_tree
.adapters_for_language(&manifest, &language_name, cx);
self.new_tree
.get_with_adapters(path, adapters, delegate, cx)
.init_with_adapters(manifest, language_name, adapters, cx)
.filter_map(|node| {
// Inspect result of the query and initialize it ourselves before
// handing it off to the caller.
let disposition = node.0.upgrade()?;
let live_node = node.0.upgrade()?;
if disposition.id.get().is_some() {
if live_node.id.get().is_some() {
return Some(node);
}
let disposition = &live_node.disposition;
let Some((existing_node, _)) = self
.old_contents
.get(&disposition.path.worktree_id)
.and_then(|worktree_nodes| worktree_nodes.roots.get(&disposition.path.path))
.and_then(|roots| roots.get(&disposition.name))
.filter(|(old_node, _)| disposition.settings == old_node.settings)
.and_then(|roots| roots.get(&disposition.server_name))
.filter(|(old_node, _)| {
(&disposition.toolchain, &disposition.settings)
== (
&old_node.disposition.toolchain,
&old_node.disposition.settings,
)
})
else {
return Some(node);
};
if let Some(existing_id) = existing_node.id.get() {
self.rebased_server_ids.insert(*existing_id);
disposition.id.set(*existing_id).ok();
live_node.id.set(*existing_id).ok();
}
Some(node)
@ -454,11 +434,19 @@ impl<'tree> ServerTreeRebase<'tree> {
}
/// Returns IDs of servers that are no longer referenced (and can be shut down).
pub(crate) fn finish(self) -> BTreeMap<LanguageServerId, LanguageServerName> {
self.all_server_ids
.into_iter()
.filter(|(id, _)| !self.rebased_server_ids.contains(id))
.collect()
pub(crate) fn finish(
self,
) -> (
LanguageServerTree,
BTreeMap<LanguageServerId, LanguageServerName>,
) {
(
self.new_tree,
self.all_server_ids
.into_iter()
.filter(|(id, _)| !self.rebased_server_ids.contains(id))
.collect(),
)
}
pub(crate) fn server_tree(&mut self) -> &mut LanguageServerTree {

View file

@ -84,7 +84,7 @@ use lsp::{
};
use lsp_command::*;
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
pub use manifest_tree::ManifestProviders;
pub use manifest_tree::ManifestProvidersStore;
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
pub use prettier_store::PrettierStore;
@ -1115,7 +1115,11 @@ impl Project {
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
toolchain_store.clone(),
toolchain_store
.read(cx)
.as_local_store()
.expect("Toolchain store to be local")
.clone(),
environment.clone(),
manifest_tree,
languages.clone(),
@ -1260,7 +1264,6 @@ impl Project {
LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
Some(toolchain_store.clone()),
languages.clone(),
ssh_proto.clone(),
SSH_PROJECT_ID,
@ -1485,7 +1488,6 @@ impl Project {
let mut lsp_store = LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
None,
languages.clone(),
client.clone().into(),
remote_id,
@ -3596,16 +3598,10 @@ impl Project {
&mut self,
abs_path: lsp::Url,
language_server_id: LanguageServerId,
language_server_name: LanguageServerName,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Buffer>>> {
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.open_local_buffer_via_lsp(
abs_path,
language_server_id,
language_server_name,
cx,
)
lsp_store.open_local_buffer_via_lsp(abs_path, language_server_id, cx)
})
}

View file

@ -22,6 +22,7 @@ use settings::{
SettingsStore, parse_json_with_comments, watch_config_file,
};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
@ -518,16 +519,15 @@ impl Default for InlineBlameSettings {
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)]
pub struct BinarySettings {
pub path: Option<String>,
pub arguments: Option<Vec<String>>,
// this can't be an FxHashMap because the extension APIs require the default SipHash
pub env: Option<std::collections::HashMap<String, String>>,
pub env: Option<BTreeMap<String, String>>,
pub ignore_system_version: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)]
#[serde(rename_all = "snake_case")]
pub struct LspSettings {
pub binary: Option<BinarySettings>,

View file

@ -1099,9 +1099,9 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
let prev_read_dir_count = fs.read_dir_call_count();
let fake_server = fake_servers.next().await.unwrap();
let (server_id, server_name) = lsp_store.read_with(cx, |lsp_store, _| {
let (id, status) = lsp_store.language_server_statuses().next().unwrap();
(id, status.name.clone())
let server_id = lsp_store.read_with(cx, |lsp_store, _| {
let (id, _) = lsp_store.language_server_statuses().next().unwrap();
id
});
// Simulate jumping to a definition in a dependency outside of the worktree.
@ -1110,7 +1110,6 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
project.open_local_buffer_via_lsp(
lsp::Url::from_file_path(path!("/the-registry/dep1/src/dep1.rs")).unwrap(),
server_id,
server_name.clone(),
cx,
)
})

View file

@ -11,7 +11,10 @@ use collections::BTreeMap;
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
};
use language::{LanguageName, LanguageRegistry, LanguageToolchainStore, Toolchain, ToolchainList};
use language::{
LanguageName, LanguageRegistry, LanguageToolchainStore, ManifestDelegate, Toolchain,
ToolchainList,
};
use rpc::{
AnyProtoClient, TypedEnvelope,
proto::{self, FromProto, ToProto},
@ -104,9 +107,11 @@ impl ToolchainStore {
cx: &App,
) -> Task<Option<Toolchain>> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => {
local.read(cx).active_toolchain(path, language_name, cx)
}
ToolchainStoreInner::Local(local, _) => Task::ready(local.read(cx).active_toolchain(
path.worktree_id,
&path.path,
language_name,
)),
ToolchainStoreInner::Remote(remote) => {
remote.read(cx).active_toolchain(path, language_name, cx)
}
@ -232,9 +237,15 @@ impl ToolchainStore {
ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
}
}
pub fn as_local_store(&self) -> Option<&Entity<LocalToolchainStore>> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => Some(local),
ToolchainStoreInner::Remote(_) => None,
}
}
}
struct LocalToolchainStore {
pub struct LocalToolchainStore {
languages: Arc<LanguageRegistry>,
worktree_store: Entity<WorktreeStore>,
project_environment: Entity<ProjectEnvironment>,
@ -243,20 +254,19 @@ struct LocalToolchainStore {
}
#[async_trait(?Send)]
impl language::LanguageToolchainStore for LocalStore {
async fn active_toolchain(
impl language::LocalLanguageToolchainStore for LocalStore {
fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
path: Arc<Path>,
path: &Arc<Path>,
language_name: LanguageName,
cx: &mut AsyncApp,
) -> Option<Toolchain> {
self.0
.update(cx, |this, cx| {
this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
.update(cx, |this, _| {
this.active_toolchain(worktree_id, path, language_name)
})
.ok()?
.await
}
}
@ -279,19 +289,18 @@ impl language::LanguageToolchainStore for RemoteStore {
}
pub struct EmptyToolchainStore;
#[async_trait(?Send)]
impl language::LanguageToolchainStore for EmptyToolchainStore {
async fn active_toolchain(
impl language::LocalLanguageToolchainStore for EmptyToolchainStore {
fn active_toolchain(
self: Arc<Self>,
_: WorktreeId,
_: Arc<Path>,
_: &Arc<Path>,
_: LanguageName,
_: &mut AsyncApp,
) -> Option<Toolchain> {
None
}
}
struct LocalStore(WeakEntity<LocalToolchainStore>);
pub(crate) struct LocalStore(WeakEntity<LocalToolchainStore>);
struct RemoteStore(WeakEntity<RemoteToolchainStore>);
#[derive(Clone)]
@ -349,17 +358,13 @@ impl LocalToolchainStore {
.flatten()?;
let worktree_id = snapshot.id();
let worktree_root = snapshot.abs_path().to_path_buf();
let delegate =
Arc::from(ManifestQueryDelegate::new(snapshot)) as Arc<dyn ManifestDelegate>;
let relative_path = manifest_tree
.update(cx, |this, cx| {
this.root_for_path(
path,
&mut std::iter::once(manifest_name.clone()),
Arc::new(ManifestQueryDelegate::new(snapshot)),
cx,
)
this.root_for_path(&path, &manifest_name, &delegate, cx)
})
.ok()?
.remove(&manifest_name)
.unwrap_or_else(|| ProjectPath {
path: Arc::from(Path::new("")),
worktree_id,
@ -394,21 +399,20 @@ impl LocalToolchainStore {
}
pub(crate) fn active_toolchain(
&self,
path: ProjectPath,
worktree_id: WorktreeId,
relative_path: &Arc<Path>,
language_name: LanguageName,
_: &App,
) -> Task<Option<Toolchain>> {
let ancestors = path.path.ancestors();
Task::ready(
self.active_toolchains
.get(&(path.worktree_id, language_name))
.and_then(|paths| {
ancestors
.into_iter()
.find_map(|root_path| paths.get(root_path))
})
.cloned(),
)
) -> Option<Toolchain> {
let ancestors = relative_path.ancestors();
self.active_toolchains
.get(&(worktree_id, language_name))
.and_then(|paths| {
ancestors
.into_iter()
.find_map(|root_path| paths.get(root_path))
})
.cloned()
}
}
struct RemoteToolchainStore {

View file

@ -171,7 +171,11 @@ impl HeadlessProject {
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
toolchain_store.clone(),
toolchain_store
.read(cx)
.as_local_store()
.expect("Toolchain store to be local")
.clone(),
environment,
manifest_tree,
languages.clone(),