lsp_tool.rs

  1use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration};
  2
  3use client::proto;
  4use collections::{HashMap, HashSet};
  5use editor::{Editor, EditorEvent};
  6use feature_flags::FeatureFlagAppExt as _;
  7use gpui::{
  8    Corner, DismissEvent, Entity, Focusable as _, MouseButton, Subscription, Task, WeakEntity,
  9    actions,
 10};
 11use language::{BinaryStatus, BufferId, LocalFile, ServerHealth};
 12use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
 13use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu};
 14use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings};
 15use settings::{Settings as _, SettingsStore};
 16use ui::{Context, Indicator, PopoverMenuHandle, Tooltip, Window, prelude::*};
 17
 18use workspace::{StatusItemView, Workspace};
 19
 20use crate::lsp_log::GlobalLogStore;
 21
 22actions!(
 23    lsp_tool,
 24    [
 25        /// Toggles the language server tool menu.
 26        ToggleMenu
 27    ]
 28);
 29
 30pub struct LspTool {
 31    state: Entity<PickerState>,
 32    popover_menu_handle: PopoverMenuHandle<Picker<LspPickerDelegate>>,
 33    lsp_picker: Option<Entity<Picker<LspPickerDelegate>>>,
 34    _subscriptions: Vec<Subscription>,
 35}
 36
 37struct PickerState {
 38    workspace: WeakEntity<Workspace>,
 39    lsp_store: WeakEntity<LspStore>,
 40    active_editor: Option<ActiveEditor>,
 41    language_servers: LanguageServers,
 42}
 43
 44#[derive(Debug)]
 45pub struct LspPickerDelegate {
 46    state: Entity<PickerState>,
 47    selected_index: usize,
 48    items: Vec<LspItem>,
 49    other_servers_start_index: Option<usize>,
 50}
 51
 52struct ActiveEditor {
 53    editor: WeakEntity<Editor>,
 54    _editor_subscription: Subscription,
 55    editor_buffers: HashSet<BufferId>,
 56}
 57
 58#[derive(Debug, Default, Clone)]
 59struct LanguageServers {
 60    health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
 61    binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
 62    servers_per_buffer_abs_path:
 63        HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>,
 64}
 65
 66#[derive(Debug, Clone)]
 67struct LanguageServerHealthStatus {
 68    name: LanguageServerName,
 69    health: Option<(Option<SharedString>, ServerHealth)>,
 70}
 71
 72#[derive(Debug, Clone)]
 73struct LanguageServerBinaryStatus {
 74    status: BinaryStatus,
 75    message: Option<SharedString>,
 76}
 77
 78#[derive(Debug)]
 79struct ServerInfo {
 80    name: LanguageServerName,
 81    id: Option<LanguageServerId>,
 82    health: Option<ServerHealth>,
 83    binary_status: Option<LanguageServerBinaryStatus>,
 84    message: Option<SharedString>,
 85}
 86
 87impl ServerInfo {
 88    fn server_selector(&self) -> LanguageServerSelector {
 89        self.id
 90            .map(LanguageServerSelector::Id)
 91            .unwrap_or_else(|| LanguageServerSelector::Name(self.name.clone()))
 92    }
 93}
 94
 95impl LanguageServerHealthStatus {
 96    fn health(&self) -> Option<ServerHealth> {
 97        self.health.as_ref().map(|(_, health)| *health)
 98    }
 99
100    fn message(&self) -> Option<SharedString> {
101        self.health
102            .as_ref()
103            .and_then(|(message, _)| message.clone())
104    }
105}
106
107impl LspPickerDelegate {
108    fn regenerate_items(&mut self, cx: &mut Context<Picker<Self>>) {
109        self.state.update(cx, |state, cx| {
110            let editor_buffers = state
111                .active_editor
112                .as_ref()
113                .map(|active_editor| active_editor.editor_buffers.clone())
114                .unwrap_or_default();
115            let editor_buffer_paths = editor_buffers
116                .iter()
117                .filter_map(|buffer_id| {
118                    let buffer_path = state
119                        .lsp_store
120                        .update(cx, |lsp_store, cx| {
121                            Some(
122                                project::File::from_dyn(
123                                    lsp_store
124                                        .buffer_store()
125                                        .read(cx)
126                                        .get(*buffer_id)?
127                                        .read(cx)
128                                        .file(),
129                                )?
130                                .abs_path(cx),
131                            )
132                        })
133                        .ok()??;
134                    Some(buffer_path)
135                })
136                .collect::<Vec<_>>();
137
138            let mut servers_with_health_checks = HashSet::default();
139            let mut server_ids_with_health_checks = HashSet::default();
140            let mut buffer_servers =
141                Vec::with_capacity(state.language_servers.health_statuses.len());
142            let mut other_servers =
143                Vec::with_capacity(state.language_servers.health_statuses.len());
144            let buffer_server_ids = editor_buffer_paths
145                .iter()
146                .filter_map(|buffer_path| {
147                    state
148                        .language_servers
149                        .servers_per_buffer_abs_path
150                        .get(buffer_path)
151                })
152                .flatten()
153                .fold(HashMap::default(), |mut acc, (server_id, name)| {
154                    match acc.entry(*server_id) {
155                        hash_map::Entry::Occupied(mut o) => {
156                            let old_name: &mut Option<&LanguageServerName> = o.get_mut();
157                            if old_name.is_none() {
158                                *old_name = name.as_ref();
159                            }
160                        }
161                        hash_map::Entry::Vacant(v) => {
162                            v.insert(name.as_ref());
163                        }
164                    }
165                    acc
166                });
167            for (server_id, server_state) in &state.language_servers.health_statuses {
168                let binary_status = state
169                    .language_servers
170                    .binary_statuses
171                    .get(&server_state.name);
172                servers_with_health_checks.insert(&server_state.name);
173                server_ids_with_health_checks.insert(*server_id);
174                if buffer_server_ids.contains_key(server_id) {
175                    buffer_servers.push(ServerData::WithHealthCheck(
176                        *server_id,
177                        server_state,
178                        binary_status,
179                    ));
180                } else {
181                    other_servers.push(ServerData::WithHealthCheck(
182                        *server_id,
183                        server_state,
184                        binary_status,
185                    ));
186                }
187            }
188
189            let mut can_stop_all = false;
190            let mut can_restart_all = true;
191
192            for (server_name, status) in state
193                .language_servers
194                .binary_statuses
195                .iter()
196                .filter(|(name, _)| !servers_with_health_checks.contains(name))
197            {
198                match status.status {
199                    BinaryStatus::None => {
200                        can_restart_all = false;
201                        can_stop_all = true;
202                    }
203                    BinaryStatus::CheckingForUpdate => {
204                        can_restart_all = false;
205                    }
206                    BinaryStatus::Downloading => {
207                        can_restart_all = false;
208                    }
209                    BinaryStatus::Starting => {
210                        can_restart_all = false;
211                    }
212                    BinaryStatus::Stopping => {
213                        can_restart_all = false;
214                    }
215                    BinaryStatus::Stopped => {}
216                    BinaryStatus::Failed { .. } => {}
217                }
218
219                let matching_server_id = state
220                    .language_servers
221                    .servers_per_buffer_abs_path
222                    .iter()
223                    .filter(|(path, _)| editor_buffer_paths.contains(path))
224                    .flat_map(|(_, server_associations)| server_associations.iter())
225                    .find_map(|(id, name)| {
226                        if name.as_ref() == Some(server_name) {
227                            Some(*id)
228                        } else {
229                            None
230                        }
231                    });
232                if let Some(server_id) = matching_server_id {
233                    buffer_servers.push(ServerData::WithBinaryStatus(
234                        Some(server_id),
235                        server_name,
236                        status,
237                    ));
238                } else {
239                    other_servers.push(ServerData::WithBinaryStatus(None, server_name, status));
240                }
241            }
242
243            buffer_servers.sort_by_key(|data| data.name().clone());
244            other_servers.sort_by_key(|data| data.name().clone());
245
246            let mut other_servers_start_index = None;
247            let mut new_lsp_items =
248                Vec::with_capacity(buffer_servers.len() + other_servers.len() + 1);
249            new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item));
250            if !new_lsp_items.is_empty() {
251                other_servers_start_index = Some(new_lsp_items.len());
252            }
253            new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item));
254            if !new_lsp_items.is_empty() {
255                if can_stop_all {
256                    new_lsp_items.push(LspItem::ToggleServersButton { restart: false });
257                } else if can_restart_all {
258                    new_lsp_items.push(LspItem::ToggleServersButton { restart: true });
259                }
260            }
261
262            self.items = new_lsp_items;
263            self.other_servers_start_index = other_servers_start_index;
264        });
265    }
266
267    fn server_info(&self, ix: usize) -> Option<ServerInfo> {
268        match self.items.get(ix)? {
269            LspItem::ToggleServersButton { .. } => None,
270            LspItem::WithHealthCheck(
271                language_server_id,
272                language_server_health_status,
273                language_server_binary_status,
274            ) => Some(ServerInfo {
275                name: language_server_health_status.name.clone(),
276                id: Some(*language_server_id),
277                health: language_server_health_status.health(),
278                binary_status: language_server_binary_status.clone(),
279                message: language_server_health_status.message(),
280            }),
281            LspItem::WithBinaryStatus(
282                server_id,
283                language_server_name,
284                language_server_binary_status,
285            ) => Some(ServerInfo {
286                name: language_server_name.clone(),
287                id: *server_id,
288                health: None,
289                binary_status: Some(language_server_binary_status.clone()),
290                message: language_server_binary_status.message.clone(),
291            }),
292        }
293    }
294}
295
296impl LanguageServers {
297    fn update_binary_status(
298        &mut self,
299        binary_status: BinaryStatus,
300        message: Option<&str>,
301        name: LanguageServerName,
302    ) {
303        let binary_status_message = message.map(SharedString::new);
304        if matches!(
305            binary_status,
306            BinaryStatus::Stopped | BinaryStatus::Failed { .. }
307        ) {
308            self.health_statuses.retain(|_, server| server.name != name);
309        }
310        self.binary_statuses.insert(
311            name,
312            LanguageServerBinaryStatus {
313                status: binary_status,
314                message: binary_status_message,
315            },
316        );
317    }
318
319    fn update_server_health(
320        &mut self,
321        id: LanguageServerId,
322        health: ServerHealth,
323        message: Option<&str>,
324        name: Option<LanguageServerName>,
325    ) {
326        if let Some(state) = self.health_statuses.get_mut(&id) {
327            state.health = Some((message.map(SharedString::new), health));
328            if let Some(name) = name {
329                state.name = name;
330            }
331        } else if let Some(name) = name {
332            self.health_statuses.insert(
333                id,
334                LanguageServerHealthStatus {
335                    health: Some((message.map(SharedString::new), health)),
336                    name,
337                },
338            );
339        }
340    }
341
342    fn is_empty(&self) -> bool {
343        self.binary_statuses.is_empty() && self.health_statuses.is_empty()
344    }
345}
346
347#[derive(Debug)]
348enum ServerData<'a> {
349    WithHealthCheck(
350        LanguageServerId,
351        &'a LanguageServerHealthStatus,
352        Option<&'a LanguageServerBinaryStatus>,
353    ),
354    WithBinaryStatus(
355        Option<LanguageServerId>,
356        &'a LanguageServerName,
357        &'a LanguageServerBinaryStatus,
358    ),
359}
360
361#[derive(Debug)]
362enum LspItem {
363    WithHealthCheck(
364        LanguageServerId,
365        LanguageServerHealthStatus,
366        Option<LanguageServerBinaryStatus>,
367    ),
368    WithBinaryStatus(
369        Option<LanguageServerId>,
370        LanguageServerName,
371        LanguageServerBinaryStatus,
372    ),
373    ToggleServersButton {
374        restart: bool,
375    },
376}
377
378impl ServerData<'_> {
379    fn name(&self) -> &LanguageServerName {
380        match self {
381            Self::WithHealthCheck(_, state, _) => &state.name,
382            Self::WithBinaryStatus(_, name, ..) => name,
383        }
384    }
385
386    fn into_lsp_item(self) -> LspItem {
387        match self {
388            Self::WithHealthCheck(id, name, status) => {
389                LspItem::WithHealthCheck(id, name.clone(), status.cloned())
390            }
391            Self::WithBinaryStatus(server_id, name, status) => {
392                LspItem::WithBinaryStatus(server_id, name.clone(), status.clone())
393            }
394        }
395    }
396}
397
398impl PickerDelegate for LspPickerDelegate {
399    type ListItem = AnyElement;
400
401    fn match_count(&self) -> usize {
402        self.items.len()
403    }
404
405    fn selected_index(&self) -> usize {
406        self.selected_index
407    }
408
409    fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
410        self.selected_index = ix;
411        cx.notify();
412    }
413
414    fn update_matches(
415        &mut self,
416        _: String,
417        _: &mut Window,
418        cx: &mut Context<Picker<Self>>,
419    ) -> Task<()> {
420        cx.spawn(async move |lsp_picker, cx| {
421            cx.background_executor()
422                .timer(Duration::from_millis(30))
423                .await;
424            lsp_picker
425                .update(cx, |lsp_picker, cx| {
426                    lsp_picker.delegate.regenerate_items(cx);
427                })
428                .ok();
429        })
430    }
431
432    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
433        Arc::default()
434    }
435
436    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
437        if let Some(LspItem::ToggleServersButton { restart }) = self.items.get(self.selected_index)
438        {
439            let lsp_store = self.state.read(cx).lsp_store.clone();
440            lsp_store
441                .update(cx, |lsp_store, cx| {
442                    if *restart {
443                        let Some(workspace) = self.state.read(cx).workspace.upgrade() else {
444                            return;
445                        };
446                        let project = workspace.read(cx).project().clone();
447                        let buffer_store = project.read(cx).buffer_store().clone();
448                        let worktree_store = project.read(cx).worktree_store();
449
450                        let buffers = self
451                            .state
452                            .read(cx)
453                            .language_servers
454                            .servers_per_buffer_abs_path
455                            .keys()
456                            .filter_map(|abs_path| {
457                                worktree_store.read(cx).find_worktree(abs_path, cx)
458                            })
459                            .filter_map(|(worktree, relative_path)| {
460                                let entry = worktree.read(cx).entry_for_path(&relative_path)?;
461                                project.read(cx).path_for_entry(entry.id, cx)
462                            })
463                            .filter_map(|project_path| {
464                                buffer_store.read(cx).get_by_path(&project_path)
465                            })
466                            .collect();
467                        let selectors = self
468                            .items
469                            .iter()
470                            // Do not try to use IDs as we have stopped all servers already, when allowing to restart them all
471                            .flat_map(|item| match item {
472                                LspItem::ToggleServersButton { .. } => None,
473                                LspItem::WithHealthCheck(_, status, ..) => {
474                                    Some(LanguageServerSelector::Name(status.name.clone()))
475                                }
476                                LspItem::WithBinaryStatus(_, server_name, ..) => {
477                                    Some(LanguageServerSelector::Name(server_name.clone()))
478                                }
479                            })
480                            .collect();
481                        lsp_store.restart_language_servers_for_buffers(buffers, selectors, cx);
482                    } else {
483                        lsp_store.stop_all_language_servers(cx);
484                    }
485                })
486                .ok();
487        }
488
489        let Some(server_selector) = self
490            .server_info(self.selected_index)
491            .map(|info| info.server_selector())
492        else {
493            return;
494        };
495        let lsp_logs = cx.global::<GlobalLogStore>().0.clone();
496        let lsp_store = self.state.read(cx).lsp_store.clone();
497        let workspace = self.state.read(cx).workspace.clone();
498        lsp_logs
499            .update(cx, |lsp_logs, cx| {
500                let has_logs = lsp_store
501                    .update(cx, |lsp_store, _| {
502                        lsp_store.as_local().is_some() && lsp_logs.has_server_logs(&server_selector)
503                    })
504                    .unwrap_or(false);
505                if has_logs {
506                    lsp_logs.open_server_trace(workspace, server_selector, window, cx);
507                }
508            })
509            .ok();
510    }
511
512    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
513        cx.emit(DismissEvent);
514    }
515
516    fn render_match(
517        &self,
518        ix: usize,
519        selected: bool,
520        _: &mut Window,
521        cx: &mut Context<Picker<Self>>,
522    ) -> Option<Self::ListItem> {
523        let rendered_match = h_flex().px_1().gap_1();
524        let rendered_match_contents = h_flex()
525            .id(("lsp-item", ix))
526            .w_full()
527            .px_2()
528            .gap_2()
529            .when(selected, |server_entry| {
530                server_entry.bg(cx.theme().colors().element_hover)
531            })
532            .hover(|s| s.bg(cx.theme().colors().element_hover));
533
534        if let Some(LspItem::ToggleServersButton { restart }) = self.items.get(ix) {
535            let label = Label::new(if *restart {
536                "Restart All Servers"
537            } else {
538                "Stop All Servers"
539            });
540            return Some(
541                rendered_match
542                    .child(rendered_match_contents.child(label))
543                    .into_any_element(),
544            );
545        }
546
547        let server_info = self.server_info(ix)?;
548        let workspace = self.state.read(cx).workspace.clone();
549        let lsp_logs = cx.global::<GlobalLogStore>().0.upgrade()?;
550        let lsp_store = self.state.read(cx).lsp_store.upgrade()?;
551        let server_selector = server_info.server_selector();
552
553        // TODO currently, Zed remote does not work well with the LSP logs
554        // https://github.com/zed-industries/zed/issues/28557
555        let has_logs = lsp_store.read(cx).as_local().is_some()
556            && lsp_logs.read(cx).has_server_logs(&server_selector);
557
558        let status_color = server_info
559            .binary_status
560            .and_then(|binary_status| match binary_status.status {
561                BinaryStatus::None => None,
562                BinaryStatus::CheckingForUpdate
563                | BinaryStatus::Downloading
564                | BinaryStatus::Starting => Some(Color::Modified),
565                BinaryStatus::Stopping => Some(Color::Disabled),
566                BinaryStatus::Stopped => Some(Color::Disabled),
567                BinaryStatus::Failed { .. } => Some(Color::Error),
568            })
569            .or_else(|| {
570                Some(match server_info.health? {
571                    ServerHealth::Ok => Color::Success,
572                    ServerHealth::Warning => Color::Warning,
573                    ServerHealth::Error => Color::Error,
574                })
575            })
576            .unwrap_or(Color::Success);
577
578        Some(
579            rendered_match
580                .child(
581                    rendered_match_contents
582                        .child(Indicator::dot().color(status_color))
583                        .child(Label::new(server_info.name.0.clone()))
584                        .when_some(
585                            server_info.message.clone(),
586                            |server_entry, server_message| {
587                                server_entry.tooltip(Tooltip::text(server_message.clone()))
588                            },
589                        ),
590                )
591                .when_else(
592                    has_logs,
593                    |server_entry| {
594                        server_entry.on_mouse_down(MouseButton::Left, {
595                            let workspace = workspace.clone();
596                            let lsp_logs = lsp_logs.downgrade();
597                            let server_selector = server_selector.clone();
598                            move |_, window, cx| {
599                                lsp_logs
600                                    .update(cx, |lsp_logs, cx| {
601                                        lsp_logs.open_server_trace(
602                                            workspace.clone(),
603                                            server_selector.clone(),
604                                            window,
605                                            cx,
606                                        );
607                                    })
608                                    .ok();
609                            }
610                        })
611                    },
612                    |div| div.cursor_default(),
613                )
614                .into_any_element(),
615        )
616    }
617
618    fn render_editor(
619        &self,
620        editor: &Entity<Editor>,
621        _: &mut Window,
622        cx: &mut Context<Picker<Self>>,
623    ) -> Div {
624        div().child(div().track_focus(&editor.focus_handle(cx)))
625    }
626
627    fn separators_after_indices(&self) -> Vec<usize> {
628        if self.items.is_empty() {
629            return Vec::new();
630        }
631        let mut indices = vec![self.items.len().saturating_sub(2)];
632        if let Some(other_servers_start_index) = self.other_servers_start_index {
633            if other_servers_start_index > 0 {
634                indices.insert(0, other_servers_start_index - 1);
635                indices.dedup();
636            }
637        }
638        indices
639    }
640}
641
642impl LspTool {
643    pub fn new(
644        workspace: &Workspace,
645        popover_menu_handle: PopoverMenuHandle<Picker<LspPickerDelegate>>,
646        window: &mut Window,
647        cx: &mut Context<Self>,
648    ) -> Self {
649        let settings_subscription =
650            cx.observe_global_in::<SettingsStore>(window, move |lsp_tool, window, cx| {
651                if ProjectSettings::get_global(cx).global_lsp_settings.button {
652                    if lsp_tool.lsp_picker.is_none() {
653                        lsp_tool.lsp_picker =
654                            Some(Self::new_lsp_picker(lsp_tool.state.clone(), window, cx));
655                        cx.notify();
656                        return;
657                    }
658                } else if lsp_tool.lsp_picker.take().is_some() {
659                    cx.notify();
660                }
661            });
662
663        let lsp_store = workspace.project().read(cx).lsp_store();
664        let lsp_store_subscription =
665            cx.subscribe_in(&lsp_store, window, |lsp_tool, _, e, window, cx| {
666                lsp_tool.on_lsp_store_event(e, window, cx)
667            });
668
669        let state = cx.new(|_| PickerState {
670            workspace: workspace.weak_handle(),
671            lsp_store: lsp_store.downgrade(),
672            active_editor: None,
673            language_servers: LanguageServers::default(),
674        });
675
676        Self {
677            state,
678            popover_menu_handle,
679            lsp_picker: None,
680            _subscriptions: vec![settings_subscription, lsp_store_subscription],
681        }
682    }
683
684    fn on_lsp_store_event(
685        &mut self,
686        e: &LspStoreEvent,
687        window: &mut Window,
688        cx: &mut Context<Self>,
689    ) {
690        let Some(lsp_picker) = self.lsp_picker.clone() else {
691            return;
692        };
693        let mut updated = false;
694
695        match e {
696            LspStoreEvent::LanguageServerUpdate {
697                language_server_id,
698                name,
699                message: proto::update_language_server::Variant::StatusUpdate(status_update),
700            } => match &status_update.status {
701                Some(proto::status_update::Status::Binary(binary_status)) => {
702                    let Some(name) = name.as_ref() else {
703                        return;
704                    };
705                    if let Some(binary_status) = proto::ServerBinaryStatus::from_i32(*binary_status)
706                    {
707                        let binary_status = match binary_status {
708                            proto::ServerBinaryStatus::None => BinaryStatus::None,
709                            proto::ServerBinaryStatus::CheckingForUpdate => {
710                                BinaryStatus::CheckingForUpdate
711                            }
712                            proto::ServerBinaryStatus::Downloading => BinaryStatus::Downloading,
713                            proto::ServerBinaryStatus::Starting => BinaryStatus::Starting,
714                            proto::ServerBinaryStatus::Stopping => BinaryStatus::Stopping,
715                            proto::ServerBinaryStatus::Stopped => BinaryStatus::Stopped,
716                            proto::ServerBinaryStatus::Failed => {
717                                let Some(error) = status_update.message.clone() else {
718                                    return;
719                                };
720                                BinaryStatus::Failed { error }
721                            }
722                        };
723                        self.state.update(cx, |state, _| {
724                            state.language_servers.update_binary_status(
725                                binary_status,
726                                status_update.message.as_deref(),
727                                name.clone(),
728                            );
729                        });
730                        updated = true;
731                    };
732                }
733                Some(proto::status_update::Status::Health(health_status)) => {
734                    if let Some(health) = proto::ServerHealth::from_i32(*health_status) {
735                        let health = match health {
736                            proto::ServerHealth::Ok => ServerHealth::Ok,
737                            proto::ServerHealth::Warning => ServerHealth::Warning,
738                            proto::ServerHealth::Error => ServerHealth::Error,
739                        };
740                        self.state.update(cx, |state, _| {
741                            state.language_servers.update_server_health(
742                                *language_server_id,
743                                health,
744                                status_update.message.as_deref(),
745                                name.clone(),
746                            );
747                        });
748                        updated = true;
749                    }
750                }
751                None => {}
752            },
753            LspStoreEvent::LanguageServerUpdate {
754                language_server_id,
755                name,
756                message: proto::update_language_server::Variant::RegisteredForBuffer(update),
757                ..
758            } => {
759                self.state.update(cx, |state, _| {
760                    state
761                        .language_servers
762                        .servers_per_buffer_abs_path
763                        .entry(PathBuf::from(&update.buffer_abs_path))
764                        .or_default()
765                        .insert(*language_server_id, name.clone());
766                });
767                updated = true;
768            }
769            _ => {}
770        };
771
772        if updated {
773            lsp_picker.update(cx, |lsp_picker, cx| {
774                lsp_picker.refresh(window, cx);
775            });
776        }
777    }
778
779    fn new_lsp_picker(
780        state: Entity<PickerState>,
781        window: &mut Window,
782        cx: &mut Context<Self>,
783    ) -> Entity<Picker<LspPickerDelegate>> {
784        cx.new(|cx| {
785            let mut delegate = LspPickerDelegate {
786                selected_index: 0,
787                other_servers_start_index: None,
788                items: Vec::new(),
789                state,
790            };
791            delegate.regenerate_items(cx);
792            Picker::list(delegate, window, cx)
793        })
794    }
795}
796
797impl StatusItemView for LspTool {
798    fn set_active_pane_item(
799        &mut self,
800        active_pane_item: Option<&dyn workspace::ItemHandle>,
801        window: &mut Window,
802        cx: &mut Context<Self>,
803    ) {
804        if ProjectSettings::get_global(cx).global_lsp_settings.button {
805            if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
806                if Some(&editor)
807                    != self
808                        .state
809                        .read(cx)
810                        .active_editor
811                        .as_ref()
812                        .and_then(|active_editor| active_editor.editor.upgrade())
813                        .as_ref()
814                {
815                    let editor_buffers =
816                        HashSet::from_iter(editor.read(cx).buffer().read(cx).excerpt_buffer_ids());
817                    let _editor_subscription = cx.subscribe_in(
818                        &editor,
819                        window,
820                        |lsp_tool, _, e: &EditorEvent, window, cx| match e {
821                            EditorEvent::ExcerptsAdded { buffer, .. } => {
822                                lsp_tool.state.update(cx, |state, cx| {
823                                    if let Some(active_editor) = state.active_editor.as_mut() {
824                                        let buffer_id = buffer.read(cx).remote_id();
825                                        if active_editor.editor_buffers.insert(buffer_id) {
826                                            if let Some(picker) = &lsp_tool.lsp_picker {
827                                                picker.update(cx, |picker, cx| {
828                                                    picker.refresh(window, cx)
829                                                });
830                                            }
831                                        }
832                                    }
833                                });
834                            }
835                            EditorEvent::ExcerptsRemoved {
836                                removed_buffer_ids, ..
837                            } => {
838                                lsp_tool.state.update(cx, |state, cx| {
839                                    if let Some(active_editor) = state.active_editor.as_mut() {
840                                        let mut removed = false;
841                                        for id in removed_buffer_ids {
842                                            active_editor.editor_buffers.retain(|buffer_id| {
843                                                let retain = buffer_id != id;
844                                                removed |= !retain;
845                                                retain
846                                            });
847                                        }
848                                        if removed {
849                                            if let Some(picker) = &lsp_tool.lsp_picker {
850                                                picker.update(cx, |picker, cx| {
851                                                    picker.refresh(window, cx)
852                                                });
853                                            }
854                                        }
855                                    }
856                                });
857                            }
858                            _ => {}
859                        },
860                    );
861                    self.state.update(cx, |state, _| {
862                        state.active_editor = Some(ActiveEditor {
863                            editor: editor.downgrade(),
864                            _editor_subscription,
865                            editor_buffers,
866                        });
867                    });
868
869                    let lsp_picker = Self::new_lsp_picker(self.state.clone(), window, cx);
870                    self.lsp_picker = Some(lsp_picker.clone());
871                    lsp_picker.update(cx, |lsp_picker, cx| lsp_picker.refresh(window, cx));
872                }
873            } else if self.state.read(cx).active_editor.is_some() {
874                self.state.update(cx, |state, _| {
875                    state.active_editor = None;
876                });
877                if let Some(lsp_picker) = self.lsp_picker.as_ref() {
878                    lsp_picker.update(cx, |lsp_picker, cx| {
879                        lsp_picker.refresh(window, cx);
880                    });
881                };
882            }
883        } else if self.state.read(cx).active_editor.is_some() {
884            self.state.update(cx, |state, _| {
885                state.active_editor = None;
886            });
887            if let Some(lsp_picker) = self.lsp_picker.as_ref() {
888                lsp_picker.update(cx, |lsp_picker, cx| {
889                    lsp_picker.refresh(window, cx);
890                });
891            }
892        }
893    }
894}
895
896impl Render for LspTool {
897    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
898        if !cx.is_staff() || self.state.read(cx).language_servers.is_empty() {
899            return div();
900        }
901
902        let Some(lsp_picker) = self.lsp_picker.clone() else {
903            return div();
904        };
905
906        let mut has_errors = false;
907        let mut has_warnings = false;
908        let mut has_other_notifications = false;
909        let state = self.state.read(cx);
910        for server in state.language_servers.health_statuses.values() {
911            if let Some(binary_status) = &state.language_servers.binary_statuses.get(&server.name) {
912                has_errors |= matches!(binary_status.status, BinaryStatus::Failed { .. });
913                has_other_notifications |= binary_status.message.is_some();
914            }
915
916            if let Some((message, health)) = &server.health {
917                has_other_notifications |= message.is_some();
918                match health {
919                    ServerHealth::Ok => {}
920                    ServerHealth::Warning => has_warnings = true,
921                    ServerHealth::Error => has_errors = true,
922                }
923            }
924        }
925
926        let indicator = if has_errors {
927            Some(Indicator::dot().color(Color::Error))
928        } else if has_warnings {
929            Some(Indicator::dot().color(Color::Warning))
930        } else if has_other_notifications {
931            Some(Indicator::dot().color(Color::Modified))
932        } else {
933            None
934        };
935
936        div().child(
937            PickerPopoverMenu::new(
938                lsp_picker.clone(),
939                IconButton::new("zed-lsp-tool-button", IconName::BoltFilledAlt)
940                    .when_some(indicator, IconButton::indicator)
941                    .icon_size(IconSize::Small)
942                    .indicator_border_color(Some(cx.theme().colors().status_bar_background)),
943                move |window, cx| Tooltip::for_action("Language Servers", &ToggleMenu, window, cx),
944                Corner::BottomLeft,
945                cx,
946            )
947            .with_handle(self.popover_menu_handle.clone())
948            .render(window, cx),
949        )
950    }
951}