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