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