Detailed changes
@@ -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)
})
}
@@ -1275,6 +1275,7 @@ impl ExtensionStore {
queries,
context_provider,
toolchain_provider: None,
+ manifest_name: None,
})
}),
);
@@ -163,6 +163,7 @@ impl HeadlessExtensionStore {
queries: LanguageQueries::default(),
context_provider: None,
toolchain_provider: None,
+ manifest_name: None,
})
}),
);
@@ -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,
@@ -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);
}
@@ -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,
@@ -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))
}
}
@@ -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)
@@ -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 {
@@ -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 _;
@@ -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(
@@ -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?;
@@ -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!({
@@ -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?;
@@ -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?;
@@ -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(),
})
}),
);
@@ -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
}
@@ -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?;
@@ -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| {
@@ -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();
@@ -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;
@@ -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 {
@@ -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,
)
})?
@@ -1,3 +1,14 @@
+//! LSP store provides unified access to the language server protocol.
+//! The consumers of LSP store can interact with language servers without knowing exactly which language server they're interacting with.
+//!
+//! # Local/Remote LSP Stores
+//! This module is split up into three distinct parts:
+//! - [`LocalLspStore`], which is ran on the host machine (either project host or SSH host), that manages the lifecycle of language servers.
+//! - [`RemoteLspStore`], which is ran on the remote machine (project guests) which is mostly about passing through the requests via RPC.
+//! The remote stores don't really care about which language server they're running against - they don't usually get to decide which language server is going to responsible for handling their request.
+//! - [`LspStore`], which unifies the two under one consistent interface for interacting with language servers.
+//!
+//! Most of the interesting work happens at the local layer, as bulk of the complexity is with managing the lifecycle of language servers. The actual implementation of the LSP protocol is handled by [`lsp`] crate.
pub mod clangd_ext;
pub mod json_language_server_ext;
pub mod lsp_ext_command;
@@ -6,20 +17,20 @@ pub mod rust_analyzer_ext;
use crate::{
CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource,
CoreCompletion, DocumentColor, Hover, InlayHint, LocationLink, LspAction, LspPullDiagnostics,
- ProjectItem, ProjectPath, ProjectTransaction, PulledDiagnostics, ResolveState, Symbol,
- ToolchainStore,
+ ManifestProvidersStore, ProjectItem, ProjectPath, ProjectTransaction, PulledDiagnostics,
+ ResolveState, Symbol,
buffer_store::{BufferStore, BufferStoreEvent},
environment::ProjectEnvironment,
lsp_command::{self, *},
lsp_store,
manifest_tree::{
- AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition,
- ManifestQueryDelegate, ManifestTree,
+ LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, ManifestQueryDelegate,
+ ManifestTree,
},
prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings},
relativize_path, resolve_path,
- toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
+ toolchain_store::{LocalToolchainStore, ToolchainStoreEvent},
worktree_store::{WorktreeStore, WorktreeStoreEvent},
yarn::YarnPathStore,
};
@@ -44,9 +55,9 @@ use itertools::Itertools as _;
use language::{
Bias, BinaryStatus, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName,
- LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch,
- PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
- WorkspaceFoldersContent,
+ LanguageRegistry, LocalFile, LspAdapter, LspAdapterDelegate, ManifestDelegate, ManifestName,
+ Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Toolchain, Transaction,
+ Unclipped, WorkspaceFoldersContent,
language_settings::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
},
@@ -140,6 +151,20 @@ impl FormatTrigger {
}
}
+#[derive(Clone)]
+struct UnifiedLanguageServer {
+ id: LanguageServerId,
+ project_roots: HashSet<Arc<Path>>,
+}
+
+#[derive(Clone, Hash, PartialEq, Eq)]
+struct LanguageServerSeed {
+ worktree_id: WorktreeId,
+ name: LanguageServerName,
+ toolchain: Option<Toolchain>,
+ settings: Arc<LspSettings>,
+}
+
#[derive(Debug)]
pub struct DocumentDiagnosticsUpdate<'a, D> {
pub diagnostics: D,
@@ -157,17 +182,18 @@ pub struct DocumentDiagnostics {
pub struct LocalLspStore {
weak: WeakEntity<LspStore>,
worktree_store: Entity<WorktreeStore>,
- toolchain_store: Entity<ToolchainStore>,
+ toolchain_store: Entity<LocalToolchainStore>,
http_client: Arc<dyn HttpClient>,
environment: Entity<ProjectEnvironment>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
- language_server_ids: HashMap<(WorktreeId, LanguageServerName), BTreeSet<LanguageServerId>>,
+ language_server_ids: HashMap<LanguageServerSeed, UnifiedLanguageServer>,
yarn: Entity<YarnPathStore>,
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
buffers_being_formatted: HashSet<BufferId>,
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
language_server_watched_paths: HashMap<LanguageServerId, LanguageServerWatchedPaths>,
+ watched_manifest_filenames: HashSet<ManifestName>,
language_server_paths_watched_for_rename:
HashMap<LanguageServerId, RenamePathsWatchedForServer>,
language_server_watcher_registrations:
@@ -188,7 +214,7 @@ pub struct LocalLspStore {
>,
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
_subscription: gpui::Subscription,
- lsp_tree: Entity<LanguageServerTree>,
+ lsp_tree: LanguageServerTree,
registered_buffers: HashMap<BufferId, usize>,
buffers_opened_in_servers: HashMap<BufferId, HashSet<LanguageServerId>>,
buffer_pull_diagnostics_result_ids: HashMap<LanguageServerId, HashMap<PathBuf, Option<String>>>,
@@ -208,19 +234,63 @@ impl LocalLspStore {
}
}
+ fn get_or_insert_language_server(
+ &mut self,
+ worktree_handle: &Entity<Worktree>,
+ delegate: Arc<LocalLspAdapterDelegate>,
+ disposition: &Arc<LaunchDisposition>,
+ language_name: &LanguageName,
+ cx: &mut App,
+ ) -> LanguageServerId {
+ let key = LanguageServerSeed {
+ worktree_id: worktree_handle.read(cx).id(),
+ name: disposition.server_name.clone(),
+ settings: disposition.settings.clone(),
+ toolchain: disposition.toolchain.clone(),
+ };
+ if let Some(state) = self.language_server_ids.get_mut(&key) {
+ state.project_roots.insert(disposition.path.path.clone());
+ state.id
+ } else {
+ let adapter = self
+ .languages
+ .lsp_adapters(language_name)
+ .into_iter()
+ .find(|adapter| adapter.name() == disposition.server_name)
+ .expect("To find LSP adapter");
+ let new_language_server_id = self.start_language_server(
+ worktree_handle,
+ delegate,
+ adapter,
+ disposition.settings.clone(),
+ key.clone(),
+ cx,
+ );
+ if let Some(state) = self.language_server_ids.get_mut(&key) {
+ state.project_roots.insert(disposition.path.path.clone());
+ } else {
+ debug_assert!(
+ false,
+ "Expected `start_language_server` to ensure that `key` exists in a map"
+ );
+ }
+ new_language_server_id
+ }
+ }
+
fn start_language_server(
&mut self,
worktree_handle: &Entity<Worktree>,
delegate: Arc<LocalLspAdapterDelegate>,
adapter: Arc<CachedLspAdapter>,
settings: Arc<LspSettings>,
+ key: LanguageServerSeed,
cx: &mut App,
) -> LanguageServerId {
let worktree = worktree_handle.read(cx);
- let worktree_id = worktree.id();
- let root_path = worktree.abs_path();
- let key = (worktree_id, adapter.name.clone());
+ let root_path = worktree.abs_path();
+ let toolchain = key.toolchain.clone();
let override_options = settings.initialization_options.clone();
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
@@ -231,7 +301,14 @@ impl LocalLspStore {
adapter.name.0
);
- let binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx);
+ let binary = self.get_language_server_binary(
+ adapter.clone(),
+ settings,
+ toolchain.clone(),
+ delegate.clone(),
+ true,
+ cx,
+ );
let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
let pending_server = cx.spawn({
@@ -290,15 +367,13 @@ impl LocalLspStore {
.enabled;
cx.spawn(async move |cx| {
let result = async {
- let toolchains =
- lsp_store.update(cx, |lsp_store, cx| lsp_store.toolchain_store(cx))?;
let language_server = pending_server.await?;
let workspace_config = Self::workspace_configuration_for_adapter(
adapter.adapter.clone(),
fs.as_ref(),
&delegate,
- toolchains.clone(),
+ toolchain,
cx,
)
.await?;
@@ -417,31 +492,26 @@ impl LocalLspStore {
self.language_servers.insert(server_id, state);
self.language_server_ids
.entry(key)
- .or_default()
- .insert(server_id);
+ .or_insert(UnifiedLanguageServer {
+ id: server_id,
+ project_roots: Default::default(),
+ });
server_id
}
fn get_language_server_binary(
&self,
adapter: Arc<CachedLspAdapter>,
+ settings: Arc<LspSettings>,
+ toolchain: Option<Toolchain>,
delegate: Arc<dyn LspAdapterDelegate>,
allow_binary_download: bool,
cx: &mut App,
) -> Task<Result<LanguageServerBinary>> {
- let settings = ProjectSettings::get(
- Some(SettingsLocation {
- worktree_id: delegate.worktree_id(),
- path: Path::new(""),
- }),
- cx,
- )
- .lsp
- .get(&adapter.name)
- .and_then(|s| s.binary.clone());
-
- if settings.as_ref().is_some_and(|b| b.path.is_some()) {
- let settings = settings.unwrap();
+ if let Some(settings) = settings.binary.as_ref()
+ && settings.path.is_some()
+ {
+ let settings = settings.clone();
return cx.background_spawn(async move {
let mut env = delegate.shell_env().await;
@@ -461,16 +531,17 @@ impl LocalLspStore {
}
let lsp_binary_options = LanguageServerBinaryOptions {
allow_path_lookup: !settings
+ .binary
.as_ref()
.and_then(|b| b.ignore_system_version)
.unwrap_or_default(),
allow_binary_download,
};
- let toolchains = self.toolchain_store.read(cx).as_language_toolchain_store();
+
cx.spawn(async move |cx| {
let binary_result = adapter
.clone()
- .get_language_server_command(delegate.clone(), toolchains, lsp_binary_options, cx)
+ .get_language_server_command(delegate.clone(), toolchain, lsp_binary_options, cx)
.await;
delegate.update_status(adapter.name.clone(), BinaryStatus::None);
@@ -480,12 +551,12 @@ impl LocalLspStore {
shell_env.extend(binary.env.unwrap_or_default());
- if let Some(settings) = settings {
- if let Some(arguments) = settings.arguments {
+ if let Some(settings) = settings.binary.as_ref() {
+ if let Some(arguments) = &settings.arguments {
binary.arguments = arguments.into_iter().map(Into::into).collect();
}
- if let Some(env) = settings.env {
- shell_env.extend(env);
+ if let Some(env) = &settings.env {
+ shell_env.extend(env.iter().map(|(k, v)| (k.clone(), v.clone())));
}
}
@@ -559,14 +630,20 @@ impl LocalLspStore {
let fs = fs.clone();
let mut cx = cx.clone();
async move {
- let toolchains =
- this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
-
+ let toolchain_for_id = this
+ .update(&mut cx, |this, _| {
+ this.as_local()?.language_server_ids.iter().find_map(
+ |(seed, value)| {
+ (value.id == server_id).then(|| seed.toolchain.clone())
+ },
+ )
+ })?
+ .context("Expected the LSP store to be in a local mode")?;
let workspace_config = Self::workspace_configuration_for_adapter(
adapter.clone(),
fs.as_ref(),
&delegate,
- toolchains.clone(),
+ toolchain_for_id,
&mut cx,
)
.await?;
@@ -700,18 +777,15 @@ impl LocalLspStore {
language_server
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
- let adapter = adapter.clone();
let this = this.clone();
move |params, cx| {
let mut cx = cx.clone();
let this = this.clone();
- let adapter = adapter.clone();
async move {
LocalLspStore::on_lsp_workspace_edit(
this.clone(),
params,
server_id,
- adapter.clone(),
&mut cx,
)
.await
@@ -960,19 +1034,18 @@ impl LocalLspStore {
) -> impl Iterator<Item = &Arc<LanguageServer>> {
self.language_server_ids
.iter()
- .flat_map(move |((language_server_path, _), ids)| {
- ids.iter().filter_map(move |id| {
- if *language_server_path != worktree_id {
- return None;
- }
- if let Some(LanguageServerState::Running { server, .. }) =
- self.language_servers.get(id)
- {
- return Some(server);
- } else {
- None
- }
- })
+ .filter_map(move |(seed, state)| {
+ if seed.worktree_id != worktree_id {
+ return None;
+ }
+
+ if let Some(LanguageServerState::Running { server, .. }) =
+ self.language_servers.get(&state.id)
+ {
+ return Some(server);
+ } else {
+ None
+ }
})
}
@@ -989,17 +1062,18 @@ impl LocalLspStore {
else {
return Vec::new();
};
- let delegate = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
- let root = self.lsp_tree.update(cx, |this, cx| {
- this.get(
+ let delegate: Arc<dyn ManifestDelegate> =
+ Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
+ let root = self
+ .lsp_tree
+ .get(
project_path,
- AdapterQuery::Language(&language.name()),
- delegate,
+ language.name(),
+ language.manifest(),
+ &delegate,
cx,
)
- .filter_map(|node| node.server_id())
- .collect::<Vec<_>>()
- });
+ .collect::<Vec<_>>();
root
}
@@ -1083,7 +1157,7 @@ impl LocalLspStore {
.collect::<Vec<_>>()
})
})?;
- for (lsp_adapter, language_server) in adapters_and_servers.iter() {
+ for (_, language_server) in adapters_and_servers.iter() {
let actions = Self::get_server_code_actions_from_action_kinds(
&lsp_store,
language_server.server_id(),
@@ -1095,7 +1169,6 @@ impl LocalLspStore {
Self::execute_code_actions_on_server(
&lsp_store,
language_server,
- lsp_adapter,
actions,
push_to_history,
&mut project_transaction,
@@ -2038,13 +2111,14 @@ impl LocalLspStore {
let buffer = buffer_handle.read(cx);
let file = buffer.file().cloned();
+
let Some(file) = File::from_dyn(file.as_ref()) else {
return;
};
if !file.is_local() {
return;
}
-
+ let path = ProjectPath::from_file(file, cx);
let worktree_id = file.worktree_id(cx);
let language = buffer.language().cloned();
@@ -2067,46 +2141,52 @@ impl LocalLspStore {
let Some(language) = language else {
return;
};
- for adapter in self.languages.lsp_adapters(&language.name()) {
- let servers = self
- .language_server_ids
- .get(&(worktree_id, adapter.name.clone()));
- if let Some(server_ids) = servers {
- for server_id in server_ids {
- let server = self
- .language_servers
- .get(server_id)
- .and_then(|server_state| {
- if let LanguageServerState::Running { server, .. } = server_state {
- Some(server.clone())
- } else {
- None
- }
- });
- let server = match server {
- Some(server) => server,
- None => continue,
- };
+ let Some(snapshot) = self
+ .worktree_store
+ .read(cx)
+ .worktree_for_id(worktree_id, cx)
+ .map(|worktree| worktree.read(cx).snapshot())
+ else {
+ return;
+ };
+ let delegate: Arc<dyn ManifestDelegate> = Arc::new(ManifestQueryDelegate::new(snapshot));
- buffer_handle.update(cx, |buffer, cx| {
- buffer.set_completion_triggers(
- server.server_id(),
- server
- .capabilities()
- .completion_provider
+ for server_id in
+ self.lsp_tree
+ .get(path, language.name(), language.manifest(), &delegate, cx)
+ {
+ let server = self
+ .language_servers
+ .get(&server_id)
+ .and_then(|server_state| {
+ if let LanguageServerState::Running { server, .. } = server_state {
+ Some(server.clone())
+ } else {
+ None
+ }
+ });
+ let server = match server {
+ Some(server) => server,
+ None => continue,
+ };
+
+ buffer_handle.update(cx, |buffer, cx| {
+ buffer.set_completion_triggers(
+ server.server_id(),
+ server
+ .capabilities()
+ .completion_provider
+ .as_ref()
+ .and_then(|provider| {
+ provider
+ .trigger_characters
.as_ref()
- .and_then(|provider| {
- provider
- .trigger_characters
- .as_ref()
- .map(|characters| characters.iter().cloned().collect())
- })
- .unwrap_or_default(),
- cx,
- );
- });
- }
- }
+ .map(|characters| characters.iter().cloned().collect())
+ })
+ .unwrap_or_default(),
+ cx,
+ );
+ });
}
}
@@ -2216,6 +2296,31 @@ impl LocalLspStore {
Ok(())
}
+ fn register_language_server_for_invisible_worktree(
+ &mut self,
+ worktree: &Entity<Worktree>,
+ language_server_id: LanguageServerId,
+ cx: &mut App,
+ ) {
+ let worktree = worktree.read(cx);
+ let worktree_id = worktree.id();
+ debug_assert!(!worktree.is_visible());
+ let Some(mut origin_seed) = self
+ .language_server_ids
+ .iter()
+ .find_map(|(seed, state)| (state.id == language_server_id).then(|| seed.clone()))
+ else {
+ return;
+ };
+ origin_seed.worktree_id = worktree_id;
+ self.language_server_ids
+ .entry(origin_seed)
+ .or_insert_with(|| UnifiedLanguageServer {
+ id: language_server_id,
+ project_roots: Default::default(),
+ });
+ }
+
fn register_buffer_with_language_servers(
&mut self,
buffer_handle: &Entity<Buffer>,
@@ -2256,27 +2361,23 @@ impl LocalLspStore {
};
let language_name = language.name();
let (reused, delegate, servers) = self
- .lsp_tree
- .update(cx, |lsp_tree, cx| {
- self.reuse_existing_language_server(lsp_tree, &worktree, &language_name, cx)
- })
- .map(|(delegate, servers)| (true, delegate, servers))
+ .reuse_existing_language_server(&self.lsp_tree, &worktree, &language_name, cx)
+ .map(|(delegate, apply)| (true, delegate, apply(&mut self.lsp_tree)))
.unwrap_or_else(|| {
let lsp_delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
- let delegate = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
+ let delegate: Arc<dyn ManifestDelegate> =
+ Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
+
let servers = self
.lsp_tree
- .clone()
- .update(cx, |language_server_tree, cx| {
- language_server_tree
- .get(
- ProjectPath { worktree_id, path },
- AdapterQuery::Language(&language.name()),
- delegate.clone(),
- cx,
- )
- .collect::<Vec<_>>()
- });
+ .walk(
+ ProjectPath { worktree_id, path },
+ language.name(),
+ language.manifest(),
+ &delegate,
+ cx,
+ )
+ .collect::<Vec<_>>();
(false, lsp_delegate, servers)
});
let servers_and_adapters = servers
@@ -2298,55 +2399,35 @@ impl LocalLspStore {
}
}
- let server_id = server_node.server_id_or_init(
- |LaunchDisposition {
- server_name,
- path,
- settings,
- }| {
- let server_id =
- {
- let uri = Url::from_file_path(
- worktree.read(cx).abs_path().join(&path.path),
- );
- let key = (worktree_id, server_name.clone());
- if !self.language_server_ids.contains_key(&key) {
- let language_name = language.name();
- let adapter = self.languages
- .lsp_adapters(&language_name)
- .into_iter()
- .find(|adapter| &adapter.name() == server_name)
- .expect("To find LSP adapter");
- self.start_language_server(
- &worktree,
- delegate.clone(),
- adapter,
- settings,
- cx,
- );
- }
- if let Some(server_ids) = self
- .language_server_ids
- .get(&key)
- {
- debug_assert_eq!(server_ids.len(), 1);
- let server_id = server_ids.iter().cloned().next().unwrap();
- if let Some(state) = self.language_servers.get(&server_id) {
- if let Ok(uri) = uri {
- state.add_workspace_folder(uri);
- };
- }
- server_id
- } else {
- unreachable!("Language server ID should be available, as it's registered on demand")
- }
+ let server_id = server_node.server_id_or_init(|disposition| {
+ let path = &disposition.path;
+ let server_id = {
+ let uri =
+ Url::from_file_path(worktree.read(cx).abs_path().join(&path.path));
+
+ let server_id = self.get_or_insert_language_server(
+ &worktree,
+ delegate.clone(),
+ disposition,
+ &language_name,
+ cx,
+ );
- };
+ if let Some(state) = self.language_servers.get(&server_id) {
+ if let Ok(uri) = uri {
+ state.add_workspace_folder(uri);
+ };
+ }
server_id
- },
- )?;
+ };
+
+ server_id
+ })?;
let server_state = self.language_servers.get(&server_id)?;
- if let LanguageServerState::Running { server, adapter, .. } = server_state {
+ if let LanguageServerState::Running {
+ server, adapter, ..
+ } = server_state
+ {
Some((server.clone(), adapter.clone()))
} else {
None
@@ -2413,13 +2494,16 @@ impl LocalLspStore {
}
}
- fn reuse_existing_language_server(
+ fn reuse_existing_language_server<'lang_name>(
&self,
- server_tree: &mut LanguageServerTree,
+ server_tree: &LanguageServerTree,
worktree: &Entity<Worktree>,
- language_name: &LanguageName,
+ language_name: &'lang_name LanguageName,
cx: &mut App,
- ) -> Option<(Arc<LocalLspAdapterDelegate>, Vec<LanguageServerTreeNode>)> {
+ ) -> Option<(
+ Arc<LocalLspAdapterDelegate>,
+ impl FnOnce(&mut LanguageServerTree) -> Vec<LanguageServerTreeNode> + use<'lang_name>,
+ )> {
if worktree.read(cx).is_visible() {
return None;
}
@@ -2458,16 +2542,16 @@ impl LocalLspStore {
.into_values()
.max_by_key(|servers| servers.len())?;
- for server_node in &servers {
- server_tree.register_reused(
- worktree.read(cx).id(),
- language_name.clone(),
- server_node.clone(),
- );
- }
+ let worktree_id = worktree.read(cx).id();
+ let apply = move |tree: &mut LanguageServerTree| {
+ for server_node in &servers {
+ tree.register_reused(worktree_id, language_name.clone(), server_node.clone());
+ }
+ servers
+ };
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, worktree, cx);
- Some((delegate, servers))
+ Some((delegate, apply))
}
pub(crate) fn unregister_old_buffer_from_language_servers(
@@ -2568,7 +2652,7 @@ impl LocalLspStore {
pub async fn execute_code_actions_on_server(
lsp_store: &WeakEntity<LspStore>,
language_server: &Arc<LanguageServer>,
- lsp_adapter: &Arc<CachedLspAdapter>,
+
actions: Vec<CodeAction>,
push_to_history: bool,
project_transaction: &mut ProjectTransaction,
@@ -2588,7 +2672,6 @@ impl LocalLspStore {
lsp_store.upgrade().context("project dropped")?,
edit.clone(),
push_to_history,
- lsp_adapter.clone(),
language_server.clone(),
cx,
)
@@ -2769,7 +2852,6 @@ impl LocalLspStore {
this: Entity<LspStore>,
edit: lsp::WorkspaceEdit,
push_to_history: bool,
- lsp_adapter: Arc<CachedLspAdapter>,
language_server: Arc<LanguageServer>,
cx: &mut AsyncApp,
) -> Result<ProjectTransaction> {
@@ -2870,7 +2952,6 @@ impl LocalLspStore {
this.open_local_buffer_via_lsp(
op.text_document.uri.clone(),
language_server.server_id(),
- lsp_adapter.name.clone(),
cx,
)
})?
@@ -2995,7 +3076,6 @@ impl LocalLspStore {
this: WeakEntity<LspStore>,
params: lsp::ApplyWorkspaceEditParams,
server_id: LanguageServerId,
- adapter: Arc<CachedLspAdapter>,
cx: &mut AsyncApp,
) -> Result<lsp::ApplyWorkspaceEditResponse> {
let this = this.upgrade().context("project project closed")?;
@@ -3006,7 +3086,6 @@ impl LocalLspStore {
this.clone(),
params.edit,
true,
- adapter.clone(),
language_server.clone(),
cx,
)
@@ -3037,23 +3116,19 @@ impl LocalLspStore {
prettier_store.remove_worktree(id_to_remove, cx);
});
- let mut servers_to_remove = BTreeMap::default();
+ let mut servers_to_remove = BTreeSet::default();
let mut servers_to_preserve = HashSet::default();
- for ((path, server_name), ref server_ids) in &self.language_server_ids {
- if *path == id_to_remove {
- servers_to_remove.extend(server_ids.iter().map(|id| (*id, server_name.clone())));
+ for (seed, ref state) in &self.language_server_ids {
+ if seed.worktree_id == id_to_remove {
+ servers_to_remove.insert(state.id);
} else {
- servers_to_preserve.extend(server_ids.iter().cloned());
+ servers_to_preserve.insert(state.id);
}
}
- servers_to_remove.retain(|server_id, _| !servers_to_preserve.contains(server_id));
-
- for (server_id_to_remove, _) in &servers_to_remove {
- self.language_server_ids
- .values_mut()
- .for_each(|server_ids| {
- server_ids.remove(server_id_to_remove);
- });
+ servers_to_remove.retain(|server_id| !servers_to_preserve.contains(server_id));
+ self.language_server_ids
+ .retain(|_, state| !servers_to_remove.contains(&state.id));
+ for server_id_to_remove in &servers_to_remove {
self.language_server_watched_paths
.remove(server_id_to_remove);
self.language_server_paths_watched_for_rename
@@ -3068,7 +3143,7 @@ impl LocalLspStore {
}
cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove));
}
- servers_to_remove.into_keys().collect()
+ servers_to_remove.into_iter().collect()
}
fn rebuild_watched_paths_inner<'a>(
@@ -3326,16 +3401,20 @@ impl LocalLspStore {
Ok(Some(initialization_config))
}
+ fn toolchain_store(&self) -> &Entity<LocalToolchainStore> {
+ &self.toolchain_store
+ }
+
async fn workspace_configuration_for_adapter(
adapter: Arc<dyn LspAdapter>,
fs: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
- toolchains: Arc<dyn LanguageToolchainStore>,
+ toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<serde_json::Value> {
let mut workspace_config = adapter
.clone()
- .workspace_configuration(fs, delegate, toolchains.clone(), cx)
+ .workspace_configuration(fs, delegate, toolchain, cx)
.await?;
for other_adapter in delegate.registered_lsp_adapters() {
@@ -3344,13 +3423,7 @@ impl LocalLspStore {
}
if let Ok(Some(target_config)) = other_adapter
.clone()
- .additional_workspace_configuration(
- adapter.name(),
- fs,
- delegate,
- toolchains.clone(),
- cx,
- )
+ .additional_workspace_configuration(adapter.name(), fs, delegate, cx)
.await
{
merge_json_value_into(target_config.clone(), &mut workspace_config);
@@ -3416,7 +3489,6 @@ pub struct LspStore {
nonce: u128,
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
- toolchain_store: Option<Entity<ToolchainStore>>,
pub languages: Arc<LanguageRegistry>,
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
active_entry: Option<ProjectEntryId>,
@@ -3607,7 +3679,7 @@ impl LspStore {
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
prettier_store: Entity<PrettierStore>,
- toolchain_store: Entity<ToolchainStore>,
+ toolchain_store: Entity<LocalToolchainStore>,
environment: Entity<ProjectEnvironment>,
manifest_tree: Entity<ManifestTree>,
languages: Arc<LanguageRegistry>,
@@ -3649,7 +3721,7 @@ impl LspStore {
mode: LspStoreMode::Local(LocalLspStore {
weak: cx.weak_entity(),
worktree_store: worktree_store.clone(),
- toolchain_store: toolchain_store.clone(),
+
supplementary_language_servers: Default::default(),
languages: languages.clone(),
language_server_ids: Default::default(),
@@ -3672,16 +3744,22 @@ impl LspStore {
.unwrap()
.shutdown_language_servers_on_quit(cx)
}),
- lsp_tree: LanguageServerTree::new(manifest_tree, languages.clone(), cx),
+ lsp_tree: LanguageServerTree::new(
+ manifest_tree,
+ languages.clone(),
+ toolchain_store.clone(),
+ ),
+ toolchain_store,
registered_buffers: HashMap::default(),
buffers_opened_in_servers: HashMap::default(),
buffer_pull_diagnostics_result_ids: HashMap::default(),
+ watched_manifest_filenames: ManifestProvidersStore::global(cx)
+ .manifest_file_names(),
}),
last_formatting_failure: None,
downstream_client: None,
buffer_store,
worktree_store,
- toolchain_store: Some(toolchain_store),
languages: languages.clone(),
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().r#gen(),
@@ -3719,7 +3797,6 @@ impl LspStore {
pub(super) fn new_remote(
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
- toolchain_store: Option<Entity<ToolchainStore>>,
languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient,
project_id: u64,
@@ -3752,7 +3829,7 @@ impl LspStore {
lsp_document_colors: HashMap::default(),
lsp_code_lens: HashMap::default(),
active_entry: None,
- toolchain_store,
+
_maintain_workspace_config,
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
@@ -3851,7 +3928,7 @@ impl LspStore {
fn on_toolchain_store_event(
&mut self,
- _: Entity<ToolchainStore>,
+ _: Entity<LocalToolchainStore>,
event: &ToolchainStoreEvent,
_: &mut Context<Self>,
) {
@@ -3930,9 +4007,9 @@ impl LspStore {
let local = this.as_local()?;
let mut servers = Vec::new();
- for ((worktree_id, _), server_ids) in &local.language_server_ids {
- for server_id in server_ids {
- let Some(states) = local.language_servers.get(server_id) else {
+ for (seed, state) in &local.language_server_ids {
+
+ let Some(states) = local.language_servers.get(&state.id) else {
continue;
};
let (json_adapter, json_server) = match states {
@@ -3947,7 +4024,7 @@ impl LspStore {
let Some(worktree) = this
.worktree_store
.read(cx)
- .worktree_for_id(*worktree_id, cx)
+ .worktree_for_id(seed.worktree_id, cx)
else {
continue;
};
@@ -3963,7 +4040,7 @@ impl LspStore {
);
servers.push((json_adapter, json_server, json_delegate));
- }
+
}
return Some(servers);
})
@@ -3974,9 +4051,9 @@ impl LspStore {
return;
};
- let Ok(Some((fs, toolchain_store))) = this.read_with(cx, |this, cx| {
+ let Ok(Some((fs, _))) = this.read_with(cx, |this, _| {
let local = this.as_local()?;
- let toolchain_store = this.toolchain_store(cx);
+ let toolchain_store = local.toolchain_store().clone();
return Some((local.fs.clone(), toolchain_store));
}) else {
return;
@@ -3988,7 +4065,7 @@ impl LspStore {
adapter,
fs.as_ref(),
&delegate,
- toolchain_store.clone(),
+ None,
cx,
)
.await
@@ -4533,7 +4610,7 @@ impl LspStore {
}
}
- self.refresh_server_tree(cx);
+ self.request_workspace_config_refresh();
if let Some(prettier_store) = self.as_local().map(|s| s.prettier_store.clone()) {
prettier_store.update(cx, |prettier_store, cx| {
@@ -4546,158 +4623,150 @@ impl LspStore {
fn refresh_server_tree(&mut self, cx: &mut Context<Self>) {
let buffer_store = self.buffer_store.clone();
- if let Some(local) = self.as_local_mut() {
- let mut adapters = BTreeMap::default();
- let get_adapter = {
- let languages = local.languages.clone();
- let environment = local.environment.clone();
- let weak = local.weak.clone();
- let worktree_store = local.worktree_store.clone();
- let http_client = local.http_client.clone();
- let fs = local.fs.clone();
- move |worktree_id, cx: &mut App| {
- let worktree = worktree_store.read(cx).worktree_for_id(worktree_id, cx)?;
- Some(LocalLspAdapterDelegate::new(
- languages.clone(),
- &environment,
- weak.clone(),
- &worktree,
- http_client.clone(),
- fs.clone(),
- cx,
- ))
- }
- };
+ let Some(local) = self.as_local_mut() else {
+ return;
+ };
+ let mut adapters = BTreeMap::default();
+ let get_adapter = {
+ let languages = local.languages.clone();
+ let environment = local.environment.clone();
+ let weak = local.weak.clone();
+ let worktree_store = local.worktree_store.clone();
+ let http_client = local.http_client.clone();
+ let fs = local.fs.clone();
+ move |worktree_id, cx: &mut App| {
+ let worktree = worktree_store.read(cx).worktree_for_id(worktree_id, cx)?;
+ Some(LocalLspAdapterDelegate::new(
+ languages.clone(),
+ &environment,
+ weak.clone(),
+ &worktree,
+ http_client.clone(),
+ fs.clone(),
+ cx,
+ ))
+ }
+ };
- let mut messages_to_report = Vec::new();
- let to_stop = local.lsp_tree.clone().update(cx, |lsp_tree, cx| {
- let mut rebase = lsp_tree.rebase();
- for buffer_handle in buffer_store.read(cx).buffers().sorted_by_key(|buffer| {
- Reverse(
- File::from_dyn(buffer.read(cx).file())
- .map(|file| file.worktree.read(cx).is_visible()),
- )
- }) {
- let buffer = buffer_handle.read(cx);
- let buffer_id = buffer.remote_id();
- if !local.registered_buffers.contains_key(&buffer_id) {
- continue;
- }
- if let Some((file, language)) = File::from_dyn(buffer.file())
- .cloned()
- .zip(buffer.language().map(|l| l.name()))
+ let mut messages_to_report = Vec::new();
+ let (new_tree, to_stop) = {
+ let mut rebase = local.lsp_tree.rebase();
+ let buffers = buffer_store
+ .read(cx)
+ .buffers()
+ .filter_map(|buffer| {
+ let raw_buffer = buffer.read(cx);
+ if !local
+ .registered_buffers
+ .contains_key(&raw_buffer.remote_id())
{
- let worktree_id = file.worktree_id(cx);
- let Some(worktree) = local
- .worktree_store
- .read(cx)
- .worktree_for_id(worktree_id, cx)
- else {
- continue;
- };
+ return None;
+ }
+ let file = File::from_dyn(raw_buffer.file()).cloned()?;
+ let language = raw_buffer.language().cloned()?;
+ Some((file, language, raw_buffer.remote_id()))
+ })
+ .sorted_by_key(|(file, _, _)| Reverse(file.worktree.read(cx).is_visible()));
- let Some((reused, delegate, nodes)) = local
- .reuse_existing_language_server(
- rebase.server_tree(),
- &worktree,
- &language,
- cx,
- )
- .map(|(delegate, servers)| (true, delegate, servers))
- .or_else(|| {
- let lsp_delegate = adapters
- .entry(worktree_id)
- .or_insert_with(|| get_adapter(worktree_id, cx))
- .clone()?;
- let delegate = Arc::new(ManifestQueryDelegate::new(
- worktree.read(cx).snapshot(),
- ));
- let path = file
- .path()
- .parent()
- .map(Arc::from)
- .unwrap_or_else(|| file.path().clone());
- let worktree_path = ProjectPath { worktree_id, path };
-
- let nodes = rebase.get(
- worktree_path,
- AdapterQuery::Language(&language),
- delegate.clone(),
- cx,
- );
+ for (file, language, buffer_id) in buffers {
+ let worktree_id = file.worktree_id(cx);
+ let Some(worktree) = local
+ .worktree_store
+ .read(cx)
+ .worktree_for_id(worktree_id, cx)
+ else {
+ continue;
+ };
- Some((false, lsp_delegate, nodes.collect()))
- })
- else {
- continue;
- };
+ if let Some((_, apply)) = local.reuse_existing_language_server(
+ rebase.server_tree(),
+ &worktree,
+ &language.name(),
+ cx,
+ ) {
+ (apply)(rebase.server_tree());
+ } else if let Some(lsp_delegate) = adapters
+ .entry(worktree_id)
+ .or_insert_with(|| get_adapter(worktree_id, cx))
+ .clone()
+ {
+ let delegate =
+ Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
+ let path = file
+ .path()
+ .parent()
+ .map(Arc::from)
+ .unwrap_or_else(|| file.path().clone());
+ let worktree_path = ProjectPath { worktree_id, path };
+ let abs_path = file.abs_path(cx);
+ let worktree_root = worktree.read(cx).abs_path();
+ let nodes = rebase
+ .walk(
+ worktree_path,
+ language.name(),
+ language.manifest(),
+ delegate.clone(),
+ cx,
+ )
+ .collect::<Vec<_>>();
- let abs_path = file.abs_path(cx);
- for node in nodes {
- if !reused {
- let server_id = node.server_id_or_init(
- |LaunchDisposition {
- server_name,
-
- path,
- settings,
- }|
- {
- let uri = Url::from_file_path(
- worktree.read(cx).abs_path().join(&path.path),
- );
- let key = (worktree_id, server_name.clone());
- local.language_server_ids.remove(&key);
-
- let adapter = local
- .languages
- .lsp_adapters(&language)
- .into_iter()
- .find(|adapter| &adapter.name() == server_name)
- .expect("To find LSP adapter");
- let server_id = local.start_language_server(
- &worktree,
- delegate.clone(),
- adapter,
- settings,
- cx,
- );
- if let Some(state) =
- local.language_servers.get(&server_id)
- {
- if let Ok(uri) = uri {
- state.add_workspace_folder(uri);
- };
- }
- server_id
- }
- );
+ for node in nodes {
+ let server_id = node.server_id_or_init(|disposition| {
+ let path = &disposition.path;
+ let uri = Url::from_file_path(worktree_root.join(&path.path));
+ let key = LanguageServerSeed {
+ worktree_id,
+ name: disposition.server_name.clone(),
+ settings: disposition.settings.clone(),
+ toolchain: local.toolchain_store.read(cx).active_toolchain(
+ path.worktree_id,
+ &path.path,
+ language.name(),
+ ),
+ };
+ local.language_server_ids.remove(&key);
- if let Some(language_server_id) = server_id {
- messages_to_report.push(LspStoreEvent::LanguageServerUpdate {
- language_server_id,
- name: node.name(),
- message:
- proto::update_language_server::Variant::RegisteredForBuffer(
- proto::RegisteredForBuffer {
- buffer_abs_path: abs_path.to_string_lossy().to_string(),
- buffer_id: buffer_id.to_proto(),
- },
- ),
- });
- }
+ let server_id = local.get_or_insert_language_server(
+ &worktree,
+ lsp_delegate.clone(),
+ disposition,
+ &language.name(),
+ cx,
+ );
+ if let Some(state) = local.language_servers.get(&server_id) {
+ if let Ok(uri) = uri {
+ state.add_workspace_folder(uri);
+ };
}
+ server_id
+ });
+
+ if let Some(language_server_id) = server_id {
+ messages_to_report.push(LspStoreEvent::LanguageServerUpdate {
+ language_server_id,
+ name: node.name(),
+ message:
+ proto::update_language_server::Variant::RegisteredForBuffer(
+ proto::RegisteredForBuffer {
+ buffer_abs_path: abs_path.to_string_lossy().to_string(),
+ buffer_id: buffer_id.to_proto(),
+ },
+ ),
+ });
}
}
+ } else {
+ continue;
}
- rebase.finish()
- });
- for message in messages_to_report {
- cx.emit(message);
- }
- for (id, _) in to_stop {
- self.stop_local_language_server(id, cx).detach();
}
+ rebase.finish()
+ };
+ for message in messages_to_report {
+ cx.emit(message);
+ }
+ local.lsp_tree = new_tree;
+ for (id, _) in to_stop {
+ self.stop_local_language_server(id, cx).detach();
}
}
@@ -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 {
}
}
}
+ marked_path.filter(|_| current_presence.eq(&LabelPresence::Present))
+ }
- roots
- .into_iter()
- .filter_map(|(k, (path, presence))| {
- let path = path?;
- presence.eq(&LabelPresence::Present).then(|| (k, path))
+ 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("")),
})
- .collect()
}
+
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 }
@@ -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()
+ }
}
@@ -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 language server root points for a given path and language; the language servers might already be initialized at a given path.
+ /// Get all initialized language server IDs for a given path.
pub(crate) fn get<'a>(
- &'a mut self,
+ &'a self,
path: ProjectPath,
- query: AdapterQuery<'_>,
- delegate: Arc<dyn ManifestDelegate>,
+ language_name: LanguageName,
+ manifest_name: Option<&ManifestName>,
+ delegate: &Arc<dyn ManifestDelegate>,
cx: &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| {
- (
- adapter.name(),
- (LspSettings::default(), BTreeSet::new(), adapter),
- )
- }))
- }
- };
- self.get_with_adapters(path, adapters, delegate, cx)
+ ) -> 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)
}
- fn get_with_adapters<'a>(
+ /// 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 walk<'a>(
&'a mut self,
path: ProjectPath,
- adapters: IndexMap<
- LanguageServerName,
- (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>),
- >,
- 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 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 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)
+ }
- 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()
- })
+ 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(),
+ root_path.clone(),
+ settings.clone(),
+ toolchain,
+ )),
+ Default::default(),
+ )
+ });
+ languages.insert(language_name.clone());
+ Arc::downgrade(&node).into()
+ })
}
- fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
- self.languages.adapter_for_name(name)
+ fn get_with_adapters<'a>(
+ &'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 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 {
@@ -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)
})
}
@@ -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>,
@@ -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,
)
})
@@ -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 {
@@ -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(),