Regroup LSP context menu items by the worktree name (#34838)
Also * remove the feature gate * open buffers with an error when no logs are present * adjust the hover text to indicate that difference <img width="480" height="380" alt="image" src="https://github.com/user-attachments/assets/6b2350fc-5121-4b1e-bc22-503d964531a2" /> Release Notes: - N/A
This commit is contained in:
parent
b6cf398eab
commit
254c7a330a
4 changed files with 363 additions and 247 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -9165,7 +9165,6 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"copilot",
|
"copilot",
|
||||||
"editor",
|
"editor",
|
||||||
"feature_flags",
|
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
|
|
@ -231,7 +231,6 @@ impl ActivityIndicator {
|
||||||
status,
|
status,
|
||||||
} => {
|
} => {
|
||||||
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||||
let project = project.clone();
|
|
||||||
let status = status.clone();
|
let status = status.clone();
|
||||||
let server_name = server_name.clone();
|
let server_name = server_name.clone();
|
||||||
cx.spawn_in(window, async move |workspace, cx| {
|
cx.spawn_in(window, async move |workspace, cx| {
|
||||||
|
@ -247,8 +246,7 @@ impl ActivityIndicator {
|
||||||
workspace.update_in(cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.add_item_to_active_pane(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(cx.new(|cx| {
|
Box::new(cx.new(|cx| {
|
||||||
let mut editor =
|
let mut editor = Editor::for_buffer(buffer, None, window, cx);
|
||||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx);
|
|
||||||
editor.set_read_only(true);
|
editor.set_read_only(true);
|
||||||
editor
|
editor
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -18,7 +18,6 @@ client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
copilot.workspace = true
|
copilot.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
feature_flags.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
use std::{collections::hash_map, path::PathBuf, rc::Rc, time::Duration};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use client::proto;
|
use client::proto;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::HashSet;
|
||||||
use editor::{Editor, EditorEvent};
|
use editor::{Editor, EditorEvent};
|
||||||
use feature_flags::FeatureFlagAppExt as _;
|
|
||||||
use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};
|
use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};
|
||||||
use language::{BinaryStatus, BufferId, LocalFile, ServerHealth};
|
use language::{BinaryStatus, BufferId, ServerHealth};
|
||||||
use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
|
use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
|
||||||
use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings};
|
use project::{LspStore, LspStoreEvent, Worktree, project_settings::ProjectSettings};
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::{Settings as _, SettingsStore};
|
||||||
use ui::{
|
use ui::{
|
||||||
Context, ContextMenu, ContextMenuEntry, ContextMenuItem, DocumentationAside, DocumentationSide,
|
Context, ContextMenu, ContextMenuEntry, ContextMenuItem, DocumentationAside, DocumentationSide,
|
||||||
|
@ -36,8 +40,7 @@ pub struct LspTool {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LanguageServerState {
|
struct LanguageServerState {
|
||||||
items: Vec<LspItem>,
|
items: Vec<LspMenuItem>,
|
||||||
other_servers_start_index: Option<usize>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
lsp_store: WeakEntity<LspStore>,
|
lsp_store: WeakEntity<LspStore>,
|
||||||
active_editor: Option<ActiveEditor>,
|
active_editor: Option<ActiveEditor>,
|
||||||
|
@ -63,8 +66,13 @@ impl std::fmt::Debug for ActiveEditor {
|
||||||
struct LanguageServers {
|
struct LanguageServers {
|
||||||
health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
|
health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
|
||||||
binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
|
binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
|
||||||
servers_per_buffer_abs_path:
|
servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,
|
||||||
HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>,
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ServersForPath {
|
||||||
|
servers: HashMap<LanguageServerId, Option<LanguageServerName>>,
|
||||||
|
worktree: Option<WeakEntity<Worktree>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -120,8 +128,8 @@ impl LanguageServerState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut first_button_encountered = false;
|
let mut first_button_encountered = false;
|
||||||
for (i, item) in self.items.iter().enumerate() {
|
for item in &self.items {
|
||||||
if let LspItem::ToggleServersButton { restart } = item {
|
if let LspMenuItem::ToggleServersButton { restart } = item {
|
||||||
let label = if *restart {
|
let label = if *restart {
|
||||||
"Restart All Servers"
|
"Restart All Servers"
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,22 +148,19 @@ impl LanguageServerState {
|
||||||
};
|
};
|
||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
let buffer_store = project.read(cx).buffer_store().clone();
|
let buffer_store = project.read(cx).buffer_store().clone();
|
||||||
let worktree_store = project.read(cx).worktree_store();
|
|
||||||
|
|
||||||
let buffers = state
|
let buffers = state
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.language_servers
|
.language_servers
|
||||||
.servers_per_buffer_abs_path
|
.servers_per_buffer_abs_path
|
||||||
.keys()
|
.iter()
|
||||||
.filter_map(|abs_path| {
|
.filter_map(|(abs_path, servers)| {
|
||||||
worktree_store.read(cx).find_worktree(abs_path, cx)
|
let worktree =
|
||||||
})
|
servers.worktree.as_ref()?.upgrade()?.read(cx);
|
||||||
.filter_map(|(worktree, relative_path)| {
|
let relative_path =
|
||||||
let entry =
|
abs_path.strip_prefix(&worktree.abs_path()).ok()?;
|
||||||
worktree.read(cx).entry_for_path(&relative_path)?;
|
let entry = worktree.entry_for_path(&relative_path)?;
|
||||||
project.read(cx).path_for_entry(entry.id, cx)
|
let project_path =
|
||||||
})
|
project.read(cx).path_for_entry(entry.id, cx)?;
|
||||||
.filter_map(|project_path| {
|
|
||||||
buffer_store.read(cx).get_by_path(&project_path)
|
buffer_store.read(cx).get_by_path(&project_path)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -165,13 +170,16 @@ impl LanguageServerState {
|
||||||
.iter()
|
.iter()
|
||||||
// Do not try to use IDs as we have stopped all servers already, when allowing to restart them all
|
// Do not try to use IDs as we have stopped all servers already, when allowing to restart them all
|
||||||
.flat_map(|item| match item {
|
.flat_map(|item| match item {
|
||||||
LspItem::ToggleServersButton { .. } => None,
|
LspMenuItem::Header { .. } => None,
|
||||||
LspItem::WithHealthCheck(_, status, ..) => Some(
|
LspMenuItem::ToggleServersButton { .. } => None,
|
||||||
LanguageServerSelector::Name(status.name.clone()),
|
LspMenuItem::WithHealthCheck { health, .. } => Some(
|
||||||
),
|
LanguageServerSelector::Name(health.name.clone()),
|
||||||
LspItem::WithBinaryStatus(_, server_name, ..) => Some(
|
|
||||||
LanguageServerSelector::Name(server_name.clone()),
|
|
||||||
),
|
),
|
||||||
|
LspMenuItem::WithBinaryStatus {
|
||||||
|
server_name, ..
|
||||||
|
} => Some(LanguageServerSelector::Name(
|
||||||
|
server_name.clone(),
|
||||||
|
)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
lsp_store.restart_language_servers_for_buffers(
|
lsp_store.restart_language_servers_for_buffers(
|
||||||
|
@ -190,13 +198,17 @@ impl LanguageServerState {
|
||||||
}
|
}
|
||||||
menu = menu.item(button);
|
menu = menu.item(button);
|
||||||
continue;
|
continue;
|
||||||
};
|
} else if let LspMenuItem::Header { header, separator } = item {
|
||||||
|
menu = menu
|
||||||
|
.when(*separator, |menu| menu.separator())
|
||||||
|
.when_some(header.as_ref(), |menu, header| menu.header(header));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(server_info) = item.server_info() else {
|
let Some(server_info) = item.server_info() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace = self.workspace.clone();
|
|
||||||
let server_selector = server_info.server_selector();
|
let server_selector = server_info.server_selector();
|
||||||
// TODO currently, Zed remote does not work well with the LSP logs
|
// TODO currently, Zed remote does not work well with the LSP logs
|
||||||
// https://github.com/zed-industries/zed/issues/28557
|
// https://github.com/zed-industries/zed/issues/28557
|
||||||
|
@ -205,6 +217,7 @@ impl LanguageServerState {
|
||||||
|
|
||||||
let status_color = server_info
|
let status_color = server_info
|
||||||
.binary_status
|
.binary_status
|
||||||
|
.as_ref()
|
||||||
.and_then(|binary_status| match binary_status.status {
|
.and_then(|binary_status| match binary_status.status {
|
||||||
BinaryStatus::None => None,
|
BinaryStatus::None => None,
|
||||||
BinaryStatus::CheckingForUpdate
|
BinaryStatus::CheckingForUpdate
|
||||||
|
@ -223,17 +236,20 @@ impl LanguageServerState {
|
||||||
})
|
})
|
||||||
.unwrap_or(Color::Success);
|
.unwrap_or(Color::Success);
|
||||||
|
|
||||||
if self
|
let message = server_info
|
||||||
.other_servers_start_index
|
.message
|
||||||
.is_some_and(|index| index == i)
|
.as_ref()
|
||||||
{
|
.or_else(|| server_info.binary_status.as_ref()?.message.as_ref())
|
||||||
menu = menu.separator().header("Other Buffers");
|
.cloned();
|
||||||
}
|
let hover_label = if has_logs {
|
||||||
|
Some("View Logs")
|
||||||
if i == 0 && self.other_servers_start_index.is_some() {
|
} else if message.is_some() {
|
||||||
menu = menu.header("Current Buffer");
|
Some("View Message")
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let server_name = server_info.name.clone();
|
||||||
menu = menu.item(ContextMenuItem::custom_entry(
|
menu = menu.item(ContextMenuItem::custom_entry(
|
||||||
move |_, _| {
|
move |_, _| {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -245,42 +261,99 @@ impl LanguageServerState {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Indicator::dot().color(status_color))
|
.child(Indicator::dot().color(status_color))
|
||||||
.child(Label::new(server_info.name.0.clone())),
|
.child(Label::new(server_name.0.clone())),
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.visible_on_hover("menu_item")
|
|
||||||
.child(
|
|
||||||
Label::new("View Logs")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ChevronRight)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.when_some(hover_label, |div, hover_label| {
|
||||||
|
div.child(
|
||||||
|
h_flex()
|
||||||
|
.visible_on_hover("menu_item")
|
||||||
|
.child(
|
||||||
|
Label::new(hover_label)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::ChevronRight)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let lsp_logs = lsp_logs.clone();
|
let lsp_logs = lsp_logs.clone();
|
||||||
|
let message = message.clone();
|
||||||
|
let server_selector = server_selector.clone();
|
||||||
|
let server_name = server_info.name.clone();
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
if !has_logs {
|
if has_logs {
|
||||||
|
lsp_logs.update(cx, |lsp_logs, cx| {
|
||||||
|
lsp_logs.open_server_trace(
|
||||||
|
workspace.clone(),
|
||||||
|
server_selector.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if let Some(message) = &message {
|
||||||
|
let Some(create_buffer) = workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.project()
|
||||||
|
.update(cx, |project, cx| project.create_buffer(cx))
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let window = window.window_handle();
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
let message = message.clone();
|
||||||
|
let server_name = server_name.clone();
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let buffer = create_buffer.await?;
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit(
|
||||||
|
[(
|
||||||
|
0..0,
|
||||||
|
format!("Language server {server_name}:\n\n{message}"),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
buffer.set_capability(language::Capability::ReadOnly, cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
window.update(cx, |_, window, cx| {
|
||||||
|
workspace.add_item_to_active_pane(
|
||||||
|
Box::new(cx.new(|cx| {
|
||||||
|
let mut editor =
|
||||||
|
Editor::for_buffer(buffer, None, window, cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
|
editor
|
||||||
|
})),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
})??;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
} else {
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lsp_logs.update(cx, |lsp_logs, cx| {
|
|
||||||
lsp_logs.open_server_trace(
|
|
||||||
workspace.clone(),
|
|
||||||
server_selector.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server_info.message.map(|server_message| {
|
message.map(|server_message| {
|
||||||
DocumentationAside::new(
|
DocumentationAside::new(
|
||||||
DocumentationSide::Right,
|
DocumentationSide::Right,
|
||||||
Rc::new(move |_| Label::new(server_message.clone()).into_any_element()),
|
Rc::new(move |_| Label::new(server_message.clone()).into_any_element()),
|
||||||
|
@ -345,81 +418,95 @@ impl LanguageServers {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ServerData<'a> {
|
enum ServerData<'a> {
|
||||||
WithHealthCheck(
|
WithHealthCheck {
|
||||||
LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
&'a LanguageServerHealthStatus,
|
health: &'a LanguageServerHealthStatus,
|
||||||
Option<&'a LanguageServerBinaryStatus>,
|
binary_status: Option<&'a LanguageServerBinaryStatus>,
|
||||||
),
|
},
|
||||||
WithBinaryStatus(
|
WithBinaryStatus {
|
||||||
Option<LanguageServerId>,
|
server_id: Option<LanguageServerId>,
|
||||||
&'a LanguageServerName,
|
server_name: &'a LanguageServerName,
|
||||||
&'a LanguageServerBinaryStatus,
|
binary_status: &'a LanguageServerBinaryStatus,
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum LspItem {
|
|
||||||
WithHealthCheck(
|
|
||||||
LanguageServerId,
|
|
||||||
LanguageServerHealthStatus,
|
|
||||||
Option<LanguageServerBinaryStatus>,
|
|
||||||
),
|
|
||||||
WithBinaryStatus(
|
|
||||||
Option<LanguageServerId>,
|
|
||||||
LanguageServerName,
|
|
||||||
LanguageServerBinaryStatus,
|
|
||||||
),
|
|
||||||
ToggleServersButton {
|
|
||||||
restart: bool,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LspItem {
|
#[derive(Debug)]
|
||||||
|
enum LspMenuItem {
|
||||||
|
WithHealthCheck {
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
health: LanguageServerHealthStatus,
|
||||||
|
binary_status: Option<LanguageServerBinaryStatus>,
|
||||||
|
},
|
||||||
|
WithBinaryStatus {
|
||||||
|
server_id: Option<LanguageServerId>,
|
||||||
|
server_name: LanguageServerName,
|
||||||
|
binary_status: LanguageServerBinaryStatus,
|
||||||
|
},
|
||||||
|
ToggleServersButton {
|
||||||
|
restart: bool,
|
||||||
|
},
|
||||||
|
Header {
|
||||||
|
header: Option<SharedString>,
|
||||||
|
separator: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspMenuItem {
|
||||||
fn server_info(&self) -> Option<ServerInfo> {
|
fn server_info(&self) -> Option<ServerInfo> {
|
||||||
match self {
|
match self {
|
||||||
LspItem::ToggleServersButton { .. } => None,
|
Self::Header { .. } => None,
|
||||||
LspItem::WithHealthCheck(
|
Self::ToggleServersButton { .. } => None,
|
||||||
language_server_id,
|
Self::WithHealthCheck {
|
||||||
language_server_health_status,
|
|
||||||
language_server_binary_status,
|
|
||||||
) => Some(ServerInfo {
|
|
||||||
name: language_server_health_status.name.clone(),
|
|
||||||
id: Some(*language_server_id),
|
|
||||||
health: language_server_health_status.health(),
|
|
||||||
binary_status: language_server_binary_status.clone(),
|
|
||||||
message: language_server_health_status.message(),
|
|
||||||
}),
|
|
||||||
LspItem::WithBinaryStatus(
|
|
||||||
server_id,
|
server_id,
|
||||||
language_server_name,
|
health,
|
||||||
language_server_binary_status,
|
binary_status,
|
||||||
) => Some(ServerInfo {
|
..
|
||||||
name: language_server_name.clone(),
|
} => Some(ServerInfo {
|
||||||
|
name: health.name.clone(),
|
||||||
|
id: Some(*server_id),
|
||||||
|
health: health.health(),
|
||||||
|
binary_status: binary_status.clone(),
|
||||||
|
message: health.message(),
|
||||||
|
}),
|
||||||
|
Self::WithBinaryStatus {
|
||||||
|
server_id,
|
||||||
|
server_name,
|
||||||
|
binary_status,
|
||||||
|
..
|
||||||
|
} => Some(ServerInfo {
|
||||||
|
name: server_name.clone(),
|
||||||
id: *server_id,
|
id: *server_id,
|
||||||
health: None,
|
health: None,
|
||||||
binary_status: Some(language_server_binary_status.clone()),
|
binary_status: Some(binary_status.clone()),
|
||||||
message: language_server_binary_status.message.clone(),
|
message: binary_status.message.clone(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerData<'_> {
|
impl ServerData<'_> {
|
||||||
fn name(&self) -> &LanguageServerName {
|
fn into_lsp_item(self) -> LspMenuItem {
|
||||||
match self {
|
match self {
|
||||||
Self::WithHealthCheck(_, state, _) => &state.name,
|
Self::WithHealthCheck {
|
||||||
Self::WithBinaryStatus(_, name, ..) => name,
|
server_id,
|
||||||
}
|
health,
|
||||||
}
|
binary_status,
|
||||||
|
..
|
||||||
fn into_lsp_item(self) -> LspItem {
|
} => LspMenuItem::WithHealthCheck {
|
||||||
match self {
|
server_id,
|
||||||
Self::WithHealthCheck(id, name, status) => {
|
health: health.clone(),
|
||||||
LspItem::WithHealthCheck(id, name.clone(), status.cloned())
|
binary_status: binary_status.cloned(),
|
||||||
}
|
},
|
||||||
Self::WithBinaryStatus(server_id, name, status) => {
|
Self::WithBinaryStatus {
|
||||||
LspItem::WithBinaryStatus(server_id, name.clone(), status.clone())
|
server_id,
|
||||||
}
|
server_name,
|
||||||
|
binary_status,
|
||||||
|
..
|
||||||
|
} => LspMenuItem::WithBinaryStatus {
|
||||||
|
server_id,
|
||||||
|
server_name: server_name.clone(),
|
||||||
|
binary_status: binary_status.clone(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -452,7 +539,6 @@ impl LspTool {
|
||||||
let state = cx.new(|_| LanguageServerState {
|
let state = cx.new(|_| LanguageServerState {
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
other_servers_start_index: None,
|
|
||||||
lsp_store: lsp_store.downgrade(),
|
lsp_store: lsp_store.downgrade(),
|
||||||
active_editor: None,
|
active_editor: None,
|
||||||
language_servers: LanguageServers::default(),
|
language_servers: LanguageServers::default(),
|
||||||
|
@ -542,13 +628,28 @@ impl LspTool {
|
||||||
message: proto::update_language_server::Variant::RegisteredForBuffer(update),
|
message: proto::update_language_server::Variant::RegisteredForBuffer(update),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
self.server_state.update(cx, |state, _| {
|
self.server_state.update(cx, |state, cx| {
|
||||||
state
|
let Ok(worktree) = state.workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.project()
|
||||||
|
.read(cx)
|
||||||
|
.find_worktree(Path::new(&update.buffer_abs_path), cx)
|
||||||
|
.map(|(worktree, _)| worktree.downgrade())
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let entry = state
|
||||||
.language_servers
|
.language_servers
|
||||||
.servers_per_buffer_abs_path
|
.servers_per_buffer_abs_path
|
||||||
.entry(PathBuf::from(&update.buffer_abs_path))
|
.entry(PathBuf::from(&update.buffer_abs_path))
|
||||||
.or_default()
|
.or_insert_with(|| ServersForPath {
|
||||||
.insert(*language_server_id, name.clone());
|
servers: HashMap::default(),
|
||||||
|
worktree: worktree.clone(),
|
||||||
|
});
|
||||||
|
entry.servers.insert(*language_server_id, name.clone());
|
||||||
|
if worktree.is_some() {
|
||||||
|
entry.worktree = worktree;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
@ -562,94 +663,95 @@ impl LspTool {
|
||||||
|
|
||||||
fn regenerate_items(&mut self, cx: &mut App) {
|
fn regenerate_items(&mut self, cx: &mut App) {
|
||||||
self.server_state.update(cx, |state, cx| {
|
self.server_state.update(cx, |state, cx| {
|
||||||
let editor_buffers = state
|
let active_worktrees = state
|
||||||
.active_editor
|
.active_editor
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|active_editor| active_editor.editor_buffers.clone())
|
.into_iter()
|
||||||
.unwrap_or_default();
|
.flat_map(|active_editor| {
|
||||||
let editor_buffer_paths = editor_buffers
|
active_editor
|
||||||
.iter()
|
.editor
|
||||||
.filter_map(|buffer_id| {
|
.upgrade()
|
||||||
let buffer_path = state
|
.into_iter()
|
||||||
.lsp_store
|
.flat_map(|active_editor| {
|
||||||
.update(cx, |lsp_store, cx| {
|
active_editor
|
||||||
Some(
|
.read(cx)
|
||||||
project::File::from_dyn(
|
.buffer()
|
||||||
lsp_store
|
.read(cx)
|
||||||
.buffer_store()
|
.all_buffers()
|
||||||
.read(cx)
|
.into_iter()
|
||||||
.get(*buffer_id)?
|
.filter_map(|buffer| {
|
||||||
.read(cx)
|
project::File::from_dyn(buffer.read(cx).file())
|
||||||
.file(),
|
})
|
||||||
)?
|
.map(|buffer_file| buffer_file.worktree.clone())
|
||||||
.abs_path(cx),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.ok()??;
|
|
||||||
Some(buffer_path)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
let mut servers_with_health_checks = HashSet::default();
|
let mut server_ids_to_worktrees =
|
||||||
let mut server_ids_with_health_checks = HashSet::default();
|
HashMap::<LanguageServerId, Entity<Worktree>>::default();
|
||||||
let mut buffer_servers =
|
let mut server_names_to_worktrees = HashMap::<
|
||||||
Vec::with_capacity(state.language_servers.health_statuses.len());
|
LanguageServerName,
|
||||||
let mut other_servers =
|
HashSet<(Entity<Worktree>, LanguageServerId)>,
|
||||||
Vec::with_capacity(state.language_servers.health_statuses.len());
|
>::default();
|
||||||
let buffer_server_ids = editor_buffer_paths
|
for servers_for_path in state.language_servers.servers_per_buffer_abs_path.values() {
|
||||||
.iter()
|
if let Some(worktree) = servers_for_path
|
||||||
.filter_map(|buffer_path| {
|
.worktree
|
||||||
state
|
.as_ref()
|
||||||
.language_servers
|
.and_then(|worktree| worktree.upgrade())
|
||||||
.servers_per_buffer_abs_path
|
{
|
||||||
.get(buffer_path)
|
for (server_id, server_name) in &servers_for_path.servers {
|
||||||
})
|
server_ids_to_worktrees.insert(*server_id, worktree.clone());
|
||||||
.flatten()
|
if let Some(server_name) = server_name {
|
||||||
.fold(HashMap::default(), |mut acc, (server_id, name)| {
|
server_names_to_worktrees
|
||||||
match acc.entry(*server_id) {
|
.entry(server_name.clone())
|
||||||
hash_map::Entry::Occupied(mut o) => {
|
.or_default()
|
||||||
let old_name: &mut Option<&LanguageServerName> = o.get_mut();
|
.insert((worktree.clone(), *server_id));
|
||||||
if old_name.is_none() {
|
|
||||||
*old_name = name.as_ref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash_map::Entry::Vacant(v) => {
|
|
||||||
v.insert(name.as_ref());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
acc
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut servers_per_worktree = BTreeMap::<SharedString, Vec<ServerData>>::new();
|
||||||
|
let mut servers_without_worktree = Vec::<ServerData>::new();
|
||||||
|
let mut servers_with_health_checks = HashSet::default();
|
||||||
|
|
||||||
|
for (server_id, health) in &state.language_servers.health_statuses {
|
||||||
|
let worktree = server_ids_to_worktrees.get(server_id).or_else(|| {
|
||||||
|
let worktrees = server_names_to_worktrees.get(&health.name)?;
|
||||||
|
worktrees
|
||||||
|
.iter()
|
||||||
|
.find(|(worktree, _)| active_worktrees.contains(worktree))
|
||||||
|
.or_else(|| worktrees.iter().next())
|
||||||
|
.map(|(worktree, _)| worktree)
|
||||||
});
|
});
|
||||||
for (server_id, server_state) in &state.language_servers.health_statuses {
|
servers_with_health_checks.insert(&health.name);
|
||||||
let binary_status = state
|
let worktree_name =
|
||||||
.language_servers
|
worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name()));
|
||||||
.binary_statuses
|
|
||||||
.get(&server_state.name);
|
let binary_status = state.language_servers.binary_statuses.get(&health.name);
|
||||||
servers_with_health_checks.insert(&server_state.name);
|
let server_data = ServerData::WithHealthCheck {
|
||||||
server_ids_with_health_checks.insert(*server_id);
|
server_id: *server_id,
|
||||||
if buffer_server_ids.contains_key(server_id) {
|
health,
|
||||||
buffer_servers.push(ServerData::WithHealthCheck(
|
binary_status,
|
||||||
*server_id,
|
};
|
||||||
server_state,
|
match worktree_name {
|
||||||
binary_status,
|
Some(worktree_name) => servers_per_worktree
|
||||||
));
|
.entry(worktree_name.clone())
|
||||||
} else {
|
.or_default()
|
||||||
other_servers.push(ServerData::WithHealthCheck(
|
.push(server_data),
|
||||||
*server_id,
|
None => servers_without_worktree.push(server_data),
|
||||||
server_state,
|
|
||||||
binary_status,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut can_stop_all = !state.language_servers.health_statuses.is_empty();
|
let mut can_stop_all = !state.language_servers.health_statuses.is_empty();
|
||||||
let mut can_restart_all = state.language_servers.health_statuses.is_empty();
|
let mut can_restart_all = state.language_servers.health_statuses.is_empty();
|
||||||
for (server_name, status) in state
|
for (server_name, binary_status) in state
|
||||||
.language_servers
|
.language_servers
|
||||||
.binary_statuses
|
.binary_statuses
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(name, _)| !servers_with_health_checks.contains(name))
|
.filter(|(name, _)| !servers_with_health_checks.contains(name))
|
||||||
{
|
{
|
||||||
match status.status {
|
match binary_status.status {
|
||||||
BinaryStatus::None => {
|
BinaryStatus::None => {
|
||||||
can_restart_all = false;
|
can_restart_all = false;
|
||||||
can_stop_all |= true;
|
can_stop_all |= true;
|
||||||
|
@ -674,52 +776,73 @@ impl LspTool {
|
||||||
BinaryStatus::Failed { .. } => {}
|
BinaryStatus::Failed { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let matching_server_id = state
|
match server_names_to_worktrees.get(server_name) {
|
||||||
.language_servers
|
Some(worktrees_for_name) => {
|
||||||
.servers_per_buffer_abs_path
|
match worktrees_for_name
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(path, _)| editor_buffer_paths.contains(path))
|
.find(|(worktree, _)| active_worktrees.contains(worktree))
|
||||||
.flat_map(|(_, server_associations)| server_associations.iter())
|
.or_else(|| worktrees_for_name.iter().next())
|
||||||
.find_map(|(id, name)| {
|
{
|
||||||
if name.as_ref() == Some(server_name) {
|
Some((worktree, server_id)) => {
|
||||||
Some(*id)
|
let worktree_name =
|
||||||
} else {
|
SharedString::new(worktree.read(cx).root_name());
|
||||||
None
|
servers_per_worktree
|
||||||
|
.entry(worktree_name.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(ServerData::WithBinaryStatus {
|
||||||
|
server_name,
|
||||||
|
binary_status,
|
||||||
|
server_id: Some(*server_id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => servers_without_worktree.push(ServerData::WithBinaryStatus {
|
||||||
|
server_name,
|
||||||
|
binary_status,
|
||||||
|
server_id: None,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if let Some(server_id) = matching_server_id {
|
None => servers_without_worktree.push(ServerData::WithBinaryStatus {
|
||||||
buffer_servers.push(ServerData::WithBinaryStatus(
|
|
||||||
Some(server_id),
|
|
||||||
server_name,
|
server_name,
|
||||||
status,
|
binary_status,
|
||||||
));
|
server_id: None,
|
||||||
} else {
|
}),
|
||||||
other_servers.push(ServerData::WithBinaryStatus(None, server_name, status));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_servers.sort_by_key(|data| data.name().clone());
|
|
||||||
other_servers.sort_by_key(|data| data.name().clone());
|
|
||||||
|
|
||||||
let mut other_servers_start_index = None;
|
|
||||||
let mut new_lsp_items =
|
let mut new_lsp_items =
|
||||||
Vec::with_capacity(buffer_servers.len() + other_servers.len() + 1);
|
Vec::with_capacity(servers_per_worktree.len() + servers_without_worktree.len() + 2);
|
||||||
new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item));
|
for (worktree_name, worktree_servers) in servers_per_worktree {
|
||||||
if !new_lsp_items.is_empty() {
|
if worktree_servers.is_empty() {
|
||||||
other_servers_start_index = Some(new_lsp_items.len());
|
continue;
|
||||||
|
}
|
||||||
|
new_lsp_items.push(LspMenuItem::Header {
|
||||||
|
header: Some(worktree_name),
|
||||||
|
separator: false,
|
||||||
|
});
|
||||||
|
new_lsp_items.extend(worktree_servers.into_iter().map(ServerData::into_lsp_item));
|
||||||
|
}
|
||||||
|
if !servers_without_worktree.is_empty() {
|
||||||
|
new_lsp_items.push(LspMenuItem::Header {
|
||||||
|
header: Some(SharedString::from("Unknown worktree")),
|
||||||
|
separator: false,
|
||||||
|
});
|
||||||
|
new_lsp_items.extend(
|
||||||
|
servers_without_worktree
|
||||||
|
.into_iter()
|
||||||
|
.map(ServerData::into_lsp_item),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item));
|
|
||||||
if !new_lsp_items.is_empty() {
|
if !new_lsp_items.is_empty() {
|
||||||
if can_stop_all {
|
if can_stop_all {
|
||||||
new_lsp_items.push(LspItem::ToggleServersButton { restart: true });
|
new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: true });
|
||||||
new_lsp_items.push(LspItem::ToggleServersButton { restart: false });
|
new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: false });
|
||||||
} else if can_restart_all {
|
} else if can_restart_all {
|
||||||
new_lsp_items.push(LspItem::ToggleServersButton { restart: true });
|
new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.items = new_lsp_items;
|
state.items = new_lsp_items;
|
||||||
state.other_servers_start_index = other_servers_start_index;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -841,10 +964,7 @@ impl StatusItemView for LspTool {
|
||||||
|
|
||||||
impl Render for LspTool {
|
impl Render for LspTool {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||||
if !cx.is_staff()
|
if self.server_state.read(cx).language_servers.is_empty() || self.lsp_menu.is_none() {
|
||||||
|| self.server_state.read(cx).language_servers.is_empty()
|
|
||||||
|| self.lsp_menu.is_none()
|
|
||||||
{
|
|
||||||
return div();
|
return div();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,12 +972,12 @@ impl Render for LspTool {
|
||||||
let mut has_warnings = false;
|
let mut has_warnings = false;
|
||||||
let mut has_other_notifications = false;
|
let mut has_other_notifications = false;
|
||||||
let state = self.server_state.read(cx);
|
let state = self.server_state.read(cx);
|
||||||
for server in state.language_servers.health_statuses.values() {
|
for binary_status in state.language_servers.binary_statuses.values() {
|
||||||
if let Some(binary_status) = &state.language_servers.binary_statuses.get(&server.name) {
|
has_errors |= matches!(binary_status.status, BinaryStatus::Failed { .. });
|
||||||
has_errors |= matches!(binary_status.status, BinaryStatus::Failed { .. });
|
has_other_notifications |= binary_status.message.is_some();
|
||||||
has_other_notifications |= binary_status.message.is_some();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
for server in state.language_servers.health_statuses.values() {
|
||||||
if let Some((message, health)) = &server.health {
|
if let Some((message, health)) = &server.health {
|
||||||
has_other_notifications |= message.is_some();
|
has_other_notifications |= message.is_some();
|
||||||
match health {
|
match health {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue