use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration}; use client::proto;use collections::{HashMap, HashSet};use editor::{Editor, EditorEvent};use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions};use language::{BinaryStatus, BufferId, LocalFile, ServerHealth};use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu};use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings};use settings::{Settings as _, SettingsStore};use ui::{Context, IconButtonShape, Indicator, Tooltip, Window, prelude::*}; use workspace::{StatusItemView, Workspace}; use crate::lsp_log::GlobalLogStore; actions!(lsp_tool, [ToggleMenu]); pub struct LspTool { state: Entity<PickerState>, lsp_picker: Option<Entity<Picker<LspPickerDelegate>>>, _subscriptions: Vec<Subscription>,} struct PickerState { workspace: WeakEntity<Workspace>, lsp_store: WeakEntity<LspStore>, active_editor: Option<ActiveEditor>, language_servers: LanguageServers,} #[derive(Debug)]struct LspPickerDelegate { state: Entity<PickerState>, selected_index: usize, items: Vec<LspItem>, other_servers_start_index: Option<usize>,} struct ActiveEditor { editor: WeakEntity<Editor>, _editor_subscription: Subscription, editor_buffers: HashSet<BufferId>,} #[derive(Debug, Default, Clone)]struct LanguageServers { health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>, binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>, servers_per_buffer_abs_path: HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>,} #[derive(Debug, Clone)]struct LanguageServerHealthStatus { name: LanguageServerName, health: Option<(Option<SharedString>, ServerHealth)>,} #[derive(Debug, Clone)]struct LanguageServerBinaryStatus { status: BinaryStatus, message: Option<SharedString>,} impl LanguageServerHealthStatus { fn health(&self) -> Option<ServerHealth> { self.health.as_ref().map(|(_, health)| *health) } fn message(&self) -> Option<SharedString> { self.health .as_ref() .and_then(|(message, _)| message.clone()) }} impl LspPickerDelegate { fn regenerate_items(&mut self, cx: &mut Context<Picker<Self>>) { self.state.update(cx, |state, cx| { let editor_buffers = state .active_editor .as_ref() .map(|active_editor| active_editor.editor_buffers.clone()) .unwrap_or_default(); let editor_buffer_paths = editor_buffers .iter() .filter_map(|buffer_id| { let buffer_path = state .lsp_store .update(cx, |lsp_store, cx| { Some( project::File::from_dyn( lsp_store .buffer_store() .read(cx) .get(*buffer_id)? .read(cx) .file(), )? .abs_path(cx), ) }) .ok()??; Some(buffer_path) }) .collect::<Vec<_>>(); let mut servers_with_health_checks = HashSet::default(); let mut server_ids_with_health_checks = HashSet::default(); let mut buffer_servers = Vec::with_capacity(state.language_servers.health_statuses.len()); let mut other_servers = Vec::with_capacity(state.language_servers.health_statuses.len()); let buffer_server_ids = editor_buffer_paths .iter() .filter_map(|buffer_path| { state .language_servers .servers_per_buffer_abs_path .get(buffer_path) }) .flatten() .fold(HashMap::default(), |mut acc, (server_id, name)| { match acc.entry(*server_id) { hash_map::Entry::Occupied(mut o) => { let old_name: &mut Option<&LanguageServerName> = o.get_mut(); if old_name.is_none() { *old_name = name.as_ref(); } } hash_map::Entry::Vacant(v) => { v.insert(name.as_ref()); } } acc }); for (server_id, server_state) in &state.language_servers.health_statuses { let binary_status = state .language_servers .binary_statuses .get(&server_state.name); servers_with_health_checks.insert(&server_state.name); server_ids_with_health_checks.insert(*server_id); if buffer_server_ids.contains_key(server_id) { buffer_servers.push(ServerData::WithHealthCheck( *server_id, server_state, binary_status, )); } else { other_servers.push(ServerData::WithHealthCheck( *server_id, server_state, binary_status, )); } } for (server_name, status) in state .language_servers .binary_statuses .iter() .filter(|(name, _)| !servers_with_health_checks.contains(name)) { let has_matching_server = state .language_servers .servers_per_buffer_abs_path .iter() .filter(|(path, _)| editor_buffer_paths.contains(path)) .flat_map(|(_, server_associations)| server_associations.iter()) .any(|(_, name)| name.as_ref() == Some(server_name)); if has_matching_server { buffer_servers.push(ServerData::WithBinaryStatus(server_name, status)); } else { other_servers.push(ServerData::WithBinaryStatus(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 = Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2); if !buffer_servers.is_empty() { new_lsp_items.push(LspItem::Header(SharedString::new("Current Buffer"))); new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item)); } if !other_servers.is_empty() { other_servers_start_index = Some(new_lsp_items.len()); new_lsp_items.push(LspItem::Header(SharedString::new("Other Active Servers"))); new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item)); } self.items = new_lsp_items; self.other_servers_start_index = other_servers_start_index; }); }} impl LanguageServers { fn update_binary_status( &mut self, binary_status: BinaryStatus, message: Option<&str>, name: LanguageServerName, ) { let binary_status_message = message.map(SharedString::new); if matches!( binary_status, BinaryStatus::Stopped | BinaryStatus::Failed { .. } ) { self.health_statuses.retain(|_, server| server.name != name); } self.binary_statuses.insert( name, LanguageServerBinaryStatus { status: binary_status, message: binary_status_message, }, ); } fn update_server_health( &mut self, id: LanguageServerId, health: ServerHealth, message: Option<&str>, name: Option<LanguageServerName>, ) { if let Some(state) = self.health_statuses.get_mut(&id) { state.health = Some((message.map(SharedString::new), health)); if let Some(name) = name { state.name = name; } } else if let Some(name) = name { self.health_statuses.insert( id, LanguageServerHealthStatus { health: Some((message.map(SharedString::new), health)), name, }, ); } }} #[derive(Debug)]enum ServerData<'a> { WithHealthCheck( LanguageServerId, &'a LanguageServerHealthStatus, Option<&'a LanguageServerBinaryStatus>, ), WithBinaryStatus(&'a LanguageServerName, &'a LanguageServerBinaryStatus),} #[derive(Debug)]enum LspItem { WithHealthCheck( LanguageServerId, LanguageServerHealthStatus, Option<LanguageServerBinaryStatus>, ), WithBinaryStatus(LanguageServerName, LanguageServerBinaryStatus), Header(SharedString),} impl ServerData<'_> { fn name(&self) -> &LanguageServerName { match self { Self::WithHealthCheck(_, state, _) => &state.name, Self::WithBinaryStatus(name, ..) => name, } } fn into_lsp_item(self) -> LspItem { match self { Self::WithHealthCheck(id, name, status) => { LspItem::WithHealthCheck(id, name.clone(), status.cloned()) } Self::WithBinaryStatus(name, status) => { LspItem::WithBinaryStatus(name.clone(), status.clone()) } } }} impl PickerDelegate for LspPickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.items.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) { self.selected_index = ix; cx.notify(); } fn update_matches( &mut self, _: String, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { cx.spawn(async move |lsp_picker, cx| { cx.background_executor() .timer(Duration::from_millis(30)) .await; lsp_picker .update(cx, |lsp_picker, cx| { lsp_picker.delegate.regenerate_items(cx); }) .ok(); }) } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { Arc::default() } fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {} fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) { cx.emit(DismissEvent); } fn render_match( &self, ix: usize, _: bool, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { let is_other_server = self .other_servers_start_index .map_or(false, |start| ix >= start); let server_binary_status; let server_health; let server_message; let server_id; let server_name; match self.items.get(ix)? { LspItem::WithHealthCheck( language_server_id, language_server_health_status, language_server_binary_status, ) => { server_binary_status = language_server_binary_status.as_ref(); server_health = language_server_health_status.health(); server_message = language_server_health_status.message(); server_id = Some(*language_server_id); server_name = language_server_health_status.name.clone(); } LspItem::WithBinaryStatus(language_server_name, language_server_binary_status) => { server_binary_status = Some(language_server_binary_status); server_health = None; server_message = language_server_binary_status.message.clone(); server_id = None; server_name = language_server_name.clone(); } LspItem::Header(header) => { return Some( h_flex() .justify_center() .child(Label::new(header.clone())) .into_any_element(), ); } }; let workspace = self.state.read(cx).workspace.clone(); let lsp_logs = cx.global::<GlobalLogStore>().0.upgrade()?; let lsp_store = self.state.read(cx).lsp_store.upgrade()?; let server_selector = server_id .map(LanguageServerSelector::Id) .unwrap_or_else(|| LanguageServerSelector::Name(server_name.clone())); let can_stop = server_binary_status.is_none_or(|status| { matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) }); // TODO currently, Zed remote does not work well with the LSP logs // https://github.com/zed-industries/zed/issues/28557 let has_logs = lsp_store.read(cx).as_local().is_some() && lsp_logs.read(cx).has_server_logs(&server_selector); let status_color = server_binary_status .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, BinaryStatus::CheckingForUpdate | BinaryStatus::Downloading | BinaryStatus::Starting => Some(Color::Modified), BinaryStatus::Stopping => Some(Color::Disabled), BinaryStatus::Stopped => Some(Color::Disabled), BinaryStatus::Failed { .. } => Some(Color::Error), }) .or_else(|| { Some(match server_health? { ServerHealth::Ok => Color::Success, ServerHealth::Warning => Color::Warning, ServerHealth::Error => Color::Error, }) }) .unwrap_or(Color::Success); Some( h_flex() .w_full() .justify_between() .gap_2() .child( h_flex() .id("server-status-indicator") .gap_2() .child(Indicator::dot().color(status_color)) .child(Label::new(server_name.0.clone())) .when_some(server_message.clone(), |div, server_message| { div.tooltip(move |_, cx| Tooltip::simple(server_message.clone(), cx)) }), ) .child( h_flex() .gap_1() .when(has_logs, |div| { div.child( IconButton::new("debug-language-server", IconName::MessageBubbles) .icon_size(IconSize::XSmall) .tooltip(|_, cx| Tooltip::simple("Debug Language Server", cx)) .on_click({ let workspace = workspace.clone(); let lsp_logs = lsp_logs.downgrade(); let server_selector = server_selector.clone(); move |_, window, cx| { lsp_logs .update(cx, |lsp_logs, cx| { lsp_logs.open_server_trace( workspace.clone(), server_selector.clone(), window, cx, ); }) .ok(); } }), ) }) .when(can_stop, |div| { div.child( IconButton::new("stop-server", IconName::Stop) .icon_size(IconSize::Small) .tooltip(|_, cx| Tooltip::simple("Stop server", cx)) .on_click({ let lsp_store = lsp_store.downgrade(); let server_selector = server_selector.clone(); move |_, _, cx| { lsp_store .update(cx, |lsp_store, cx| { lsp_store.stop_language_servers_for_buffers( Vec::new(), HashSet::from_iter([ server_selector.clone() ]), cx, ); }) .ok(); } }), ) }) .child( IconButton::new("restart-server", IconName::Rerun) .icon_size(IconSize::XSmall) .tooltip(|_, cx| Tooltip::simple("Restart server", cx)) .on_click({ let state = self.state.clone(); let workspace = workspace.clone(); let lsp_store = lsp_store.downgrade(); let editor_buffers = state .read(cx) .active_editor .as_ref() .map(|active_editor| active_editor.editor_buffers.clone()) .unwrap_or_default(); let server_selector = server_selector.clone(); move |_, _, cx| { if let Some(workspace) = workspace.upgrade() { let project = workspace.read(cx).project().clone(); let buffer_store = project.read(cx).buffer_store().clone(); let buffers = if is_other_server { let worktree_store = project.read(cx).worktree_store(); state .read(cx)Lines 1–500 of 917. Load more
use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration}; use client::proto; use collections::{HashMap, HashSet}; use editor::{Editor, EditorEvent}; use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions}; use language::{BinaryStatus, BufferId, LocalFile, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu}; use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings}; use settings::{Settings as _, SettingsStore}; use ui::{Context, IconButtonShape, Indicator, Tooltip, Window, prelude::*}; use workspace::{StatusItemView, Workspace}; use crate::lsp_log::GlobalLogStore; actions!(lsp_tool, [ToggleMenu]); pub struct LspTool { state: Entity<PickerState>, lsp_picker: Option<Entity<Picker<LspPickerDelegate>>>, _subscriptions: Vec<Subscription>, } struct PickerState { workspace: WeakEntity<Workspace>, lsp_store: WeakEntity<LspStore>, active_editor: Option<ActiveEditor>, language_servers: LanguageServers, } #[derive(Debug)] struct LspPickerDelegate { state: Entity<PickerState>, selected_index: usize, items: Vec<LspItem>, other_servers_start_index: Option<usize>, } struct ActiveEditor { editor: WeakEntity<Editor>, _editor_subscription: Subscription, editor_buffers: HashSet<BufferId>, } #[derive(Debug, Default, Clone)] struct LanguageServers { health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>, binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>, servers_per_buffer_abs_path: HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>, } #[derive(Debug, Clone)] struct LanguageServerHealthStatus { name: LanguageServerName, health: Option<(Option<SharedString>, ServerHealth)>, } #[derive(Debug, Clone)] struct LanguageServerBinaryStatus { status: BinaryStatus, message: Option<SharedString>, } impl LanguageServerHealthStatus { fn health(&self) -> Option<ServerHealth> { self.health.as_ref().map(|(_, health)| *health) } fn message(&self) -> Option<SharedString> { self.health .as_ref() .and_then(|(message, _)| message.clone()) } } impl LspPickerDelegate { fn regenerate_items(&mut self, cx: &mut Context<Picker<Self>>) { self.state.update(cx, |state, cx| { let editor_buffers = state .active_editor .as_ref() .map(|active_editor| active_editor.editor_buffers.clone()) .unwrap_or_default(); let editor_buffer_paths = editor_buffers .iter() .filter_map(|buffer_id| { let buffer_path = state .lsp_store .update(cx, |lsp_store, cx| { Some( project::File::from_dyn( lsp_store .buffer_store() .read(cx) .get(*buffer_id)? .read(cx) .file(), )? .abs_path(cx), ) }) .ok()??; Some(buffer_path) }) .collect::<Vec<_>>(); let mut servers_with_health_checks = HashSet::default(); let mut server_ids_with_health_checks = HashSet::default(); let mut buffer_servers = Vec::with_capacity(state.language_servers.health_statuses.len()); let mut other_servers = Vec::with_capacity(state.language_servers.health_statuses.len()); let buffer_server_ids = editor_buffer_paths .iter() .filter_map(|buffer_path| { state .language_servers .servers_per_buffer_abs_path .get(buffer_path) }) .flatten() .fold(HashMap::default(), |mut acc, (server_id, name)| { match acc.entry(*server_id) { hash_map::Entry::Occupied(mut o) => { let old_name: &mut Option<&LanguageServerName> = o.get_mut(); if old_name.is_none() { *old_name = name.as_ref(); } } hash_map::Entry::Vacant(v) => { v.insert(name.as_ref()); } } acc }); for (server_id, server_state) in &state.language_servers.health_statuses { let binary_status = state .language_servers .binary_statuses .get(&server_state.name); servers_with_health_checks.insert(&server_state.name); server_ids_with_health_checks.insert(*server_id); if buffer_server_ids.contains_key(server_id) { buffer_servers.push(ServerData::WithHealthCheck( *server_id, server_state, binary_status, )); } else { other_servers.push(ServerData::WithHealthCheck( *server_id, server_state, binary_status, )); } } for (server_name, status) in state .language_servers .binary_statuses .iter() .filter(|(name, _)| !servers_with_health_checks.contains(name)) { let has_matching_server = state .language_servers .servers_per_buffer_abs_path .iter() .filter(|(path, _)| editor_buffer_paths.contains(path)) .flat_map(|(_, server_associations)| server_associations.iter()) .any(|(_, name)| name.as_ref() == Some(server_name)); if has_matching_server { buffer_servers.push(ServerData::WithBinaryStatus(server_name, status)); } else { other_servers.push(ServerData::WithBinaryStatus(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 = Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2); if !buffer_servers.is_empty() { new_lsp_items.push(LspItem::Header(SharedString::new("Current Buffer"))); new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item)); } if !other_servers.is_empty() { other_servers_start_index = Some(new_lsp_items.len()); new_lsp_items.push(LspItem::Header(SharedString::new("Other Active Servers"))); new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item)); } self.items = new_lsp_items; self.other_servers_start_index = other_servers_start_index; }); } } impl LanguageServers { fn update_binary_status( &mut self, binary_status: BinaryStatus, message: Option<&str>, name: LanguageServerName, ) { let binary_status_message = message.map(SharedString::new); if matches!( binary_status, BinaryStatus::Stopped | BinaryStatus::Failed { .. } ) { self.health_statuses.retain(|_, server| server.name != name); } self.binary_statuses.insert( name, LanguageServerBinaryStatus { status: binary_status, message: binary_status_message, }, ); } fn update_server_health( &mut self, id: LanguageServerId, health: ServerHealth, message: Option<&str>, name: Option<LanguageServerName>, ) { if let Some(state) = self.health_statuses.get_mut(&id) { state.health = Some((message.map(SharedString::new), health)); if let Some(name) = name { state.name = name; } } else if let Some(name) = name { self.health_statuses.insert( id, LanguageServerHealthStatus { health: Some((message.map(SharedString::new), health)), name, }, ); } } } #[derive(Debug)] enum ServerData<'a> { WithHealthCheck( LanguageServerId, &'a LanguageServerHealthStatus, Option<&'a LanguageServerBinaryStatus>, ), WithBinaryStatus(&'a LanguageServerName, &'a LanguageServerBinaryStatus), } #[derive(Debug)] enum LspItem { WithHealthCheck( LanguageServerId, LanguageServerHealthStatus, Option<LanguageServerBinaryStatus>, ), WithBinaryStatus(LanguageServerName, LanguageServerBinaryStatus), Header(SharedString), } impl ServerData<'_> { fn name(&self) -> &LanguageServerName { match self { Self::WithHealthCheck(_, state, _) => &state.name, Self::WithBinaryStatus(name, ..) => name, } } fn into_lsp_item(self) -> LspItem { match self { Self::WithHealthCheck(id, name, status) => { LspItem::WithHealthCheck(id, name.clone(), status.cloned()) } Self::WithBinaryStatus(name, status) => { LspItem::WithBinaryStatus(name.clone(), status.clone()) } } } } impl PickerDelegate for LspPickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.items.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) { self.selected_index = ix; cx.notify(); } fn update_matches( &mut self, _: String, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { cx.spawn(async move |lsp_picker, cx| { cx.background_executor() .timer(Duration::from_millis(30)) .await; lsp_picker .update(cx, |lsp_picker, cx| { lsp_picker.delegate.regenerate_items(cx); }) .ok(); }) } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { Arc::default() } fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {} fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) { cx.emit(DismissEvent); } fn render_match( &self, ix: usize, _: bool, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { let is_other_server = self .other_servers_start_index .map_or(false, |start| ix >= start); let server_binary_status; let server_health; let server_message; let server_id; let server_name; match self.items.get(ix)? { LspItem::WithHealthCheck( language_server_id, language_server_health_status, language_server_binary_status, ) => { server_binary_status = language_server_binary_status.as_ref(); server_health = language_server_health_status.health(); server_message = language_server_health_status.message(); server_id = Some(*language_server_id); server_name = language_server_health_status.name.clone(); } LspItem::WithBinaryStatus(language_server_name, language_server_binary_status) => { server_binary_status = Some(language_server_binary_status); server_health = None; server_message = language_server_binary_status.message.clone(); server_id = None; server_name = language_server_name.clone(); } LspItem::Header(header) => { return Some( h_flex() .justify_center() .child(Label::new(header.clone())) .into_any_element(), ); } }; let workspace = self.state.read(cx).workspace.clone(); let lsp_logs = cx.global::<GlobalLogStore>().0.upgrade()?; let lsp_store = self.state.read(cx).lsp_store.upgrade()?; let server_selector = server_id .map(LanguageServerSelector::Id) .unwrap_or_else(|| LanguageServerSelector::Name(server_name.clone())); let can_stop = server_binary_status.is_none_or(|status| { matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) }); // TODO currently, Zed remote does not work well with the LSP logs // https://github.com/zed-industries/zed/issues/28557 let has_logs = lsp_store.read(cx).as_local().is_some() && lsp_logs.read(cx).has_server_logs(&server_selector); let status_color = server_binary_status .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, BinaryStatus::CheckingForUpdate | BinaryStatus::Downloading | BinaryStatus::Starting => Some(Color::Modified), BinaryStatus::Stopping => Some(Color::Disabled), BinaryStatus::Stopped => Some(Color::Disabled), BinaryStatus::Failed { .. } => Some(Color::Error), }) .or_else(|| { Some(match server_health? { ServerHealth::Ok => Color::Success, ServerHealth::Warning => Color::Warning, ServerHealth::Error => Color::Error, }) }) .unwrap_or(Color::Success); Some( h_flex() .w_full() .justify_between() .gap_2() .child( h_flex() .id("server-status-indicator") .gap_2() .child(Indicator::dot().color(status_color)) .child(Label::new(server_name.0.clone())) .when_some(server_message.clone(), |div, server_message| { div.tooltip(move |_, cx| Tooltip::simple(server_message.clone(), cx)) }), ) .child( h_flex() .gap_1() .when(has_logs, |div| { div.child( IconButton::new("debug-language-server", IconName::MessageBubbles) .icon_size(IconSize::XSmall) .tooltip(|_, cx| Tooltip::simple("Debug Language Server", cx)) .on_click({ let workspace = workspace.clone(); let lsp_logs = lsp_logs.downgrade(); let server_selector = server_selector.clone(); move |_, window, cx| { lsp_logs .update(cx, |lsp_logs, cx| { lsp_logs.open_server_trace( workspace.clone(), server_selector.clone(), window, cx, ); }) .ok(); } }), ) }) .when(can_stop, |div| { div.child( IconButton::new("stop-server", IconName::Stop) .icon_size(IconSize::Small) .tooltip(|_, cx| Tooltip::simple("Stop server", cx)) .on_click({ let lsp_store = lsp_store.downgrade(); let server_selector = server_selector.clone(); move |_, _, cx| { lsp_store .update(cx, |lsp_store, cx| { lsp_store.stop_language_servers_for_buffers( Vec::new(), HashSet::from_iter([ server_selector.clone() ]), cx, ); }) .ok(); } }), ) }) .child( IconButton::new("restart-server", IconName::Rerun) .icon_size(IconSize::XSmall) .tooltip(|_, cx| Tooltip::simple("Restart server", cx)) .on_click({ let state = self.state.clone(); let workspace = workspace.clone(); let lsp_store = lsp_store.downgrade(); let editor_buffers = state .read(cx) .active_editor .as_ref() .map(|active_editor| active_editor.editor_buffers.clone()) .unwrap_or_default(); let server_selector = server_selector.clone(); move |_, _, cx| { if let Some(workspace) = workspace.upgrade() { let project = workspace.read(cx).project().clone(); let buffer_store = project.read(cx).buffer_store().clone(); let buffers = if is_other_server { let worktree_store = project.read(cx).worktree_store(); state .read(cx)