lsp: Scope logs and status updates to their projects (#61221)
The language registry and LSP log store are shared by every workspace in a Zed process. The LSP Logs selector enumerated the shared log store without filtering by project, while each activity indicator consumed global binary status updates. This exposed servers from other windows as "Unknown worktree" entries and surfaced their startup failures or timeouts in unrelated status bars. Key each logged server by its owning project or LSP store together with its process-local language server ID. Use that identity consistently when adding, removing, streaming, selecting, opening, and reselecting log entries so local, remote, SSH, and supplementary servers cannot collide across ownership domains. Model Copilot and other servers outside the project LSP store as project-scoped supplementary servers, while matching SSH servers by their owning LSP store. Tag project-originated binary status updates with the owning LSP store entity ID. Carry that source through extension worktree delegates and every worktree-based WASM language-server call, then let each local LSP store forward only statuses for its project. Activity indicators rely solely on their project LSP store, and remote projects continue to receive host statuses through the existing protocol path instead of subscribing to local registry events. Own each binary-status subscription task in its local LSP store and unregister its broadcaster sender when the store is dropped, so closing windows cannot leave detached status tasks or stale senders behind. Add regression coverage for duplicate process-local server IDs across ownership domains, filtering remote, supplementary, and SSH servers from other projects, retaining servers owned by the current project or LSP store, preserving extension status sources, and unregistering dropped status subscriptions. Release Notes: - Fixed language servers from other projects appearing in the LSP Logs view and their statuses showing in unrelated windows' status bars
This commit is contained in:
1 parent
9decdcc1f2
commit
60bf47b9f2
21 files changed
+1266
-476
No files matched your search
@@ -7,8 +7,7 @@ use gpui::{
|
||||
SharedString, Styled, Task, Window, actions,
|
||||
};
|
||||
use language::{
|
||||
BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,
|
||||
LanguageServerStatusUpdate, ServerHealth,
|
||||
BinaryStatus, LanguageServerId, LanguageServerName, LanguageServerStatusUpdate, ServerHealth,
|
||||
};
|
||||
use project::{
|
||||
LanguageServerProgress, LspStoreEvent, ProgressToken, Project, ProjectEnvironmentEvent,
|
||||
@@ -91,28 +90,11 @@ struct Content {
|
||||
impl ActivityIndicator {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<ActivityIndicator> {
|
||||
let project = workspace.project().clone();
|
||||
let this = cx.new(|cx| {
|
||||
let mut status_events = languages.language_server_binary_statuses();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some((name, binary_status)) = status_events.next().await {
|
||||
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(ServerStatus {
|
||||
name,
|
||||
status: LanguageServerStatusUpdate::Binary(binary_status),
|
||||
});
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let mut job_events = fs.subscribe_to_jobs();
|
||||
cx.spawn(async move |this, cx| {
|
||||
|
||||
@@ -927,12 +927,12 @@ async fn test_ssh_restarting_language_server_replaces_remote_status(
|
||||
let matching_server_ids = log_store
|
||||
.language_servers
|
||||
.iter()
|
||||
.filter_map(|(server_id, state)| {
|
||||
.filter_map(|(key, state)| {
|
||||
state
|
||||
.name
|
||||
.as_ref()
|
||||
.is_some_and(|name| name.0 == "the-language-server")
|
||||
.then_some(*server_id)
|
||||
.then_some(key.server_id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(matching_server_ids, vec![first_server_id]);
|
||||
@@ -958,10 +958,6 @@ async fn test_ssh_restarting_language_server_replaces_remote_status(
|
||||
statuses[0].0, restarted_server_id,
|
||||
"restarting a remote language server should publish the replacement server id"
|
||||
);
|
||||
assert_ne!(
|
||||
statuses[0].0, first_server_id,
|
||||
"restarting a remote language server should remove the previous server id"
|
||||
);
|
||||
assert_eq!(statuses[0].1.name.0, "the-language-server");
|
||||
});
|
||||
cx_a.read_global::<GlobalLogStore, _>(|global, cx| {
|
||||
@@ -969,12 +965,12 @@ async fn test_ssh_restarting_language_server_replaces_remote_status(
|
||||
let matching_server_ids = log_store
|
||||
.language_servers
|
||||
.iter()
|
||||
.filter_map(|(server_id, state)| {
|
||||
.filter_map(|(key, state)| {
|
||||
state
|
||||
.name
|
||||
.as_ref()
|
||||
.is_some_and(|name| name.0 == "the-language-server")
|
||||
.then_some(*server_id)
|
||||
.then_some(key.server_id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
@@ -982,10 +978,6 @@ async fn test_ssh_restarting_language_server_replaces_remote_status(
|
||||
vec![restarted_server_id],
|
||||
"restarting a remote language server should replace the old log store entry"
|
||||
);
|
||||
assert!(
|
||||
!log_store.language_servers.contains_key(&first_server_id),
|
||||
"restarting a remote language server should remove the previous log store entry"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::sync::Arc;
|
||||
use ::lsp::LanguageServerName;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use async_trait::async_trait;
|
||||
use gpui::{App, Task};
|
||||
use gpui::{App, EntityId, Task};
|
||||
use language::LanguageName;
|
||||
use semver::Version;
|
||||
use task::{SpawnInTerminal, ZedDebugConfig};
|
||||
@@ -64,6 +64,7 @@ pub trait Extension: Send + Sync + 'static {
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Command>;
|
||||
|
||||
async fn language_server_initialization_options(
|
||||
@@ -71,24 +72,28 @@ pub trait Extension: Send + Sync + 'static {
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_workspace_configuration(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_initialization_options_schema(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_workspace_configuration_schema(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_additional_initialization_options(
|
||||
@@ -96,6 +101,7 @@ pub trait Extension: Send + Sync + 'static {
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_additional_workspace_configuration(
|
||||
@@ -103,6 +109,7 @@ pub trait Extension: Send + Sync + 'static {
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
language_server_status_source: EntityId,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn labels_for_completions(
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use fs::Fs;
|
||||
use gpui::{App, Global, ReadGlobal, SharedString, Task};
|
||||
use gpui::{App, EntityId, Global, ReadGlobal, SharedString, Task};
|
||||
use language::{BinaryStatus, LanguageLoader, LanguageMatcher, LanguageName};
|
||||
use lsp::LanguageServerName;
|
||||
use parking_lot::RwLock;
|
||||
@@ -301,6 +301,7 @@ pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
source: Option<EntityId>,
|
||||
language_server_id: LanguageServerName,
|
||||
status: BinaryStatus,
|
||||
);
|
||||
@@ -335,6 +336,7 @@ impl ExtensionLanguageServerProxy for ExtensionHostProxy {
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
source: Option<EntityId>,
|
||||
language_server_id: LanguageServerName,
|
||||
status: BinaryStatus,
|
||||
) {
|
||||
@@ -342,7 +344,7 @@ impl ExtensionLanguageServerProxy for ExtensionHostProxy {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.update_language_server_status(language_server_id, status)
|
||||
proxy.update_language_server_status(source, language_server_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use extension::{
|
||||
};
|
||||
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
|
||||
use futures::{AsyncReadExt, FutureExt, StreamExt, io::BufReader};
|
||||
use gpui::{AppContext as _, BackgroundExecutor, Entity, TaskExt, TestAppContext};
|
||||
use gpui::{AppContext as _, BackgroundExecutor, Entity, EntityId, TaskExt, TestAppContext};
|
||||
use http_client::{FakeHttpClient, Response};
|
||||
use language::{
|
||||
BinaryStatus, LanguageConfig, LanguageMatcher, LanguageName, LanguageRegistry, QueryFiles,
|
||||
@@ -826,6 +826,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
|
||||
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
|
||||
let lsp_store_id = project.read_with(cx, |project, _| project.lsp_store().entity_id());
|
||||
language_extension::init(
|
||||
LspAccess::ViaLspStore(
|
||||
project
|
||||
@@ -837,7 +838,8 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
);
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let mut status_updates = language_registry.language_server_binary_statuses();
|
||||
let (mut status_updates, _status_updates_subscription) =
|
||||
language_registry.language_server_binary_statuses();
|
||||
|
||||
struct FakeLanguageServerVersion {
|
||||
version: String,
|
||||
@@ -1093,18 +1095,25 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
],
|
||||
[
|
||||
(
|
||||
Some(lsp_store_id),
|
||||
LanguageServerName::new_static("gleam"),
|
||||
BinaryStatus::Starting
|
||||
),
|
||||
(
|
||||
Some(lsp_store_id),
|
||||
LanguageServerName::new_static("gleam"),
|
||||
BinaryStatus::CheckingForUpdate
|
||||
),
|
||||
(
|
||||
Some(lsp_store_id),
|
||||
LanguageServerName::new_static("gleam"),
|
||||
BinaryStatus::Downloading
|
||||
),
|
||||
(LanguageServerName::new_static("gleam"), BinaryStatus::None)
|
||||
(
|
||||
Some(lsp_store_id),
|
||||
LanguageServerName::new_static("gleam"),
|
||||
BinaryStatus::None
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
@@ -4107,6 +4116,7 @@ impl Extension for FakeExtension {
|
||||
_language_server_id: LanguageServerName,
|
||||
_language_name: LanguageName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Command> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4116,6 +4126,7 @@ impl Extension for FakeExtension {
|
||||
_language_server_id: LanguageServerName,
|
||||
_language_name: LanguageName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4124,6 +4135,7 @@ impl Extension for FakeExtension {
|
||||
&self,
|
||||
_language_server_id: LanguageServerName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4132,6 +4144,7 @@ impl Extension for FakeExtension {
|
||||
&self,
|
||||
_language_server_id: LanguageServerName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4140,6 +4153,7 @@ impl Extension for FakeExtension {
|
||||
&self,
|
||||
_language_server_id: LanguageServerName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4149,6 +4163,7 @@ impl Extension for FakeExtension {
|
||||
_language_server_id: LanguageServerName,
|
||||
_target_language_server_id: LanguageServerName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
@@ -4158,6 +4173,7 @@ impl Extension for FakeExtension {
|
||||
_language_server_id: LanguageServerName,
|
||||
_target_language_server_id: LanguageServerName,
|
||||
_worktree: Arc<dyn WorktreeDelegate>,
|
||||
_language_server_status_source: EntityId,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
anyhow::bail!("not supported by FakeExtension")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use futures::{
|
||||
},
|
||||
future::BoxFuture,
|
||||
};
|
||||
use gpui::{App, AsyncApp, BackgroundExecutor, Task};
|
||||
use gpui::{App, AsyncApp, BackgroundExecutor, EntityId, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::LanguageName;
|
||||
use lsp::LanguageServerName;
|
||||
@@ -90,8 +90,9 @@ impl extension::Extension for WasmExtension {
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Command> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
let command = extension
|
||||
@@ -116,8 +117,9 @@ impl extension::Extension for WasmExtension {
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
let options = extension
|
||||
@@ -140,8 +142,9 @@ impl extension::Extension for WasmExtension {
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
let options = extension
|
||||
@@ -163,8 +166,9 @@ impl extension::Extension for WasmExtension {
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
extension
|
||||
@@ -185,8 +189,9 @@ impl extension::Extension for WasmExtension {
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
extension
|
||||
@@ -208,8 +213,9 @@ impl extension::Extension for WasmExtension {
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
let options = extension
|
||||
@@ -233,8 +239,9 @@ impl extension::Extension for WasmExtension {
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
status_source: EntityId,
|
||||
) -> Result<Option<String>> {
|
||||
self.call(|extension, store| {
|
||||
self.call_with_language_server_status_source(status_source, move |extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table.push(worktree)?;
|
||||
let options = extension
|
||||
@@ -538,6 +545,7 @@ pub struct WasmState {
|
||||
ctx: WasiCtx,
|
||||
pub host: Arc<WasmHost>,
|
||||
pub(crate) capability_granter: CapabilityGranter,
|
||||
pub(crate) language_server_status_source: Option<gpui::EntityId>,
|
||||
}
|
||||
|
||||
type MainThreadCall = Box<dyn Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, ()>>;
|
||||
@@ -671,6 +679,7 @@ impl WasmHost {
|
||||
this.granted_capabilities.clone(),
|
||||
manifest.clone(),
|
||||
),
|
||||
language_server_status_source: None,
|
||||
},
|
||||
);
|
||||
// Store will yield after 1 tick, and get a new deadline of 1 tick after each yield.
|
||||
@@ -872,6 +881,34 @@ impl WasmExtension {
|
||||
.with_context(|| format!("loading wasm extension: {}", manifest.id))
|
||||
}
|
||||
|
||||
async fn call_with_language_server_status_source<T, Fn>(
|
||||
&self,
|
||||
source: EntityId,
|
||||
f: Fn,
|
||||
) -> Result<T>
|
||||
where
|
||||
T: 'static + Send,
|
||||
Fn: 'static
|
||||
+ Send
|
||||
+ for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, T>,
|
||||
{
|
||||
self.call(move |extension, store| {
|
||||
async move {
|
||||
debug_assert!(store.data().language_server_status_source.is_none());
|
||||
|
||||
// The installation-status WIT methods do not receive a worktree, so expose the
|
||||
// source through WasmState for the duration of this serialized extension call.
|
||||
// Clear it before returning so failed calls cannot affect the next invocation.
|
||||
store.data_mut().language_server_status_source = Some(source);
|
||||
let result = f(extension, store).await;
|
||||
store.data_mut().language_server_status_source = None;
|
||||
result
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn call<T, Fn>(&self, f: Fn) -> Result<T>
|
||||
where
|
||||
T: 'static + Send,
|
||||
|
||||
@@ -148,9 +148,11 @@ impl ExtensionImports for WasmState {
|
||||
LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(lsp::LanguageServerName(server_name.into()), status);
|
||||
self.host.proxy.update_language_server_status(
|
||||
self.language_server_status_source,
|
||||
lsp::LanguageServerName(server_name.into()),
|
||||
status,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -507,9 +507,11 @@ impl ExtensionImports for WasmState {
|
||||
LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
self.host.proxy.update_language_server_status(
|
||||
self.language_server_status_source,
|
||||
::lsp::LanguageServerName(server_name.into()),
|
||||
status,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1064,9 +1064,11 @@ impl ExtensionImports for WasmState {
|
||||
LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
self.host.proxy.update_language_server_status(
|
||||
self.language_server_status_source,
|
||||
::lsp::LanguageServerName(server_name.into()),
|
||||
status,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ use collections::{HashMap, HashSet};
|
||||
use futures::Future;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::lock::OwnedMutexGuard;
|
||||
use gpui::{App, AsyncApp, Entity};
|
||||
use gpui::{App, AsyncApp, Entity, EntityId};
|
||||
use http_client::HttpClient;
|
||||
|
||||
pub use language_core::{
|
||||
@@ -492,6 +492,7 @@ pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn resolve_relative_path(&self, path: PathBuf) -> PathBuf;
|
||||
fn status_source_id(&self) -> EntityId;
|
||||
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
|
||||
|
||||
@@ -19,7 +19,7 @@ use futures::{
|
||||
future::{BoxFuture, FutureExt as _},
|
||||
};
|
||||
use globset::GlobSet;
|
||||
use gpui::{App, BackgroundExecutor};
|
||||
use gpui::{App, BackgroundExecutor, EntityId, Subscription};
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
@@ -89,9 +89,17 @@ impl std::fmt::Display for LanguageNotFound {
|
||||
}
|
||||
}
|
||||
|
||||
type ServerStatus = (Option<EntityId>, LanguageServerName, BinaryStatus);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct ServerStatusSender {
|
||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, BinaryStatus)>>>>,
|
||||
state: Arc<Mutex<ServerStatusSenderState>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ServerStatusSenderState {
|
||||
next_subscription_id: usize,
|
||||
txs: HashMap<usize, mpsc::UnboundedSender<ServerStatus>>,
|
||||
}
|
||||
|
||||
pub struct LoadedLanguage {
|
||||
@@ -922,7 +930,17 @@ impl LanguageRegistry {
|
||||
}
|
||||
|
||||
pub fn update_lsp_binary_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
|
||||
self.lsp_binary_status_tx.send(server_name, status);
|
||||
self.lsp_binary_status_tx.send(None, server_name, status);
|
||||
}
|
||||
|
||||
pub fn update_lsp_binary_status_for_entity(
|
||||
&self,
|
||||
source: EntityId,
|
||||
server_name: LanguageServerName,
|
||||
status: BinaryStatus,
|
||||
) {
|
||||
self.lsp_binary_status_tx
|
||||
.send(Some(source), server_name, status);
|
||||
}
|
||||
|
||||
pub fn next_language_server_id(&self) -> LanguageServerId {
|
||||
@@ -968,7 +986,10 @@ impl LanguageRegistry {
|
||||
|
||||
pub fn language_server_binary_statuses(
|
||||
&self,
|
||||
) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
|
||||
) -> (
|
||||
mpsc::UnboundedReceiver<(Option<EntityId>, LanguageServerName, BinaryStatus)>,
|
||||
Subscription,
|
||||
) {
|
||||
self.lsp_binary_status_tx.subscribe()
|
||||
}
|
||||
}
|
||||
@@ -1065,14 +1086,41 @@ impl LanguageRegistryState {
|
||||
}
|
||||
|
||||
impl ServerStatusSender {
|
||||
fn subscribe(&self) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
|
||||
fn subscribe(&self) -> (mpsc::UnboundedReceiver<ServerStatus>, Subscription) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
self.txs.lock().push(tx);
|
||||
rx
|
||||
let subscription_id = {
|
||||
let mut state = self.state.lock();
|
||||
let subscription_id = post_inc(&mut state.next_subscription_id);
|
||||
state.txs.insert(subscription_id, tx);
|
||||
subscription_id
|
||||
};
|
||||
let state = self.state.clone();
|
||||
let subscription = Subscription::new(move || {
|
||||
state.lock().txs.remove(&subscription_id);
|
||||
});
|
||||
(rx, subscription)
|
||||
}
|
||||
|
||||
fn send(&self, name: LanguageServerName, status: BinaryStatus) {
|
||||
let mut txs = self.txs.lock();
|
||||
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
|
||||
fn send(&self, source: Option<EntityId>, name: LanguageServerName, status: BinaryStatus) {
|
||||
let mut state = self.state.lock();
|
||||
state.txs.retain(|_, tx| {
|
||||
tx.unbounded_send((source, name.clone(), status.clone()))
|
||||
.is_ok()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dropping_server_status_subscription_unregisters_sender() {
|
||||
let sender = ServerStatusSender::default();
|
||||
let (_receiver, subscription) = sender.subscribe();
|
||||
assert_eq!(sender.state.lock().txs.len(), 1);
|
||||
|
||||
drop(subscription);
|
||||
assert!(sender.state.lock().txs.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,7 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
source: Option<gpui::EntityId>,
|
||||
language_server_id: LanguageServerName,
|
||||
status: BinaryStatus,
|
||||
) {
|
||||
@@ -130,8 +131,16 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
|
||||
language_server_id,
|
||||
status
|
||||
);
|
||||
self.language_registry
|
||||
.update_lsp_binary_status(language_server_id, status);
|
||||
if let Some(source) = source {
|
||||
self.language_registry.update_lsp_binary_status_for_entity(
|
||||
source,
|
||||
language_server_id,
|
||||
status,
|
||||
);
|
||||
} else {
|
||||
self.language_registry
|
||||
.update_lsp_binary_status(language_server_id, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +176,7 @@ impl DynLspInstaller for ExtensionLspAdapter {
|
||||
) -> LanguageServerBinaryLocations {
|
||||
async move {
|
||||
let ret = maybe!(async move {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let command = self
|
||||
.extension
|
||||
@@ -174,6 +184,7 @@ impl DynLspInstaller for ExtensionLspAdapter {
|
||||
self.language_server_id.clone(),
|
||||
self.language_name.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -314,6 +325,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options = self
|
||||
.extension
|
||||
@@ -321,6 +333,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
self.language_server_id.clone(),
|
||||
self.language_name.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await?;
|
||||
Ok(if let Some(json_options) = json_options {
|
||||
@@ -339,10 +352,15 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
_: Option<Uri>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options: Option<String> = self
|
||||
.extension
|
||||
.language_server_workspace_configuration(self.language_server_id.clone(), delegate)
|
||||
.language_server_workspace_configuration(
|
||||
self.language_server_id.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await?;
|
||||
Ok(if let Some(json_options) = json_options {
|
||||
serde_json::from_str(&json_options).with_context(|| {
|
||||
@@ -359,12 +377,14 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
_cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Option<serde_json::Value> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_schema: Option<String> = self
|
||||
.extension
|
||||
.language_server_initialization_options_schema(
|
||||
self.language_server_id.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
@@ -378,12 +398,14 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
_cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Option<serde_json::Value> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_schema: Option<String> = self
|
||||
.extension
|
||||
.language_server_workspace_configuration_schema(
|
||||
self.language_server_id.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
@@ -396,6 +418,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
target_language_server_id: LanguageServerName,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options: Option<String> = self
|
||||
.extension
|
||||
@@ -403,6 +426,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
self.language_server_id.clone(),
|
||||
target_language_server_id.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await?;
|
||||
Ok(if let Some(json_options) = json_options {
|
||||
@@ -424,6 +448,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let language_server_status_source = delegate.status_source_id();
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options: Option<String> = self
|
||||
.extension
|
||||
@@ -431,6 +456,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
self.language_server_id.clone(),
|
||||
target_language_server_id.clone(),
|
||||
delegate,
|
||||
language_server_status_source,
|
||||
)
|
||||
.await?;
|
||||
Ok(if let Some(json_options) = json_options {
|
||||
|
||||
@@ -346,7 +346,15 @@ impl LanguageServerState {
|
||||
.lsp_store
|
||||
.update(cx, |lsp_store, _| lsp_store.as_remote().is_some())
|
||||
.unwrap_or(false);
|
||||
let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector);
|
||||
let has_logs = is_remote
|
||||
|| self.workspace.upgrade().is_some_and(|workspace| {
|
||||
let project = workspace.read(cx).project();
|
||||
lsp_logs.read(cx).has_server_logs(
|
||||
&server_selector,
|
||||
&project.downgrade(),
|
||||
&self.lsp_store,
|
||||
)
|
||||
});
|
||||
|
||||
let (status_color, status_label) = server_info
|
||||
.binary_status
|
||||
|
||||
@@ -13,7 +13,9 @@ use lsp::{
|
||||
};
|
||||
use project::{
|
||||
LanguageServerStatus, Project,
|
||||
lsp_store::log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message},
|
||||
lsp_store::log_store::{
|
||||
self, Event, LanguageServerKind, LanguageServerLogKey, LogKind, LogStore, Message,
|
||||
},
|
||||
search::SearchQuery,
|
||||
};
|
||||
use proto::toggle_lsp_logs::LogType;
|
||||
@@ -44,6 +46,9 @@ pub fn open(
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let weak_project = project.downgrade();
|
||||
let project_is_local = project.read(cx).is_local();
|
||||
let weak_lsp_store = project.read(cx).lsp_store().downgrade();
|
||||
let tool_log_store = log_store.clone();
|
||||
let log_view = get_or_create_tool(
|
||||
workspace,
|
||||
@@ -53,22 +58,35 @@ pub fn open(
|
||||
move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
|
||||
);
|
||||
log_view.update(cx, |log_view, cx| {
|
||||
let server_id = match server {
|
||||
LanguageServerSelector::Id(id) => Some(id),
|
||||
LanguageServerSelector::Name(name) => {
|
||||
log_store.read(cx).language_servers.iter().find_map(
|
||||
|(id, state)| {
|
||||
if state.name.as_ref() == Some(&name) {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
let key_priority = |key: &LanguageServerLogKey| match &key.kind {
|
||||
LanguageServerKind::Local { .. } if project_is_local => 0,
|
||||
LanguageServerKind::Remote { .. } if !project_is_local => 0,
|
||||
_ => 1,
|
||||
};
|
||||
if let Some(server_id) = server_id {
|
||||
log_view.show_logs_for_server(server_id, window, cx);
|
||||
let server_key = match server {
|
||||
LanguageServerSelector::Id(id) => log_store
|
||||
.read(cx)
|
||||
.language_servers
|
||||
.keys()
|
||||
.filter(|key| {
|
||||
key.server_id == id
|
||||
&& key.is_for_project(&weak_project, &weak_lsp_store)
|
||||
})
|
||||
.min_by_key(|key| key_priority(key))
|
||||
.cloned(),
|
||||
LanguageServerSelector::Name(name) => log_store
|
||||
.read(cx)
|
||||
.language_servers
|
||||
.iter()
|
||||
.filter(|(key, state)| {
|
||||
key.is_for_project(&weak_project, &weak_lsp_store)
|
||||
&& state.name.as_ref() == Some(&name)
|
||||
})
|
||||
.min_by_key(|(key, _)| key_priority(key))
|
||||
.map(|(key, _)| key.clone()),
|
||||
};
|
||||
if let Some(server_key) = server_key {
|
||||
log_view.show_logs_for_server(server_key, window, cx);
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -82,7 +100,7 @@ pub struct LspLogView {
|
||||
pub(crate) editor: Entity<Editor>,
|
||||
editor_subscriptions: Vec<Subscription>,
|
||||
log_store: Entity<LogStore>,
|
||||
current_server_id: Option<LanguageServerId>,
|
||||
current_server_key: Option<LanguageServerLogKey>,
|
||||
active_entry_kind: LogKind,
|
||||
project: Entity<Project>,
|
||||
focus_handle: FocusHandle,
|
||||
@@ -105,6 +123,12 @@ pub(crate) struct LogMenuItem {
|
||||
pub server_kind: LanguageServerKind,
|
||||
}
|
||||
|
||||
impl LogMenuItem {
|
||||
fn key(&self) -> LanguageServerLogKey {
|
||||
LanguageServerLogKey::new(self.server_kind.clone(), self.server_id)
|
||||
}
|
||||
}
|
||||
|
||||
actions!(
|
||||
dev,
|
||||
[
|
||||
@@ -144,35 +168,38 @@ impl LspLogView {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let server_id = log_store
|
||||
.read(cx)
|
||||
.language_servers
|
||||
.iter()
|
||||
.find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
|
||||
.map(|(id, _)| *id);
|
||||
|
||||
let weak_project = project.downgrade();
|
||||
let weak_lsp_store = project.read(cx).lsp_store().downgrade();
|
||||
let server_key = log_store
|
||||
.read(cx)
|
||||
.server_keys_for_project(&weak_project, &weak_lsp_store)
|
||||
.next();
|
||||
let model_changes_subscription =
|
||||
cx.observe_in(&log_store, window, move |this, store, window, cx| {
|
||||
let first_server_id_for_project =
|
||||
store.read(cx).server_ids_for_project(&weak_project).next();
|
||||
if let Some(current_lsp) = this.current_server_id {
|
||||
if !store.read(cx).language_servers.contains_key(¤t_lsp)
|
||||
&& let Some(server_id) = first_server_id_for_project
|
||||
let first_server_key_for_project = store
|
||||
.read(cx)
|
||||
.server_keys_for_project(&weak_project, &weak_lsp_store)
|
||||
.next();
|
||||
if let Some(current_key) = this.current_server_key.as_ref() {
|
||||
let current_server_is_for_project =
|
||||
store.read(cx).language_servers.contains_key(current_key)
|
||||
&& current_key.is_for_project(&weak_project, &weak_lsp_store);
|
||||
if !current_server_is_for_project
|
||||
&& let Some(server_key) = first_server_key_for_project
|
||||
{
|
||||
match this.active_entry_kind {
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_key, window, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_key, window, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_key, window, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_key, window, cx),
|
||||
}
|
||||
}
|
||||
} else if let Some(server_id) = first_server_id_for_project {
|
||||
} else if let Some(server_key) = first_server_key_for_project {
|
||||
match this.active_entry_kind {
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
|
||||
LogKind::Rpc => this.show_rpc_trace_for_server(server_key, window, cx),
|
||||
LogKind::Trace => this.show_trace_for_server(server_key, window, cx),
|
||||
LogKind::Logs => this.show_logs_for_server(server_key, window, cx),
|
||||
LogKind::ServerInfo => this.show_server_info(server_key, window, cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,8 +210,8 @@ impl LspLogView {
|
||||
&log_store,
|
||||
window,
|
||||
move |log_view, _, e, window, cx| match e {
|
||||
Event::NewServerLogEntry { id, kind, text } => {
|
||||
if log_view.current_server_id == Some(*id)
|
||||
Event::NewServerLogEntry { key, kind, text } => {
|
||||
if log_view.current_server_key.as_ref() == Some(key)
|
||||
&& LogKind::from_server_log_type(kind) == log_view.active_entry_kind
|
||||
{
|
||||
log_view.editor.update(cx, |editor, cx| {
|
||||
@@ -234,12 +261,15 @@ impl LspLogView {
|
||||
});
|
||||
|
||||
cx.on_release(|log_view, cx| {
|
||||
let project = log_view.project.downgrade();
|
||||
let lsp_store = log_view.project.read(cx).lsp_store().downgrade();
|
||||
log_view.log_store.update(cx, |log_store, cx| {
|
||||
for (server_id, state) in &log_store.language_servers {
|
||||
if let Some(log_kind) = state.toggled_log_kind {
|
||||
if let Some(log_type) = log_type(log_kind) {
|
||||
send_toggle_log_message(state, *server_id, false, log_type, cx);
|
||||
}
|
||||
for (key, state) in &log_store.language_servers {
|
||||
if key.is_for_project(&project, &lsp_store)
|
||||
&& let Some(log_kind) = state.toggled_log_kind
|
||||
&& let Some(log_type) = log_type(log_kind)
|
||||
{
|
||||
send_toggle_log_message(key, false, log_type, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -252,7 +282,7 @@ impl LspLogView {
|
||||
editor_subscriptions,
|
||||
project,
|
||||
log_store,
|
||||
current_server_id: None,
|
||||
current_server_key: None,
|
||||
active_entry_kind: LogKind::Logs,
|
||||
_log_store_subscriptions: vec![
|
||||
model_changes_subscription,
|
||||
@@ -260,8 +290,8 @@ impl LspLogView {
|
||||
focus_subscription,
|
||||
],
|
||||
};
|
||||
if let Some(server_id) = server_id {
|
||||
lsp_log_view.show_logs_for_server(server_id, window, cx);
|
||||
if let Some(server_key) = server_key {
|
||||
lsp_log_view.show_logs_for_server(server_key, window, cx);
|
||||
}
|
||||
lsp_log_view
|
||||
}
|
||||
@@ -342,55 +372,28 @@ impl LspLogView {
|
||||
);
|
||||
(editor, vec![editor_subscription, search_subscription])
|
||||
}
|
||||
pub(crate) fn try_ensure_copilot_for_project(&self, cx: &mut App) {
|
||||
self.log_store.update(cx, |this, cx| {
|
||||
let copilot = EditPredictionStore::try_global(cx)
|
||||
.and_then(|store| store.read(cx).copilot_for_project(&self.project))?;
|
||||
let server = copilot.read(cx).language_server()?.clone();
|
||||
let log_subscription = this.copilot_state_for_project(&self.project.downgrade());
|
||||
if let Some(subscription_slot @ None) = log_subscription {
|
||||
let weak_lsp_store = cx.weak_entity();
|
||||
let server_id = server.server_id();
|
||||
|
||||
let name = LanguageServerName::new_static("copilot");
|
||||
*subscription_slot =
|
||||
Some(server.on_notification::<lsp::notification::LogMessage, _>(
|
||||
move |params, cx| {
|
||||
weak_lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.add_language_server_log(
|
||||
server_id,
|
||||
MessageType::LOG,
|
||||
¶ms.message,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
));
|
||||
this.add_language_server(
|
||||
LanguageServerKind::Global,
|
||||
server.server_id(),
|
||||
Some(name),
|
||||
None,
|
||||
Some(server.clone()),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
Some(())
|
||||
pub(crate) fn sync_copilot_for_project(&self, cx: &mut App) {
|
||||
let server = EditPredictionStore::try_global(cx)
|
||||
.and_then(|store| store.read(cx).copilot_for_project(&self.project))
|
||||
.and_then(|copilot| copilot.read(cx).language_server().cloned());
|
||||
self.log_store.update(cx, |log_store, cx| {
|
||||
log_store.sync_copilot_for_project(&self.project.downgrade(), server, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn menu_items(&self, cx: &mut App) -> Option<Vec<LogMenuItem>> {
|
||||
self.try_ensure_copilot_for_project(cx);
|
||||
self.sync_copilot_for_project(cx);
|
||||
let log_store = self.log_store.read(cx);
|
||||
|
||||
let unknown_server = LanguageServerName::new_static("unknown server");
|
||||
let project = self.project.downgrade();
|
||||
let lsp_store = self.project.read(cx).lsp_store().downgrade();
|
||||
|
||||
let mut rows = log_store
|
||||
.language_servers
|
||||
.iter()
|
||||
.map(|(server_id, state)| match &state.kind {
|
||||
.filter(|(key, _)| key.is_for_project(&project, &lsp_store))
|
||||
.map(|(key, state)| match &key.kind {
|
||||
LanguageServerKind::Local { .. }
|
||||
| LanguageServerKind::Remote { .. }
|
||||
| LanguageServerKind::LocalSsh { .. } => {
|
||||
@@ -401,9 +404,9 @@ impl LspLogView {
|
||||
.unwrap_or_else(|| "Unknown worktree".to_string());
|
||||
|
||||
LogMenuItem {
|
||||
server_id: *server_id,
|
||||
server_id: key.server_id,
|
||||
server_name: state.name.clone().unwrap_or(unknown_server.clone()),
|
||||
server_kind: state.kind.clone(),
|
||||
server_kind: key.kind.clone(),
|
||||
worktree_root_name,
|
||||
rpc_trace_enabled: state.rpc_state.is_some(),
|
||||
selected_entry: self.active_entry_kind,
|
||||
@@ -411,42 +414,24 @@ impl LspLogView {
|
||||
}
|
||||
}
|
||||
|
||||
LanguageServerKind::Global => LogMenuItem {
|
||||
server_id: *server_id,
|
||||
LanguageServerKind::Supplementary { .. } => LogMenuItem {
|
||||
server_id: key.server_id,
|
||||
server_name: state.name.clone().unwrap_or(unknown_server.clone()),
|
||||
server_kind: state.kind.clone(),
|
||||
server_kind: key.kind.clone(),
|
||||
worktree_root_name: "supplementary".to_string(),
|
||||
rpc_trace_enabled: state.rpc_state.is_some(),
|
||||
selected_entry: self.active_entry_kind,
|
||||
trace_level: lsp::TraceValue::Off,
|
||||
},
|
||||
})
|
||||
.chain(
|
||||
self.project
|
||||
.read(cx)
|
||||
.supplementary_language_servers(cx)
|
||||
.filter_map(|(server_id, name)| {
|
||||
let state = log_store.language_servers.get(&server_id)?;
|
||||
Some(LogMenuItem {
|
||||
server_id,
|
||||
server_name: name,
|
||||
server_kind: state.kind.clone(),
|
||||
worktree_root_name: "supplementary".to_string(),
|
||||
rpc_trace_enabled: state.rpc_state.is_some(),
|
||||
selected_entry: self.active_entry_kind,
|
||||
trace_level: lsp::TraceValue::Off,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
rows.sort_unstable_by_key(|row| row.server_id);
|
||||
rows.dedup_by_key(|row| row.server_id);
|
||||
rows.sort_by_key(|row| row.server_id);
|
||||
Some(rows)
|
||||
}
|
||||
|
||||
fn show_logs_for_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -454,16 +439,16 @@ impl LspLogView {
|
||||
.log_store
|
||||
.read(cx)
|
||||
.language_servers
|
||||
.get(&server_id)
|
||||
.get(&key)
|
||||
.map(|v| v.log_level)
|
||||
.unwrap_or(MessageType::LOG);
|
||||
let log_contents = self
|
||||
.log_store
|
||||
.read(cx)
|
||||
.server_logs(server_id)
|
||||
.server_logs(&key)
|
||||
.map(|v| log_contents(v, typ));
|
||||
if let Some(log_contents) = log_contents {
|
||||
self.current_server_id = Some(server_id);
|
||||
self.current_server_key = Some(key.clone());
|
||||
self.active_entry_kind = LogKind::Logs;
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
|
||||
self.editor = editor;
|
||||
@@ -472,26 +457,26 @@ impl LspLogView {
|
||||
}
|
||||
self.editor.read(cx).focus_handle(cx).focus(window, cx);
|
||||
self.log_store.update(cx, |log_store, cx| {
|
||||
let state = log_store.get_language_server_state(server_id)?;
|
||||
let state = log_store.get_language_server_state(&key)?;
|
||||
state.toggled_log_kind = Some(LogKind::Logs);
|
||||
send_toggle_log_message(state, server_id, true, LogType::Log, cx);
|
||||
send_toggle_log_message(&key, true, LogType::Log, cx);
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
fn update_log_level(
|
||||
&self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
level: MessageType,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let log_contents = self.log_store.update(cx, |this, _| {
|
||||
if let Some(state) = this.get_language_server_state(server_id) {
|
||||
if let Some(state) = this.get_language_server_state(&key) {
|
||||
state.log_level = level;
|
||||
}
|
||||
|
||||
this.server_logs(server_id).map(|v| log_contents(v, level))
|
||||
this.server_logs(&key).map(|v| log_contents(v, level))
|
||||
});
|
||||
|
||||
if let Some(log_contents) = log_contents {
|
||||
@@ -507,31 +492,31 @@ impl LspLogView {
|
||||
|
||||
fn show_trace_for_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let trace_level = self
|
||||
.log_store
|
||||
.update(cx, |log_store, _| {
|
||||
Some(log_store.get_language_server_state(server_id)?.trace_level)
|
||||
Some(log_store.get_language_server_state(&key)?.trace_level)
|
||||
})
|
||||
.unwrap_or(TraceValue::Messages);
|
||||
let log_contents = self
|
||||
.log_store
|
||||
.read(cx)
|
||||
.server_trace(server_id)
|
||||
.server_trace(&key)
|
||||
.map(|v| log_contents(v, trace_level));
|
||||
if let Some(log_contents) = log_contents {
|
||||
self.current_server_id = Some(server_id);
|
||||
self.current_server_key = Some(key.clone());
|
||||
self.active_entry_kind = LogKind::Trace;
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
|
||||
self.editor = editor;
|
||||
self.editor_subscriptions = editor_subscriptions;
|
||||
self.log_store.update(cx, |log_store, cx| {
|
||||
let state = log_store.get_language_server_state(server_id)?;
|
||||
let state = log_store.get_language_server_state(&key)?;
|
||||
state.toggled_log_kind = Some(LogKind::Trace);
|
||||
send_toggle_log_message(state, server_id, true, LogType::Trace, cx);
|
||||
send_toggle_log_message(&key, true, LogType::Trace, cx);
|
||||
Some(())
|
||||
});
|
||||
cx.notify();
|
||||
@@ -541,18 +526,18 @@ impl LspLogView {
|
||||
|
||||
fn show_rpc_trace_for_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.toggle_rpc_trace_for_server(server_id, true, window, cx);
|
||||
self.toggle_rpc_trace_for_server(key.clone(), true, window, cx);
|
||||
let rpc_log = self.log_store.update(cx, |log_store, _| {
|
||||
log_store
|
||||
.enable_rpc_trace_for_language_server(server_id)
|
||||
.enable_rpc_trace_for_language_server(&key)
|
||||
.map(|state| log_contents(&state.rpc_messages, ()))
|
||||
});
|
||||
if let Some(rpc_log) = rpc_log {
|
||||
self.current_server_id = Some(server_id);
|
||||
self.current_server_key = Some(key);
|
||||
self.active_entry_kind = LogKind::Rpc;
|
||||
let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
|
||||
let language = self.project.read(cx).languages().language_for_name("JSON");
|
||||
@@ -585,43 +570,50 @@ impl LspLogView {
|
||||
|
||||
fn toggle_rpc_trace_for_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
enabled: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.log_store.update(cx, |log_store, cx| {
|
||||
if enabled {
|
||||
log_store.enable_rpc_trace_for_language_server(server_id);
|
||||
log_store.enable_rpc_trace_for_language_server(&key);
|
||||
} else {
|
||||
log_store.disable_rpc_trace_for_language_server(server_id);
|
||||
log_store.disable_rpc_trace_for_language_server(&key);
|
||||
}
|
||||
|
||||
if let Some(server_state) = log_store.language_servers.get(&server_id) {
|
||||
send_toggle_log_message(server_state, server_id, enabled, LogType::Rpc, cx);
|
||||
};
|
||||
if log_store.language_servers.contains_key(&key) {
|
||||
send_toggle_log_message(&key, enabled, LogType::Rpc, cx);
|
||||
}
|
||||
});
|
||||
if !enabled && Some(server_id) == self.current_server_id {
|
||||
self.show_logs_for_server(server_id, window, cx);
|
||||
if !enabled && self.current_server_key.as_ref() == Some(&key) {
|
||||
self.show_logs_for_server(key, window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_trace_level(
|
||||
&self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
level: TraceValue,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(server) = self
|
||||
.project
|
||||
let server = self
|
||||
.log_store
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.read(cx)
|
||||
.language_server_for_id(server_id)
|
||||
{
|
||||
.language_servers
|
||||
.get(&key)
|
||||
.and_then(|state| state.server())
|
||||
.or_else(|| {
|
||||
self.project
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.read(cx)
|
||||
.language_server_for_id(key.server_id)
|
||||
});
|
||||
if let Some(server) = server {
|
||||
self.log_store.update(cx, |this, _| {
|
||||
if let Some(state) = this.get_language_server_state(server_id) {
|
||||
if let Some(state) = this.get_language_server_state(&key) {
|
||||
state.trace_level = level;
|
||||
}
|
||||
});
|
||||
@@ -634,35 +626,46 @@ impl LspLogView {
|
||||
|
||||
fn show_server_info(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(server_info) = self
|
||||
.project
|
||||
let server_id = key.server_id;
|
||||
let server_info = self
|
||||
.log_store
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.update(cx, |lsp_store, _| {
|
||||
lsp_store
|
||||
.language_server_for_id(server_id)
|
||||
.as_ref()
|
||||
.map(|language_server| ServerInfo::new(language_server))
|
||||
.or_else(move || {
|
||||
let capabilities =
|
||||
lsp_store.lsp_server_capabilities.get(&server_id)?.clone();
|
||||
let status = lsp_store.language_server_statuses.get(&server_id)?.clone();
|
||||
.language_servers
|
||||
.get(&key)
|
||||
.and_then(|state| state.server())
|
||||
.as_deref()
|
||||
.map(ServerInfo::new);
|
||||
let server_info = server_info.or_else(|| {
|
||||
self.project
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.update(cx, |lsp_store, _| {
|
||||
lsp_store
|
||||
.language_server_for_id(server_id)
|
||||
.as_ref()
|
||||
.map(|language_server| ServerInfo::new(language_server))
|
||||
.or_else(move || {
|
||||
let capabilities =
|
||||
lsp_store.lsp_server_capabilities.get(&server_id)?.clone();
|
||||
let status =
|
||||
lsp_store.language_server_statuses.get(&server_id)?.clone();
|
||||
|
||||
Some(ServerInfo {
|
||||
id: server_id,
|
||||
capabilities,
|
||||
status,
|
||||
Some(ServerInfo {
|
||||
id: server_id,
|
||||
capabilities,
|
||||
status,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
else {
|
||||
})
|
||||
});
|
||||
let Some(server_info) = server_info else {
|
||||
return;
|
||||
};
|
||||
self.current_server_id = Some(server_id);
|
||||
self.current_server_key = Some(key.clone());
|
||||
self.active_entry_kind = LogKind::ServerInfo;
|
||||
let (editor, editor_subscriptions) = Self::editor_for_server_info(server_info, window, cx);
|
||||
self.editor = editor;
|
||||
@@ -670,12 +673,12 @@ impl LspLogView {
|
||||
cx.notify();
|
||||
self.editor.read(cx).focus_handle(cx).focus(window, cx);
|
||||
self.log_store.update(cx, |log_store, cx| {
|
||||
let state = log_store.get_language_server_state(server_id)?;
|
||||
if let Some(log_kind) = state.toggled_log_kind.take() {
|
||||
if let Some(log_type) = log_type(log_kind) {
|
||||
send_toggle_log_message(state, server_id, false, log_type, cx);
|
||||
}
|
||||
};
|
||||
let state = log_store.get_language_server_state(&key)?;
|
||||
if let Some(log_kind) = state.toggled_log_kind.take()
|
||||
&& let Some(log_type) = log_type(log_kind)
|
||||
{
|
||||
send_toggle_log_message(&key, false, log_type, cx);
|
||||
}
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
@@ -691,13 +694,12 @@ fn log_type(log_kind: LogKind) -> Option<LogType> {
|
||||
}
|
||||
|
||||
fn send_toggle_log_message(
|
||||
server_state: &log_store::LanguageServerState,
|
||||
server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
enabled: bool,
|
||||
log_type: LogType,
|
||||
cx: &mut App,
|
||||
) {
|
||||
if let LanguageServerKind::Remote { project } = &server_state.kind {
|
||||
if let LanguageServerKind::Remote { project } = &key.kind {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
if let Some((client, project_id)) = project.lsp_store().read(cx).upstream_client() {
|
||||
@@ -705,7 +707,7 @@ fn send_toggle_log_message(
|
||||
.send(proto::ToggleLspLogs {
|
||||
project_id,
|
||||
log_type: log_type as i32,
|
||||
server_id: server_id.to_proto(),
|
||||
server_id: key.server_id.to_proto(),
|
||||
enabled,
|
||||
})
|
||||
.log_err();
|
||||
@@ -790,12 +792,12 @@ impl Item for LspLogView {
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
|
||||
if let Some(server_id) = self.current_server_id {
|
||||
if let Some(server_key) = self.current_server_key.clone() {
|
||||
match self.active_entry_kind {
|
||||
LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, window, cx),
|
||||
LogKind::Trace => new_view.show_trace_for_server(server_id, window, cx),
|
||||
LogKind::Logs => new_view.show_logs_for_server(server_id, window, cx),
|
||||
LogKind::ServerInfo => new_view.show_server_info(server_id, window, cx),
|
||||
LogKind::Rpc => new_view.show_rpc_trace_for_server(server_key, window, cx),
|
||||
LogKind::Trace => new_view.show_trace_for_server(server_key, window, cx),
|
||||
LogKind::Logs => new_view.show_logs_for_server(server_key, window, cx),
|
||||
LogKind::ServerInfo => new_view.show_server_info(server_key, window, cx),
|
||||
}
|
||||
}
|
||||
new_view
|
||||
@@ -934,25 +936,24 @@ impl Render for LspLogToolbarItemView {
|
||||
return div();
|
||||
};
|
||||
|
||||
let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
|
||||
let (menu_rows, current_server_key) = log_view.update(cx, |log_view, cx| {
|
||||
let menu_rows = log_view.menu_items(cx).unwrap_or_default();
|
||||
let current_server_id = log_view.current_server_id;
|
||||
(menu_rows, current_server_id)
|
||||
let current_server_key = log_view.current_server_key.clone();
|
||||
(menu_rows, current_server_key)
|
||||
});
|
||||
|
||||
let current_server = current_server_id.and_then(|current_server_id| {
|
||||
if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
|
||||
Some(menu_rows[ix].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let current_server = current_server_key.and_then(|current_server_key| {
|
||||
menu_rows
|
||||
.iter()
|
||||
.find(|row| row.key() == current_server_key)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
let available_language_servers: Vec<_> = menu_rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
(
|
||||
row.server_id,
|
||||
row.key(),
|
||||
row.server_name,
|
||||
row.worktree_root_name,
|
||||
row.selected_entry,
|
||||
@@ -988,33 +989,44 @@ impl Render for LspLogToolbarItemView {
|
||||
move |window, cx| {
|
||||
let log_view = log_view.clone();
|
||||
ContextMenu::build(window, cx, |mut menu, window, _| {
|
||||
for (server_id, name, worktree_root, active_entry_kind) in
|
||||
for (server_key, name, worktree_root, active_entry_kind) in
|
||||
available_language_servers.iter()
|
||||
{
|
||||
let label = format!("{name} ({worktree_root})");
|
||||
let server_id = *server_id;
|
||||
let server_key = server_key.clone();
|
||||
let active_entry_kind = *active_entry_kind;
|
||||
menu = menu.entry(
|
||||
label,
|
||||
None,
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.current_server_id = Some(server_id);
|
||||
view.current_server_key = Some(server_key.clone());
|
||||
view.active_entry_kind = active_entry_kind;
|
||||
match view.active_entry_kind {
|
||||
LogKind::Rpc => {
|
||||
view.toggle_rpc_trace_for_server(
|
||||
server_id, true, window, cx,
|
||||
server_key.clone(),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
view.show_rpc_trace_for_server(
|
||||
server_key.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
view.show_rpc_trace_for_server(server_id, window, cx);
|
||||
}
|
||||
LogKind::Trace => {
|
||||
view.show_trace_for_server(server_id, window, cx)
|
||||
}
|
||||
LogKind::Logs => {
|
||||
view.show_logs_for_server(server_id, window, cx)
|
||||
}
|
||||
LogKind::Trace => view.show_trace_for_server(
|
||||
server_key.clone(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
LogKind::Logs => view.show_logs_for_server(
|
||||
server_key.clone(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
LogKind::ServerInfo => {
|
||||
view.show_server_info(server_id, window, cx)
|
||||
view.show_server_info(server_key.clone(), window, cx)
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
@@ -1028,7 +1040,7 @@ impl Render for LspLogToolbarItemView {
|
||||
});
|
||||
|
||||
let view_selector = current_server.map(|server| {
|
||||
let server_id = server.server_id;
|
||||
let server_key = server.key();
|
||||
let rpc_trace_enabled = server.rpc_trace_enabled;
|
||||
let log_view = log_view.clone();
|
||||
let label = match server.selected_entry {
|
||||
@@ -1049,25 +1061,34 @@ impl Render for LspLogToolbarItemView {
|
||||
.menu(move |window, cx| {
|
||||
let log_toolbar_view = log_toolbar_view.upgrade()?;
|
||||
let log_view = log_view.clone();
|
||||
let server_key = server_key.clone();
|
||||
Some(ContextMenu::build(window, cx, move |this, window, _| {
|
||||
this.entry(
|
||||
SERVER_LOGS,
|
||||
None,
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_logs_for_server(server_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let server_key = server_key.clone();
|
||||
move |view, window, cx| {
|
||||
view.show_logs_for_server(server_key.clone(), window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.entry(
|
||||
SERVER_TRACE,
|
||||
None,
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_trace_for_server(server_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let server_key = server_key.clone();
|
||||
move |view, window, cx| {
|
||||
view.show_trace_for_server(server_key.clone(), window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.custom_entry(
|
||||
{
|
||||
let log_toolbar_view = log_toolbar_view.clone();
|
||||
let server_key = server_key.clone();
|
||||
move |window, _| {
|
||||
let server_key = server_key.clone();
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
@@ -1090,7 +1111,10 @@ impl Render for LspLogToolbarItemView {
|
||||
ToggleState::Selected
|
||||
);
|
||||
view.toggle_rpc_logging_for_server(
|
||||
server_id, enabled, window, cx,
|
||||
server_key.clone(),
|
||||
enabled,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cx.stop_propagation();
|
||||
},
|
||||
@@ -1100,15 +1124,21 @@ impl Render for LspLogToolbarItemView {
|
||||
.into_any_element()
|
||||
}
|
||||
},
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_rpc_trace_for_server(server_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let server_key = server_key.clone();
|
||||
move |view, window, cx| {
|
||||
view.show_rpc_trace_for_server(server_key.clone(), window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.entry(
|
||||
SERVER_INFO,
|
||||
None,
|
||||
window.handler_for(&log_view, move |view, window, cx| {
|
||||
view.show_server_info(server_id, window, cx);
|
||||
window.handler_for(&log_view, {
|
||||
let server_key = server_key.clone();
|
||||
move |view, window, cx| {
|
||||
view.show_server_info(server_key.clone(), window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
}))
|
||||
@@ -1146,14 +1176,17 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view;
|
||||
|
||||
move |window, cx| {
|
||||
let id = log_view.read(cx).current_server_id?;
|
||||
let key =
|
||||
log_view.read(cx).current_server_key.clone()?;
|
||||
|
||||
let trace_level =
|
||||
log_view.update(cx, |this, cx| {
|
||||
this.log_store.update(cx, |this, _| {
|
||||
Some(
|
||||
this.get_language_server_state(id)?
|
||||
.trace_level,
|
||||
this.get_language_server_state(
|
||||
&key,
|
||||
)?
|
||||
.trace_level,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
@@ -1173,11 +1206,12 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view.clone();
|
||||
move |_, cx| {
|
||||
log_view.update(cx, |this, cx| {
|
||||
if let Some(id) =
|
||||
this.current_server_id
|
||||
if let Some(key) = this
|
||||
.current_server_key
|
||||
.clone()
|
||||
{
|
||||
this.update_trace_level(
|
||||
id, option, cx,
|
||||
key, option, cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1216,14 +1250,17 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view;
|
||||
|
||||
move |window, cx| {
|
||||
let id = log_view.read(cx).current_server_id?;
|
||||
let key =
|
||||
log_view.read(cx).current_server_key.clone()?;
|
||||
|
||||
let log_level =
|
||||
log_view.update(cx, |this, cx| {
|
||||
this.log_store.update(cx, |this, _| {
|
||||
Some(
|
||||
this.get_language_server_state(id)?
|
||||
.log_level,
|
||||
this.get_language_server_state(
|
||||
&key,
|
||||
)?
|
||||
.log_level,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
@@ -1244,11 +1281,12 @@ impl Render for LspLogToolbarItemView {
|
||||
let log_view = log_view.clone();
|
||||
move |window, cx| {
|
||||
log_view.update(cx, |this, cx| {
|
||||
if let Some(id) =
|
||||
this.current_server_id
|
||||
if let Some(key) = this
|
||||
.current_server_key
|
||||
.clone()
|
||||
{
|
||||
this.update_log_level(
|
||||
id, option, window, cx,
|
||||
key, option, window, cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1328,19 +1366,19 @@ impl LspLogToolbarItemView {
|
||||
|
||||
fn toggle_rpc_logging_for_server(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
enabled: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(log_view) = &self.log_view {
|
||||
log_view.update(cx, |log_view, cx| {
|
||||
log_view.toggle_rpc_trace_for_server(id, enabled, window, cx);
|
||||
if !enabled && Some(id) == log_view.current_server_id {
|
||||
log_view.show_logs_for_server(id, window, cx);
|
||||
log_view.toggle_rpc_trace_for_server(key.clone(), enabled, window, cx);
|
||||
if !enabled && log_view.current_server_key.as_ref() == Some(&key) {
|
||||
log_view.show_logs_for_server(key.clone(), window, cx);
|
||||
cx.notify();
|
||||
} else if enabled {
|
||||
log_view.show_rpc_trace_for_server(id, window, cx);
|
||||
log_view.show_rpc_trace_for_server(key.clone(), window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
window.focus(&log_view.focus_handle, cx);
|
||||
|
||||
@@ -5,16 +5,323 @@ use crate::lsp_log_view::LogMenuItem;
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext as _, TestAppContext, VisualTestContext};
|
||||
use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
|
||||
use language::{
|
||||
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerId, tree_sitter_rust,
|
||||
};
|
||||
use lsp::LanguageServerName;
|
||||
use project::{
|
||||
FakeFs, Project,
|
||||
lsp_store::log_store::{LanguageServerKind, LogKind, LogStore},
|
||||
lsp_store::log_store::{LanguageServerKind, LanguageServerLogKey, LogKind, LogStore},
|
||||
};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_lsp_log_view_filters_servers_from_other_projects(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(path!("/current-project"), json!({ "test.rs": "" }))
|
||||
.await;
|
||||
fs.insert_tree(path!("/other-project"), json!({ "test.rs": "" }))
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/current-project").as_ref()], cx).await;
|
||||
let other_project = Project::test(fs.clone(), [path!("/other-project").as_ref()], cx).await;
|
||||
let worktree_id = project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let current_lsp_store = project
|
||||
.read_with(cx, |project, _| project.lsp_store())
|
||||
.downgrade();
|
||||
let other_lsp_store = other_project
|
||||
.read_with(cx, |project, _| project.lsp_store())
|
||||
.downgrade();
|
||||
|
||||
let current_ssh_server_id = LanguageServerId(99);
|
||||
let current_server_id = LanguageServerId(100);
|
||||
let log_store = cx.new(|cx| LogStore::new(false, cx));
|
||||
log_store.update(cx, |store, cx| {
|
||||
store.add_language_server(
|
||||
LanguageServerKind::LocalSsh {
|
||||
lsp_store: current_lsp_store,
|
||||
},
|
||||
current_ssh_server_id,
|
||||
Some(LanguageServerName::new_static("current-ssh-server")),
|
||||
Some(worktree_id),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
store.add_language_server(
|
||||
LanguageServerKind::Local {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
current_server_id,
|
||||
Some(LanguageServerName::new_static("current-server")),
|
||||
Some(worktree_id),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
store.add_language_server(
|
||||
LanguageServerKind::Remote {
|
||||
project: other_project.downgrade(),
|
||||
},
|
||||
current_server_id,
|
||||
Some(LanguageServerName::new_static("other-remote-server")),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
store.add_language_server(
|
||||
LanguageServerKind::Supplementary {
|
||||
project: other_project.downgrade(),
|
||||
},
|
||||
current_server_id,
|
||||
Some(LanguageServerName::new_static("other-supplementary-server")),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
store.add_language_server(
|
||||
LanguageServerKind::LocalSsh {
|
||||
lsp_store: other_lsp_store,
|
||||
},
|
||||
current_ssh_server_id,
|
||||
Some(LanguageServerName::new_static("other-ssh-server")),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
log_store.read_with(cx, |store, _| store.language_servers.len()),
|
||||
5
|
||||
);
|
||||
|
||||
let window =
|
||||
cx.add_window(|window, cx| LspLogView::new(project.clone(), log_store, window, cx));
|
||||
let log_view = window.root(cx).unwrap();
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
log_view.update(&mut cx, |view, cx| {
|
||||
let visible_servers = view
|
||||
.menu_items(cx)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|item| (item.server_id, item.server_name))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
visible_servers,
|
||||
[
|
||||
(
|
||||
current_ssh_server_id,
|
||||
LanguageServerName::new_static("current-ssh-server"),
|
||||
),
|
||||
(
|
||||
current_server_id,
|
||||
LanguageServerName::new_static("current-server"),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_lsp_log_view_labels_registered_supplementary_servers(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(path!("/the-root"), json!({ "test.rs": "" }))
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/the-root").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let log_store = cx.new(|cx| LogStore::new(false, cx));
|
||||
log_store.update(cx, |store, cx| store.add_project(&project, cx));
|
||||
|
||||
let server_id = LanguageServerId(100);
|
||||
let (server, _fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
lsp::LanguageServerBinary {
|
||||
path: "path/to/prettier".into(),
|
||||
arguments: Vec::new(),
|
||||
env: None,
|
||||
},
|
||||
"prettier".to_string(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.register_supplementary_language_server_for_test(
|
||||
server_id,
|
||||
LanguageServerName::new_static("prettier (default)"),
|
||||
Arc::new(server),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let window =
|
||||
cx.add_window(|window, cx| LspLogView::new(project.clone(), log_store, window, cx));
|
||||
let log_view = window.root(cx).unwrap();
|
||||
let mut cx = VisualTestContext::from_window(*window, cx);
|
||||
|
||||
log_view.update(&mut cx, |view, cx| {
|
||||
assert_eq!(
|
||||
view.menu_items(cx).unwrap(),
|
||||
&[LogMenuItem {
|
||||
server_id,
|
||||
server_name: LanguageServerName::new_static("prettier (default)"),
|
||||
worktree_root_name: "supplementary".to_string(),
|
||||
rpc_trace_enabled: false,
|
||||
selected_entry: LogKind::Logs,
|
||||
trace_level: lsp::TraceValue::Off,
|
||||
server_kind: LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
}]
|
||||
);
|
||||
});
|
||||
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.unregister_supplementary_language_server_for_test(server_id, cx);
|
||||
});
|
||||
log_view.update(&mut cx, |view, cx| {
|
||||
assert!(view.menu_items(cx).unwrap().is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_log_store_does_not_retain_language_servers(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(path!("/the-root"), json!({ "test.rs": "" }))
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/the-root").as_ref()], cx).await;
|
||||
let server_id = LanguageServerId(100);
|
||||
let server_kind = LanguageServerKind::Local {
|
||||
project: project.downgrade(),
|
||||
};
|
||||
let server_key = LanguageServerLogKey::new(server_kind.clone(), server_id);
|
||||
let log_store = cx.new(|cx| LogStore::new(false, cx));
|
||||
|
||||
let (first_server, _first_fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
lsp::LanguageServerBinary {
|
||||
path: "path/to/first-language-server".into(),
|
||||
arguments: Vec::new(),
|
||||
env: None,
|
||||
},
|
||||
"first-language-server".to_string(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
let first_server = Arc::new(first_server);
|
||||
let first_server_weak = Arc::downgrade(&first_server);
|
||||
log_store.update(cx, |store, cx| {
|
||||
store.add_language_server(
|
||||
server_kind.clone(),
|
||||
server_id,
|
||||
Some(LanguageServerName::new_static("first-language-server")),
|
||||
None,
|
||||
Some(first_server.clone()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let (replacement_server, _replacement_fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
lsp::LanguageServerBinary {
|
||||
path: "path/to/replacement-language-server".into(),
|
||||
arguments: Vec::new(),
|
||||
env: None,
|
||||
},
|
||||
"replacement-language-server".to_string(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
let replacement_server = Arc::new(replacement_server);
|
||||
let replacement_server_weak = Arc::downgrade(&replacement_server);
|
||||
log_store.update(cx, |store, cx| {
|
||||
store.add_language_server(
|
||||
server_kind,
|
||||
server_id,
|
||||
Some(LanguageServerName::new_static(
|
||||
"replacement-language-server",
|
||||
)),
|
||||
None,
|
||||
Some(replacement_server.clone()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let stored_server = log_store
|
||||
.read_with(cx, |store, _| {
|
||||
store
|
||||
.language_servers
|
||||
.get(&server_key)
|
||||
.and_then(|state| state.server())
|
||||
})
|
||||
.expect("replacement language server should be available");
|
||||
assert!(Arc::ptr_eq(&stored_server, &replacement_server));
|
||||
drop(stored_server);
|
||||
|
||||
drop(first_server);
|
||||
assert!(first_server_weak.upgrade().is_none());
|
||||
drop(replacement_server);
|
||||
assert!(replacement_server_weak.upgrade().is_none());
|
||||
assert!(
|
||||
log_store
|
||||
.read_with(cx, |store, _| store
|
||||
.language_servers
|
||||
.get(&server_key)
|
||||
.and_then(|state| state.server()))
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_log_store_removes_unavailable_copilot_server(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(path!("/the-root"), json!({ "test.rs": "" }))
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/the-root").as_ref()], cx).await;
|
||||
let server_id = LanguageServerId(100);
|
||||
let server_kind = LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
};
|
||||
let server_key = LanguageServerLogKey::new(server_kind, server_id);
|
||||
let log_store = cx.new(|cx| LogStore::new(false, cx));
|
||||
log_store.update(cx, |log_store, cx| log_store.add_project(&project, cx));
|
||||
|
||||
let (server, _fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
lsp::LanguageServerBinary {
|
||||
path: "path/to/copilot-language-server".into(),
|
||||
arguments: Vec::new(),
|
||||
env: None,
|
||||
},
|
||||
"copilot".to_string(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.sync_copilot_for_project(&project.downgrade(), Some(Arc::new(server)), cx);
|
||||
});
|
||||
assert!(log_store.read_with(cx, |log_store, _| {
|
||||
log_store.language_servers.contains_key(&server_key)
|
||||
}));
|
||||
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.sync_copilot_for_project(&project.downgrade(), None, cx);
|
||||
});
|
||||
assert!(!log_store.read_with(cx, |log_store, _| {
|
||||
log_store.language_servers.contains_key(&server_key)
|
||||
}));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_lsp_log_view(cx: &mut TestAppContext) {
|
||||
zlog::init_test();
|
||||
|
||||
@@ -333,6 +333,7 @@ pub struct LocalLspStore {
|
||||
>,
|
||||
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
|
||||
_subscription: gpui::Subscription,
|
||||
_binary_status_task: Task<()>,
|
||||
lsp_tree: LanguageServerTree,
|
||||
registered_buffers: HashMap<BufferId, usize>,
|
||||
buffers_opened_in_servers: HashMap<BufferId, HashSet<LanguageServerId>>,
|
||||
@@ -415,6 +416,14 @@ impl LocalLspStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_binary_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
|
||||
self.languages.update_lsp_binary_status_for_entity(
|
||||
self.weak.entity_id(),
|
||||
server_name,
|
||||
status,
|
||||
);
|
||||
}
|
||||
|
||||
fn start_language_server(
|
||||
&mut self,
|
||||
worktree_handle: &Entity<Worktree>,
|
||||
@@ -678,8 +687,7 @@ impl LocalLspStore {
|
||||
};
|
||||
|
||||
if update_binary_status {
|
||||
self.languages
|
||||
.update_lsp_binary_status(adapter.name(), BinaryStatus::Starting);
|
||||
self.update_binary_status(adapter.name(), BinaryStatus::Starting);
|
||||
}
|
||||
|
||||
self.language_servers.insert(server_id, state);
|
||||
@@ -707,7 +715,6 @@ impl LocalLspStore {
|
||||
&& let Some(path) = settings.path.as_ref().map(PathBuf::from)
|
||||
{
|
||||
let settings = settings.clone();
|
||||
let languages = self.languages.clone();
|
||||
return cx.background_spawn(async move {
|
||||
if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
|
||||
let already_trusted = *wait_until_worktree_trust.borrow();
|
||||
@@ -726,8 +733,7 @@ impl LocalLspStore {
|
||||
adapter.name(),
|
||||
);
|
||||
}
|
||||
languages
|
||||
.update_lsp_binary_status(adapter.name(), BinaryStatus::Starting);
|
||||
delegate.update_status(adapter.name(), BinaryStatus::Starting);
|
||||
}
|
||||
let mut env = delegate.shell_env().await;
|
||||
env.extend(settings.env.unwrap_or_default());
|
||||
@@ -748,7 +754,6 @@ impl LocalLspStore {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if !adapter.adapter.is_extension() && self.languages.has_fake_lsp_server(&adapter.name) {
|
||||
let language_server_name = adapter.name.clone();
|
||||
let languages = self.languages.clone();
|
||||
return cx.spawn(async move |_| {
|
||||
if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
|
||||
let already_trusted = *wait_until_worktree_trust.borrow();
|
||||
@@ -765,7 +770,7 @@ impl LocalLspStore {
|
||||
"Worktree {worktree_abs_path:?} is trusted, starting language server {language_server_name}",
|
||||
);
|
||||
}
|
||||
languages.update_lsp_binary_status(
|
||||
delegate.update_status(
|
||||
language_server_name.clone(),
|
||||
BinaryStatus::Starting,
|
||||
);
|
||||
@@ -4457,7 +4462,9 @@ impl BufferLspData {
|
||||
#[derive(Debug)]
|
||||
pub enum LspStoreEvent {
|
||||
LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
|
||||
SupplementaryLanguageServerAdded(LanguageServerId, LanguageServerName),
|
||||
LanguageServerRemoved(LanguageServerId),
|
||||
SupplementaryLanguageServerRemoved(LanguageServerId),
|
||||
LanguageServerUpdate {
|
||||
language_server_id: LanguageServerId,
|
||||
name: Option<LanguageServerName>,
|
||||
@@ -4680,7 +4687,7 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
subscribe_to_binary_statuses(&languages, cx).detach();
|
||||
let binary_status_task = subscribe_to_binary_statuses(&languages, cx);
|
||||
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
@@ -4717,6 +4724,7 @@ impl LspStore {
|
||||
.unwrap()
|
||||
.shutdown_language_servers_on_quit()
|
||||
}),
|
||||
_binary_status_task: binary_status_task,
|
||||
lsp_tree: LanguageServerTree::new(
|
||||
manifest_tree,
|
||||
languages.clone(),
|
||||
@@ -4785,7 +4793,6 @@ impl LspStore {
|
||||
.detach();
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
subscribe_to_binary_statuses(&languages, cx).detach();
|
||||
let _maintain_workspace_config = {
|
||||
let (sender, receiver) = watch::channel();
|
||||
(Self::maintain_workspace_config(receiver, cx), sender)
|
||||
@@ -4811,7 +4818,7 @@ impl LspStore {
|
||||
active_entry: None,
|
||||
|
||||
_maintain_workspace_config,
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10953,6 +10960,19 @@ impl LspStore {
|
||||
.map(|(key, value)| (*key, value))
|
||||
}
|
||||
|
||||
fn should_forward_untagged_binary_status(&self, server_name: &LanguageServerName) -> bool {
|
||||
self.as_local().is_some_and(|local| {
|
||||
local
|
||||
.language_server_ids
|
||||
.keys()
|
||||
.any(|seed| &seed.name == server_name)
|
||||
|| self
|
||||
.language_server_statuses
|
||||
.values()
|
||||
.any(|status| &status.name == server_name)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn has_language_server_seed_for_worktree(&self, worktree_id: WorktreeId) -> bool {
|
||||
self.as_local().is_some_and(|local| {
|
||||
@@ -12116,17 +12136,18 @@ impl LspStore {
|
||||
|
||||
if let Some(name) = name {
|
||||
log::info!("stopping language server {name}");
|
||||
self.languages
|
||||
.update_lsp_binary_status(name.clone(), BinaryStatus::Stopping);
|
||||
if let Some(local) = self.as_local() {
|
||||
local.update_binary_status(name.clone(), BinaryStatus::Stopping);
|
||||
}
|
||||
cx.notify();
|
||||
|
||||
return cx.spawn(async move |lsp_store, cx| {
|
||||
Self::shutdown_language_server(server_state, name.clone(), cx).await;
|
||||
lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.languages
|
||||
.update_lsp_binary_status(name, BinaryStatus::Stopped);
|
||||
if let Some(local) = lsp_store.as_local() {
|
||||
local.update_binary_status(name, BinaryStatus::Stopped);
|
||||
}
|
||||
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
|
||||
cx.notify();
|
||||
})
|
||||
@@ -12677,9 +12698,7 @@ impl LspStore {
|
||||
simulate_disk_based_diagnostics_completion: None,
|
||||
},
|
||||
);
|
||||
local
|
||||
.languages
|
||||
.update_lsp_binary_status(adapter.name(), BinaryStatus::None);
|
||||
local.update_binary_status(adapter.name(), BinaryStatus::None);
|
||||
if let Some(file_ops_caps) = language_server
|
||||
.capabilities()
|
||||
.workspace
|
||||
@@ -12977,10 +12996,21 @@ impl LspStore {
|
||||
local
|
||||
.supplementary_language_servers
|
||||
.insert(id, (name.clone(), server));
|
||||
cx.emit(LspStoreEvent::LanguageServerAdded(id, name, None));
|
||||
cx.emit(LspStoreEvent::SupplementaryLanguageServerAdded(id, name));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn register_supplementary_language_server_for_test(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
name: LanguageServerName,
|
||||
server: Arc<LanguageServer>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.register_supplementary_language_server(id, name, server, cx);
|
||||
}
|
||||
|
||||
fn unregister_supplementary_language_server(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
@@ -12988,19 +13018,17 @@ impl LspStore {
|
||||
) {
|
||||
if let Some(local) = self.as_local_mut() {
|
||||
local.supplementary_language_servers.remove(&id);
|
||||
cx.emit(LspStoreEvent::LanguageServerRemoved(id));
|
||||
cx.emit(LspStoreEvent::SupplementaryLanguageServerRemoved(id));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn supplementary_language_servers(
|
||||
&self,
|
||||
) -> impl '_ + Iterator<Item = (LanguageServerId, LanguageServerName)> {
|
||||
self.as_local().into_iter().flat_map(|local| {
|
||||
local
|
||||
.supplementary_language_servers
|
||||
.iter()
|
||||
.map(|(id, (name, _))| (*id, name.clone()))
|
||||
})
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn unregister_supplementary_language_server_for_test(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.unregister_supplementary_language_server(id, cx);
|
||||
}
|
||||
|
||||
pub fn language_server_adapter_for_id(
|
||||
@@ -14015,11 +14043,19 @@ fn subscribe_to_binary_statuses(
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
cx: &mut Context<'_, LspStore>,
|
||||
) -> Task<()> {
|
||||
let mut server_statuses = languages.language_server_binary_statuses();
|
||||
let (mut server_statuses, subscription) = languages.language_server_binary_statuses();
|
||||
cx.spawn(async move |lsp_store, cx| {
|
||||
while let Some((server_name, binary_status)) = server_statuses.next().await {
|
||||
let _subscription = subscription;
|
||||
while let Some((source, server_name, binary_status)) = server_statuses.next().await {
|
||||
if lsp_store
|
||||
.update(cx, |_, cx| {
|
||||
.update(cx, |lsp_store, cx| {
|
||||
if source.is_some_and(|source| source != cx.entity_id())
|
||||
|| source.is_none()
|
||||
&& !lsp_store.should_forward_untagged_binary_status(&server_name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut message = None;
|
||||
let binary_status = match binary_status {
|
||||
BinaryStatus::None => proto::ServerBinaryStatus::None,
|
||||
@@ -15208,9 +15244,16 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn status_source_id(&self) -> gpui::EntityId {
|
||||
self.lsp_store.entity_id()
|
||||
}
|
||||
|
||||
fn update_status(&self, server_name: LanguageServerName, status: language::BinaryStatus) {
|
||||
self.language_registry
|
||||
.update_lsp_binary_status(server_name, status);
|
||||
self.language_registry.update_lsp_binary_status_for_entity(
|
||||
self.status_source_id(),
|
||||
server_name,
|
||||
status,
|
||||
);
|
||||
}
|
||||
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::Arc,
|
||||
sync::{Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Global for GlobalLogStore {}
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
NewServerLogEntry {
|
||||
id: LanguageServerId,
|
||||
key: LanguageServerLogKey,
|
||||
kind: LanguageServerLogType,
|
||||
text: String,
|
||||
},
|
||||
@@ -46,12 +46,13 @@ impl EventEmitter<Event> for LogStore {}
|
||||
pub struct LogStore {
|
||||
on_headless_host: bool,
|
||||
projects: HashMap<WeakEntity<Project>, ProjectState>,
|
||||
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
|
||||
io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String, Instant)>,
|
||||
pub language_servers: HashMap<LanguageServerLogKey, LanguageServerState>,
|
||||
io_tx: mpsc::UnboundedSender<(LanguageServerLogKey, IoKind, String, Instant)>,
|
||||
}
|
||||
|
||||
struct ProjectState {
|
||||
_subscriptions: [Subscription; 2],
|
||||
copilot_server_id: Option<LanguageServerId>,
|
||||
copilot_log_subscription: Option<lsp::Subscription>,
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ impl Message for RpcMessage {
|
||||
pub struct LanguageServerState {
|
||||
pub name: Option<LanguageServerName>,
|
||||
pub worktree_id: Option<WorktreeId>,
|
||||
pub kind: LanguageServerKind,
|
||||
server: Option<Weak<LanguageServer>>,
|
||||
log_messages: VecDeque<LogMessage>,
|
||||
trace_messages: VecDeque<TraceMessage>,
|
||||
pub rpc_state: Option<LanguageServerRpcState>,
|
||||
@@ -142,12 +143,17 @@ pub struct LanguageServerState {
|
||||
pub toggled_log_kind: Option<LogKind>,
|
||||
}
|
||||
|
||||
impl LanguageServerState {
|
||||
pub fn server(&self) -> Option<Arc<LanguageServer>> {
|
||||
self.server.as_ref()?.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LanguageServerState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LanguageServerState")
|
||||
.field("name", &self.name)
|
||||
.field("worktree_id", &self.worktree_id)
|
||||
.field("kind", &self.kind)
|
||||
.field("log_messages", &self.log_messages)
|
||||
.field("trace_messages", &self.trace_messages)
|
||||
.field("rpc_state", &self.rpc_state)
|
||||
@@ -158,12 +164,12 @@ impl std::fmt::Debug for LanguageServerState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub enum LanguageServerKind {
|
||||
Local { project: WeakEntity<Project> },
|
||||
Remote { project: WeakEntity<Project> },
|
||||
LocalSsh { lsp_store: WeakEntity<LspStore> },
|
||||
Global,
|
||||
Supplementary { project: WeakEntity<Project> },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LanguageServerKind {
|
||||
@@ -172,18 +178,61 @@ impl std::fmt::Debug for LanguageServerKind {
|
||||
LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
|
||||
LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
|
||||
LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"),
|
||||
LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
|
||||
LanguageServerKind::Supplementary { .. } => {
|
||||
write!(f, "LanguageServerKind::Supplementary")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LanguageServerLogKey {
|
||||
pub kind: LanguageServerKind,
|
||||
pub server_id: LanguageServerId,
|
||||
}
|
||||
|
||||
impl LanguageServerLogKey {
|
||||
pub fn new(kind: LanguageServerKind, server_id: LanguageServerId) -> Self {
|
||||
Self { kind, server_id }
|
||||
}
|
||||
|
||||
pub fn is_for_project(
|
||||
&self,
|
||||
project: &WeakEntity<Project>,
|
||||
lsp_store: &WeakEntity<LspStore>,
|
||||
) -> bool {
|
||||
self.kind.is_for_project(project, lsp_store)
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageServerKind {
|
||||
pub fn project(&self) -> Option<&WeakEntity<Project>> {
|
||||
match self {
|
||||
Self::Local { project } => Some(project),
|
||||
Self::Remote { project } => Some(project),
|
||||
Self::Local { project }
|
||||
| Self::Remote { project }
|
||||
| Self::Supplementary { project } => Some(project),
|
||||
Self::LocalSsh { .. } => None,
|
||||
Self::Global { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_for_project(
|
||||
&self,
|
||||
project: &WeakEntity<Project>,
|
||||
lsp_store: &WeakEntity<LspStore>,
|
||||
) -> bool {
|
||||
match self {
|
||||
Self::Local {
|
||||
project: server_project,
|
||||
}
|
||||
| Self::Remote {
|
||||
project: server_project,
|
||||
}
|
||||
| Self::Supplementary {
|
||||
project: server_project,
|
||||
} => server_project == project,
|
||||
Self::LocalSsh {
|
||||
lsp_store: server_lsp_store,
|
||||
} => server_lsp_store == lsp_store,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,10 +449,10 @@ impl LogStore {
|
||||
io_tx,
|
||||
};
|
||||
cx.spawn(async move |log_store, cx| {
|
||||
while let Some((server_id, io_kind, message, observed_at)) = io_rx.next().await {
|
||||
while let Some((server_key, io_kind, message, observed_at)) = io_rx.next().await {
|
||||
if let Some(log_store) = log_store.upgrade() {
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.on_io(server_id, io_kind, &message, observed_at, cx);
|
||||
log_store.on_io(&server_key, io_kind, &message, observed_at, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -423,10 +472,11 @@ impl LogStore {
|
||||
cx.observe_release(project, move |this, _, _| {
|
||||
this.projects.remove(&weak_project);
|
||||
this.language_servers
|
||||
.retain(|_, state| state.kind.project() != Some(&weak_project));
|
||||
.retain(|key, _| key.kind.project() != Some(&weak_project));
|
||||
}),
|
||||
cx.subscribe(project, move |log_store, project, event, cx| {
|
||||
let server_kind = if project.read(cx).is_local() {
|
||||
let is_local = project.read(cx).is_local();
|
||||
let primary_server_kind = if is_local {
|
||||
LanguageServerKind::Local {
|
||||
project: project.downgrade(),
|
||||
}
|
||||
@@ -435,10 +485,32 @@ impl LogStore {
|
||||
project: project.downgrade(),
|
||||
}
|
||||
};
|
||||
let server_kind_for_id = |log_store: &LogStore, server_id| {
|
||||
// Remote project events carry host-side server IDs, which may
|
||||
// collide numerically with locally allocated supplementary
|
||||
// server IDs, so only local projects may resolve an ID to a
|
||||
// supplementary server.
|
||||
if is_local {
|
||||
let supplementary_server_kind = LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
};
|
||||
let supplementary_server_key = LanguageServerLogKey::new(
|
||||
supplementary_server_kind.clone(),
|
||||
server_id,
|
||||
);
|
||||
if log_store
|
||||
.language_servers
|
||||
.contains_key(&supplementary_server_key)
|
||||
{
|
||||
return supplementary_server_kind;
|
||||
}
|
||||
}
|
||||
primary_server_kind.clone()
|
||||
};
|
||||
match event {
|
||||
crate::Event::LanguageServerAdded(id, name, worktree_id) => {
|
||||
log_store.add_language_server(
|
||||
server_kind,
|
||||
primary_server_kind,
|
||||
*id,
|
||||
Some(name.clone()),
|
||||
*worktree_id,
|
||||
@@ -450,6 +522,22 @@ impl LogStore {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
crate::Event::SupplementaryLanguageServerAdded(id, name) => {
|
||||
log_store.add_language_server(
|
||||
LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
*id,
|
||||
Some(name.clone()),
|
||||
None,
|
||||
project
|
||||
.read(cx)
|
||||
.lsp_store()
|
||||
.read(cx)
|
||||
.language_server_for_id(*id),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
crate::Event::LanguageServerBufferRegistered {
|
||||
server_id,
|
||||
buffer_id,
|
||||
@@ -472,7 +560,7 @@ impl LogStore {
|
||||
.map(|status| status.name.clone())
|
||||
});
|
||||
log_store.add_language_server(
|
||||
server_kind,
|
||||
server_kind_for_id(log_store, *server_id),
|
||||
*server_id,
|
||||
name,
|
||||
worktree_id,
|
||||
@@ -481,9 +569,23 @@ impl LogStore {
|
||||
);
|
||||
}
|
||||
crate::Event::LanguageServerRemoved(id) => {
|
||||
log_store.remove_language_server(*id, cx);
|
||||
let server_key =
|
||||
LanguageServerLogKey::new(primary_server_kind, *id);
|
||||
log_store.remove_language_server(&server_key, cx);
|
||||
}
|
||||
crate::Event::SupplementaryLanguageServerRemoved(id) => {
|
||||
let server_key = LanguageServerLogKey::new(
|
||||
LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
*id,
|
||||
);
|
||||
log_store.remove_language_server(&server_key, cx);
|
||||
}
|
||||
crate::Event::LanguageServerLog(id, typ, message) => {
|
||||
let server_kind = server_kind_for_id(log_store, *id);
|
||||
let server_key =
|
||||
LanguageServerLogKey::new(server_kind.clone(), *id);
|
||||
log_store.add_language_server(
|
||||
server_kind,
|
||||
*id,
|
||||
@@ -494,11 +596,16 @@ impl LogStore {
|
||||
);
|
||||
match typ {
|
||||
crate::LanguageServerLogType::Log(typ) => {
|
||||
log_store.add_language_server_log(*id, *typ, message, cx);
|
||||
log_store.add_language_server_log(
|
||||
&server_key,
|
||||
*typ,
|
||||
message,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
crate::LanguageServerLogType::Trace { verbose_info } => {
|
||||
log_store.add_language_server_trace(
|
||||
*id,
|
||||
&server_key,
|
||||
message,
|
||||
verbose_info.clone(),
|
||||
cx,
|
||||
@@ -511,7 +618,7 @@ impl LogStore {
|
||||
MessageKind::Send
|
||||
};
|
||||
log_store.add_language_server_rpc(
|
||||
*id,
|
||||
&server_key,
|
||||
kind,
|
||||
message,
|
||||
RpcTiming::Forwarded(*elapsed),
|
||||
@@ -525,12 +632,17 @@ impl LogStore {
|
||||
enabled,
|
||||
toggled_log_kind,
|
||||
} => {
|
||||
log_store.toggle_lsp_logs(*server_id, *enabled, *toggled_log_kind);
|
||||
let server_key = LanguageServerLogKey::new(
|
||||
server_kind_for_id(log_store, *server_id),
|
||||
*server_id,
|
||||
);
|
||||
log_store.toggle_lsp_logs(&server_key, *enabled, *toggled_log_kind);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}),
|
||||
],
|
||||
copilot_server_id: None,
|
||||
copilot_log_subscription: None,
|
||||
},
|
||||
);
|
||||
@@ -538,9 +650,9 @@ impl LogStore {
|
||||
|
||||
pub fn get_language_server_state(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
) -> Option<&mut LanguageServerState> {
|
||||
self.language_servers.get_mut(&id)
|
||||
self.language_servers.get_mut(key)
|
||||
}
|
||||
|
||||
pub fn add_language_server(
|
||||
@@ -552,21 +664,25 @@ impl LogStore {
|
||||
server: Option<Arc<LanguageServer>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<&mut LanguageServerState> {
|
||||
let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
|
||||
cx.notify();
|
||||
LanguageServerState {
|
||||
name: None,
|
||||
worktree_id: None,
|
||||
kind,
|
||||
rpc_state: None,
|
||||
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
|
||||
trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
|
||||
trace_level: TraceValue::Off,
|
||||
log_level: MessageType::LOG,
|
||||
io_logs_subscription: None,
|
||||
toggled_log_kind: None,
|
||||
}
|
||||
});
|
||||
let server_key = LanguageServerLogKey::new(kind, server_id);
|
||||
let server_state = self
|
||||
.language_servers
|
||||
.entry(server_key.clone())
|
||||
.or_insert_with(|| {
|
||||
cx.notify();
|
||||
LanguageServerState {
|
||||
name: None,
|
||||
worktree_id: None,
|
||||
server: server.as_ref().map(Arc::downgrade),
|
||||
rpc_state: None,
|
||||
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
|
||||
trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
|
||||
trace_level: TraceValue::Off,
|
||||
log_level: MessageType::LOG,
|
||||
io_logs_subscription: None,
|
||||
toggled_log_kind: None,
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(name) = name {
|
||||
server_state.name = Some(name);
|
||||
@@ -575,15 +691,29 @@ impl LogStore {
|
||||
server_state.worktree_id = Some(worktree_id);
|
||||
}
|
||||
|
||||
if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
|
||||
let io_tx = self.io_tx.clone();
|
||||
let server_id = server.server_id();
|
||||
server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
|
||||
let observed_at = Instant::now();
|
||||
io_tx
|
||||
.unbounded_send((server_id, io_kind, message.to_string(), observed_at))
|
||||
.ok();
|
||||
}));
|
||||
if let Some(server) = server {
|
||||
let is_new_server = match server_state.server() {
|
||||
Some(current_server) => !Arc::ptr_eq(¤t_server, &server),
|
||||
None => true,
|
||||
};
|
||||
if is_new_server {
|
||||
server_state.server = Some(Arc::downgrade(&server));
|
||||
server_state.io_logs_subscription = None;
|
||||
}
|
||||
if server_state.io_logs_subscription.is_none() {
|
||||
let io_tx = self.io_tx.clone();
|
||||
server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
|
||||
let observed_at = Instant::now();
|
||||
io_tx
|
||||
.unbounded_send((
|
||||
server_key.clone(),
|
||||
io_kind,
|
||||
message.to_string(),
|
||||
observed_at,
|
||||
))
|
||||
.ok();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Some(server_state)
|
||||
@@ -591,13 +721,13 @@ impl LogStore {
|
||||
|
||||
pub fn add_language_server_log(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
typ: MessageType,
|
||||
message: &str,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let store_logs = !self.on_headless_host;
|
||||
let language_server_state = self.get_language_server_state(id)?;
|
||||
let language_server_state = self.get_language_server_state(key)?;
|
||||
|
||||
let log_lines = &mut language_server_state.log_messages;
|
||||
let message = message.trim_end().to_string();
|
||||
@@ -605,7 +735,7 @@ impl LogStore {
|
||||
// Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
|
||||
self.emit_event(
|
||||
Event::NewServerLogEntry {
|
||||
id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Log(typ),
|
||||
text: message,
|
||||
},
|
||||
@@ -618,7 +748,7 @@ impl LogStore {
|
||||
) {
|
||||
self.emit_event(
|
||||
Event::NewServerLogEntry {
|
||||
id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Log(typ),
|
||||
text: new_message,
|
||||
},
|
||||
@@ -630,20 +760,20 @@ impl LogStore {
|
||||
|
||||
fn add_language_server_trace(
|
||||
&mut self,
|
||||
id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
message: &str,
|
||||
verbose_info: Option<String>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let store_logs = !self.on_headless_host;
|
||||
let language_server_state = self.get_language_server_state(id)?;
|
||||
let language_server_state = self.get_language_server_state(key)?;
|
||||
|
||||
let log_lines = &mut language_server_state.trace_messages;
|
||||
if !store_logs {
|
||||
// Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
|
||||
self.emit_event(
|
||||
Event::NewServerLogEntry {
|
||||
id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Trace { verbose_info },
|
||||
text: message.trim().to_string(),
|
||||
},
|
||||
@@ -669,7 +799,7 @@ impl LogStore {
|
||||
}
|
||||
self.emit_event(
|
||||
Event::NewServerLogEntry {
|
||||
id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Trace { verbose_info },
|
||||
text: new_message,
|
||||
},
|
||||
@@ -696,7 +826,7 @@ impl LogStore {
|
||||
|
||||
fn add_language_server_rpc(
|
||||
&mut self,
|
||||
language_server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
kind: MessageKind,
|
||||
message: &str,
|
||||
timing: RpcTiming,
|
||||
@@ -704,7 +834,7 @@ impl LogStore {
|
||||
) {
|
||||
let store_logs = !self.on_headless_host;
|
||||
let Some(state) = self
|
||||
.get_language_server_state(language_server_id)
|
||||
.get_language_server_state(key)
|
||||
.and_then(|state| state.rpc_state.as_mut())
|
||||
else {
|
||||
return;
|
||||
@@ -737,7 +867,7 @@ impl LogStore {
|
||||
if let Some(header) = header {
|
||||
// Do not send a synthetic message over the wire, it will be derived from the actual RPC message
|
||||
cx.emit(Event::NewServerLogEntry {
|
||||
id: language_server_id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Rpc {
|
||||
received,
|
||||
elapsed: None,
|
||||
@@ -748,7 +878,7 @@ impl LogStore {
|
||||
|
||||
self.emit_event(
|
||||
Event::NewServerLogEntry {
|
||||
id: language_server_id,
|
||||
key: key.clone(),
|
||||
kind: LanguageServerLogType::Rpc { received, elapsed },
|
||||
text: message.to_owned(),
|
||||
},
|
||||
@@ -756,44 +886,37 @@ impl LogStore {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
|
||||
self.language_servers.remove(&id);
|
||||
pub fn remove_language_server(&mut self, key: &LanguageServerLogKey, cx: &mut Context<Self>) {
|
||||
self.language_servers.remove(key);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
|
||||
Some(&self.language_servers.get(&server_id)?.log_messages)
|
||||
pub fn server_logs(&self, key: &LanguageServerLogKey) -> Option<&VecDeque<LogMessage>> {
|
||||
Some(&self.language_servers.get(key)?.log_messages)
|
||||
}
|
||||
|
||||
pub fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
|
||||
Some(&self.language_servers.get(&server_id)?.trace_messages)
|
||||
pub fn server_trace(&self, key: &LanguageServerLogKey) -> Option<&VecDeque<TraceMessage>> {
|
||||
Some(&self.language_servers.get(key)?.trace_messages)
|
||||
}
|
||||
|
||||
pub fn server_ids_for_project<'a>(
|
||||
pub fn server_keys_for_project<'a>(
|
||||
&'a self,
|
||||
lookup_project: &'a WeakEntity<Project>,
|
||||
) -> impl Iterator<Item = LanguageServerId> + 'a {
|
||||
project: &'a WeakEntity<Project>,
|
||||
lsp_store: &'a WeakEntity<LspStore>,
|
||||
) -> impl Iterator<Item = LanguageServerLogKey> + 'a {
|
||||
self.language_servers
|
||||
.iter()
|
||||
.filter_map(move |(id, state)| match &state.kind {
|
||||
LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
|
||||
if project == lookup_project {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id),
|
||||
})
|
||||
.keys()
|
||||
.filter(move |key| key.is_for_project(project, lsp_store))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn enable_rpc_trace_for_language_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
) -> Option<&mut LanguageServerRpcState> {
|
||||
let rpc_state = self
|
||||
.language_servers
|
||||
.get_mut(&server_id)?
|
||||
.get_mut(key)?
|
||||
.rpc_state
|
||||
.get_or_insert_with(|| LanguageServerRpcState {
|
||||
rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
|
||||
@@ -805,25 +928,30 @@ impl LogStore {
|
||||
|
||||
pub fn disable_rpc_trace_for_language_server(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
) -> Option<()> {
|
||||
self.language_servers.get_mut(&server_id)?.rpc_state.take();
|
||||
self.language_servers.get_mut(key)?.rpc_state.take();
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
|
||||
match server {
|
||||
LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
|
||||
LanguageServerSelector::Name(name) => self
|
||||
.language_servers
|
||||
.iter()
|
||||
.any(|(_, state)| state.name.as_ref() == Some(name)),
|
||||
}
|
||||
pub fn has_server_logs(
|
||||
&self,
|
||||
server: &LanguageServerSelector,
|
||||
project: &WeakEntity<Project>,
|
||||
lsp_store: &WeakEntity<LspStore>,
|
||||
) -> bool {
|
||||
self.language_servers.iter().any(|(key, state)| {
|
||||
key.is_for_project(project, lsp_store)
|
||||
&& match server {
|
||||
LanguageServerSelector::Id(id) => key.server_id == *id,
|
||||
LanguageServerSelector::Name(name) => state.name.as_ref() == Some(name),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_io(
|
||||
&mut self,
|
||||
language_server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
io_kind: IoKind,
|
||||
message: &str,
|
||||
observed_at: Instant,
|
||||
@@ -833,7 +961,7 @@ impl LogStore {
|
||||
IoKind::StdOut => true,
|
||||
IoKind::StdIn => false,
|
||||
IoKind::StdErr => {
|
||||
self.add_language_server_log(language_server_id, MessageType::LOG, message, cx);
|
||||
self.add_language_server_log(key, MessageType::LOG, message, cx);
|
||||
return Some(());
|
||||
}
|
||||
};
|
||||
@@ -844,36 +972,30 @@ impl LogStore {
|
||||
MessageKind::Send
|
||||
};
|
||||
|
||||
self.add_language_server_rpc(
|
||||
language_server_id,
|
||||
kind,
|
||||
message,
|
||||
RpcTiming::ObservedAt(observed_at),
|
||||
cx,
|
||||
);
|
||||
self.add_language_server_rpc(key, kind, message, RpcTiming::ObservedAt(observed_at), cx);
|
||||
cx.notify();
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn emit_event(&mut self, e: Event, cx: &mut Context<Self>) {
|
||||
match &e {
|
||||
Event::NewServerLogEntry { id, kind, text } => {
|
||||
if let Some(state) = self.get_language_server_state(*id) {
|
||||
let downstream_client = match &state.kind {
|
||||
Event::NewServerLogEntry { key, kind, text } => {
|
||||
if let Some(state) = self.get_language_server_state(key) {
|
||||
let downstream_client = match &key.kind {
|
||||
LanguageServerKind::Remote { project }
|
||||
| LanguageServerKind::Local { project } => project
|
||||
.upgrade()
|
||||
.map(|project| project.read(cx).lsp_store()),
|
||||
LanguageServerKind::LocalSsh { lsp_store } => lsp_store.upgrade(),
|
||||
LanguageServerKind::Global => None,
|
||||
LanguageServerKind::Supplementary { .. } => None,
|
||||
}
|
||||
.and_then(|lsp_store| lsp_store.read(cx).downstream_client());
|
||||
if let Some((client, project_id)) = downstream_client {
|
||||
if Some(LogKind::from_server_log_type(kind)) == state.toggled_log_kind {
|
||||
if state.toggled_log_kind == Some(LogKind::from_server_log_type(kind)) {
|
||||
client
|
||||
.send(proto::LanguageServerLog {
|
||||
project_id,
|
||||
language_server_id: id.to_proto(),
|
||||
language_server_id: key.server_id.to_proto(),
|
||||
message: text.clone(),
|
||||
log_type: Some(kind.to_proto()),
|
||||
})
|
||||
@@ -889,31 +1011,88 @@ impl LogStore {
|
||||
|
||||
pub fn toggle_lsp_logs(
|
||||
&mut self,
|
||||
server_id: LanguageServerId,
|
||||
key: &LanguageServerLogKey,
|
||||
enabled: bool,
|
||||
toggled_log_kind: LogKind,
|
||||
) {
|
||||
if let Some(server_state) = self.get_language_server_state(server_id) {
|
||||
if let Some(server_state) = self.get_language_server_state(key) {
|
||||
if enabled {
|
||||
server_state.toggled_log_kind = Some(toggled_log_kind);
|
||||
} else {
|
||||
server_state.toggled_log_kind = None;
|
||||
}
|
||||
}
|
||||
if LogKind::Rpc == toggled_log_kind {
|
||||
if toggled_log_kind == LogKind::Rpc {
|
||||
if enabled {
|
||||
self.enable_rpc_trace_for_language_server(server_id);
|
||||
self.enable_rpc_trace_for_language_server(key);
|
||||
} else {
|
||||
self.disable_rpc_trace_for_language_server(server_id);
|
||||
self.disable_rpc_trace_for_language_server(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn copilot_state_for_project(
|
||||
pub fn sync_copilot_for_project(
|
||||
&mut self,
|
||||
project: &WeakEntity<Project>,
|
||||
) -> Option<&mut Option<lsp::Subscription>> {
|
||||
self.projects
|
||||
.get_mut(project)
|
||||
.map(|project| &mut project.copilot_log_subscription)
|
||||
server: Option<Arc<LanguageServer>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let server_kind = LanguageServerKind::Supplementary {
|
||||
project: project.clone(),
|
||||
};
|
||||
let current_server_id = server.as_ref().map(|server| server.server_id());
|
||||
let current_server_matches = server.as_ref().is_some_and(|server| {
|
||||
let server_key = LanguageServerLogKey::new(server_kind.clone(), server.server_id());
|
||||
self.language_servers
|
||||
.get(&server_key)
|
||||
.and_then(|state| state.server())
|
||||
.is_some_and(|current_server| Arc::ptr_eq(¤t_server, server))
|
||||
});
|
||||
let project_state = self.projects.get_mut(project)?;
|
||||
if project_state.copilot_server_id == current_server_id && current_server_matches {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
project_state.copilot_log_subscription = None;
|
||||
let previous_server_id = project_state.copilot_server_id.take();
|
||||
if previous_server_id != current_server_id
|
||||
&& let Some(previous_server_id) = previous_server_id
|
||||
{
|
||||
let previous_server_key =
|
||||
LanguageServerLogKey::new(server_kind.clone(), previous_server_id);
|
||||
self.remove_language_server(&previous_server_key, cx);
|
||||
}
|
||||
|
||||
let Some(server) = server else {
|
||||
return Some(());
|
||||
};
|
||||
let server_id = server.server_id();
|
||||
let server_key = LanguageServerLogKey::new(server_kind.clone(), server_id);
|
||||
let weak_log_store = cx.weak_entity();
|
||||
let log_subscription =
|
||||
server.on_notification::<lsp::notification::LogMessage, _>(move |params, cx| {
|
||||
weak_log_store
|
||||
.update(cx, |log_store, cx| {
|
||||
log_store.add_language_server_log(
|
||||
&server_key,
|
||||
MessageType::LOG,
|
||||
¶ms.message,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
self.add_language_server(
|
||||
server_kind,
|
||||
server_id,
|
||||
Some(LanguageServerName::new_static("copilot")),
|
||||
None,
|
||||
Some(server),
|
||||
cx,
|
||||
);
|
||||
|
||||
let project_state = self.projects.get_mut(project)?;
|
||||
project_state.copilot_server_id = Some(server_id);
|
||||
project_state.copilot_log_subscription = Some(log_subscription);
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,9 @@ pub struct ToastLink {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Event {
|
||||
LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
|
||||
SupplementaryLanguageServerAdded(LanguageServerId, LanguageServerName),
|
||||
LanguageServerRemoved(LanguageServerId),
|
||||
SupplementaryLanguageServerRemoved(LanguageServerId),
|
||||
LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
|
||||
// [`lsp::notification::DidOpenTextDocument`] was sent to this server using the buffer data.
|
||||
// Zed's buffer-related data is updated accordingly.
|
||||
@@ -3696,9 +3698,15 @@ impl Project {
|
||||
LspStoreEvent::LanguageServerAdded(server_id, name, worktree_id) => cx.emit(
|
||||
Event::LanguageServerAdded(*server_id, name.clone(), *worktree_id),
|
||||
),
|
||||
LspStoreEvent::SupplementaryLanguageServerAdded(server_id, name) => cx.emit(
|
||||
Event::SupplementaryLanguageServerAdded(*server_id, name.clone()),
|
||||
),
|
||||
LspStoreEvent::LanguageServerRemoved(server_id) => {
|
||||
cx.emit(Event::LanguageServerRemoved(*server_id))
|
||||
}
|
||||
LspStoreEvent::SupplementaryLanguageServerRemoved(server_id) => {
|
||||
cx.emit(Event::SupplementaryLanguageServerRemoved(*server_id))
|
||||
}
|
||||
LspStoreEvent::LanguageServerLog(server_id, log_type, string) => cx.emit(
|
||||
Event::LanguageServerLog(*server_id, log_type.clone(), string.clone()),
|
||||
),
|
||||
@@ -6260,13 +6268,6 @@ impl Project {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn supplementary_language_servers<'a>(
|
||||
&'a self,
|
||||
cx: &'a App,
|
||||
) -> impl 'a + Iterator<Item = (LanguageServerId, LanguageServerName)> {
|
||||
self.lsp_store.read(cx).supplementary_language_servers()
|
||||
}
|
||||
|
||||
pub fn any_language_server_supports_inlay_hints(&self, buffer: &Buffer, cx: &mut App) -> bool {
|
||||
let Some(language) = buffer.language().cloned() else {
|
||||
return false;
|
||||
|
||||
@@ -22,7 +22,9 @@ use project::{
|
||||
debugger::{breakpoint_store::BreakpointStore, dap_store::DapStore},
|
||||
git_store::GitStore,
|
||||
image_store::ImageId,
|
||||
lsp_store::log_store::{self, GlobalLogStore, LanguageServerKind, LogKind},
|
||||
lsp_store::log_store::{
|
||||
self, GlobalLogStore, LanguageServerKind, LanguageServerLogKey, LogKind,
|
||||
},
|
||||
project_settings::SettingsObserver,
|
||||
search::SearchQuery,
|
||||
task_store::TaskStore,
|
||||
@@ -408,13 +410,39 @@ impl HeadlessProject {
|
||||
});
|
||||
}
|
||||
}
|
||||
LspStoreEvent::LanguageServerRemoved(id) => {
|
||||
LspStoreEvent::SupplementaryLanguageServerAdded(id, name) => {
|
||||
let log_store = cx
|
||||
.try_global::<GlobalLogStore>()
|
||||
.map(|lsp_logs| lsp_logs.0.clone());
|
||||
if let Some(log_store) = log_store {
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.remove_language_server(*id, cx);
|
||||
log_store.add_language_server(
|
||||
LanguageServerKind::LocalSsh {
|
||||
lsp_store: self.lsp_store.downgrade(),
|
||||
},
|
||||
*id,
|
||||
Some(name.clone()),
|
||||
None,
|
||||
lsp_store.read(cx).language_server_for_id(*id),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
LspStoreEvent::LanguageServerRemoved(id)
|
||||
| LspStoreEvent::SupplementaryLanguageServerRemoved(id) => {
|
||||
let log_store = cx
|
||||
.try_global::<GlobalLogStore>()
|
||||
.map(|lsp_logs| lsp_logs.0.clone());
|
||||
if let Some(log_store) = log_store {
|
||||
let server_key = LanguageServerLogKey::new(
|
||||
LanguageServerKind::LocalSsh {
|
||||
lsp_store: self.lsp_store.downgrade(),
|
||||
},
|
||||
*id,
|
||||
);
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.remove_language_server(&server_key, cx);
|
||||
});
|
||||
}
|
||||
self.session
|
||||
@@ -837,11 +865,12 @@ impl HeadlessProject {
|
||||
}
|
||||
|
||||
async fn handle_toggle_lsp_logs(
|
||||
_: Entity<Self>,
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::ToggleLspLogs>,
|
||||
cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
let server_id = LanguageServerId::from_proto(envelope.payload.server_id);
|
||||
let lsp_store = this.read_with(&cx, |this, _| this.lsp_store.downgrade());
|
||||
cx.update(|cx| {
|
||||
let log_store = cx
|
||||
.try_global::<GlobalLogStore>()
|
||||
@@ -856,8 +885,10 @@ impl HeadlessProject {
|
||||
proto::toggle_lsp_logs::LogType::Trace => LogKind::Trace,
|
||||
proto::toggle_lsp_logs::LogType::Rpc => LogKind::Rpc,
|
||||
};
|
||||
let server_key =
|
||||
LanguageServerLogKey::new(LanguageServerKind::LocalSsh { lsp_store }, server_id);
|
||||
log_store.update(cx, |log_store, _| {
|
||||
log_store.toggle_lsp_logs(server_id, envelope.payload.enabled, toggled_log_kind);
|
||||
log_store.toggle_lsp_logs(&server_key, envelope.payload.enabled, toggled_log_kind);
|
||||
});
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
|
||||
@@ -36,13 +36,14 @@ use language::{
|
||||
};
|
||||
use lsp::{
|
||||
CompletionContext, CompletionResponse, CompletionTriggerKind, DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||
LanguageServerName,
|
||||
LanguageServerId, LanguageServerName,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{
|
||||
ProgressToken, Project, ProjectPath,
|
||||
LanguageServerLogType, ProgressToken, Project, ProjectPath,
|
||||
agent_server_store::AgentServerCommand,
|
||||
image_store,
|
||||
lsp_store::log_store::{LanguageServerKind, LanguageServerLogKey, LogStore},
|
||||
search::{SearchQuery, SearchResult},
|
||||
};
|
||||
use remote::{ConnectionState, RemoteClient, RemoteClientEvent};
|
||||
@@ -4692,6 +4693,77 @@ async fn test_remote_project_creation_notifies_new_entity_observers(
|
||||
assert!(project.read_with(cx, |project, _| project.is_remote()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_log_store_keys_remote_events_by_primary_kind_on_supplementary_id_collision(
|
||||
cx: &mut TestAppContext,
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
let server_fs = Arc::new(FakeFs::new(server_cx.executor()));
|
||||
server_fs
|
||||
.insert_tree(path!("/code"), json!({ "project1": { "README.md": "" } }))
|
||||
.await;
|
||||
let (project, _headless) = init_test(&server_fs, cx, server_cx).await;
|
||||
|
||||
let log_store = cx.new(|cx| LogStore::new(false, cx));
|
||||
log_store.update(cx, |log_store, cx| log_store.add_project(&project, cx));
|
||||
|
||||
// A supplementary server (e.g. Copilot) allocates its ID from the local
|
||||
// registry, which may collide numerically with a host-side server ID.
|
||||
let server_id = LanguageServerId(42);
|
||||
let supplementary_server_key = LanguageServerLogKey::new(
|
||||
LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
server_id,
|
||||
);
|
||||
log_store.update(cx, |log_store, cx| {
|
||||
log_store.add_language_server(
|
||||
LanguageServerKind::Supplementary {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
server_id,
|
||||
Some(LanguageServerName::new_static("copilot")),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::LanguageServerLog(
|
||||
server_id,
|
||||
LanguageServerLogType::Log(lsp::MessageType::LOG),
|
||||
"host server log".to_string(),
|
||||
));
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let remote_server_key = LanguageServerLogKey::new(
|
||||
LanguageServerKind::Remote {
|
||||
project: project.downgrade(),
|
||||
},
|
||||
server_id,
|
||||
);
|
||||
log_store.read_with(cx, |log_store, _| {
|
||||
assert_eq!(
|
||||
log_store.server_logs(&remote_server_key).map(|logs| {
|
||||
logs.iter()
|
||||
.map(|log| log.as_ref().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
Some(vec!["host server log".to_string()]),
|
||||
"host server logs should be keyed by the remote server kind"
|
||||
);
|
||||
assert_eq!(
|
||||
log_store
|
||||
.server_logs(&supplementary_server_key)
|
||||
.map(|logs| logs.len()),
|
||||
Some(0),
|
||||
"host server logs should not leak into the supplementary server with the same ID"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn init_test(
|
||||
server_fs: &Arc<FakeFs>,
|
||||
cx: &mut TestAppContext,
|
||||
|
||||
@@ -602,12 +602,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut App) {
|
||||
let diagnostic_summary =
|
||||
cx.new(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
|
||||
let active_file_name = cx.new(|_| workspace::active_file_name::ActiveFileName::new());
|
||||
let activity_indicator = activity_indicator::ActivityIndicator::new(
|
||||
workspace,
|
||||
workspace.project().read(cx).languages().clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let activity_indicator = activity_indicator::ActivityIndicator::new(workspace, window, cx);
|
||||
let active_buffer_encoding =
|
||||
cx.new(|_| encoding_selector::ActiveBufferEncoding::new(workspace));
|
||||
let active_buffer_language =
|
||||
@@ -2948,9 +2943,8 @@ mod tests {
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace =
|
||||
multi_workspace.read_with(cx, |multi_workspace, _| multi_workspace.workspace().clone());
|
||||
let languages = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let indicator = workspace.update_in(cx, |workspace, window, cx| {
|
||||
activity_indicator::ActivityIndicator::new(workspace, languages, window, cx)
|
||||
activity_indicator::ActivityIndicator::new(workspace, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
|
||||
Reference in new issue
Block a user