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:
Kirill Bulatov 2025-07-21 20:48:07 +03:00 committed by GitHub
parent b6cf398eab
commit 254c7a330a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 363 additions and 247 deletions

1
Cargo.lock generated
View file

@ -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",

View file

@ -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
})), })),

View file

@ -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

View file

@ -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 {