lsp_button.rs

   1use std::{
   2    collections::{BTreeMap, HashMap},
   3    path::{Path, PathBuf},
   4    rc::Rc,
   5    time::Duration,
   6};
   7
   8use client::proto;
   9use collections::HashSet;
  10use editor::{Editor, EditorEvent};
  11use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};
  12use language::{BinaryStatus, BufferId, ServerHealth};
  13use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
  14use project::{
  15    LspStore, LspStoreEvent, Worktree, lsp_store::log_store::GlobalLogStore,
  16    project_settings::ProjectSettings,
  17};
  18use settings::{Settings as _, SettingsStore};
  19use ui::{
  20    Context, ContextMenu, ContextMenuEntry, ContextMenuItem, DocumentationAside, DocumentationEdge,
  21    DocumentationSide, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, Window, prelude::*,
  22};
  23
  24use util::{ResultExt, rel_path::RelPath};
  25use workspace::{StatusItemView, Workspace};
  26
  27use crate::lsp_log_view;
  28
  29actions!(
  30    lsp_tool,
  31    [
  32        /// Toggles the language server tool menu.
  33        ToggleMenu
  34    ]
  35);
  36
  37pub struct LspButton {
  38    server_state: Entity<LanguageServerState>,
  39    popover_menu_handle: PopoverMenuHandle<ContextMenu>,
  40    lsp_menu: Option<Entity<ContextMenu>>,
  41    lsp_menu_refresh: Task<()>,
  42    _subscriptions: Vec<Subscription>,
  43}
  44
  45#[derive(Debug)]
  46struct LanguageServerState {
  47    items: Vec<LspMenuItem>,
  48    workspace: WeakEntity<Workspace>,
  49    lsp_store: WeakEntity<LspStore>,
  50    active_editor: Option<ActiveEditor>,
  51    language_servers: LanguageServers,
  52}
  53
  54struct ActiveEditor {
  55    editor: WeakEntity<Editor>,
  56    _editor_subscription: Subscription,
  57    editor_buffers: HashSet<BufferId>,
  58}
  59
  60impl std::fmt::Debug for ActiveEditor {
  61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  62        f.debug_struct("ActiveEditor")
  63            .field("editor", &self.editor)
  64            .field("editor_buffers", &self.editor_buffers)
  65            .finish_non_exhaustive()
  66    }
  67}
  68
  69#[derive(Debug, Default, Clone)]
  70struct LanguageServers {
  71    health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
  72    binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
  73    servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,
  74}
  75
  76#[derive(Debug, Clone)]
  77struct ServersForPath {
  78    servers: HashMap<LanguageServerId, Option<LanguageServerName>>,
  79    worktree: Option<WeakEntity<Worktree>>,
  80}
  81
  82#[derive(Debug, Clone)]
  83struct LanguageServerHealthStatus {
  84    name: LanguageServerName,
  85    health: Option<(Option<SharedString>, ServerHealth)>,
  86}
  87
  88#[derive(Debug, Clone)]
  89struct LanguageServerBinaryStatus {
  90    status: BinaryStatus,
  91    message: Option<SharedString>,
  92}
  93
  94#[derive(Debug)]
  95struct ServerInfo {
  96    name: LanguageServerName,
  97    id: Option<LanguageServerId>,
  98    health: Option<ServerHealth>,
  99    binary_status: Option<LanguageServerBinaryStatus>,
 100    message: Option<SharedString>,
 101}
 102
 103impl ServerInfo {
 104    fn server_selector(&self) -> LanguageServerSelector {
 105        self.id
 106            .map(LanguageServerSelector::Id)
 107            .unwrap_or_else(|| LanguageServerSelector::Name(self.name.clone()))
 108    }
 109}
 110
 111impl LanguageServerHealthStatus {
 112    fn health(&self) -> Option<ServerHealth> {
 113        self.health.as_ref().map(|(_, health)| *health)
 114    }
 115
 116    fn message(&self) -> Option<SharedString> {
 117        self.health
 118            .as_ref()
 119            .and_then(|(message, _)| message.clone())
 120    }
 121}
 122
 123impl LanguageServerState {
 124    fn fill_menu(&self, mut menu: ContextMenu, cx: &mut Context<Self>) -> ContextMenu {
 125        let lsp_logs = cx
 126            .try_global::<GlobalLogStore>()
 127            .map(|lsp_logs| lsp_logs.0.clone());
 128        let Some(lsp_logs) = lsp_logs else {
 129            return menu;
 130        };
 131
 132        let mut first_button_encountered = false;
 133        for item in &self.items {
 134            if let LspMenuItem::ToggleServersButton { restart } = item {
 135                let label = if *restart {
 136                    "Restart All Servers"
 137                } else {
 138                    "Stop All Servers"
 139                };
 140                let restart = *restart;
 141                let button = ContextMenuEntry::new(label).handler({
 142                    let state = cx.entity();
 143                    move |_, cx| {
 144                        let lsp_store = state.read(cx).lsp_store.clone();
 145                        lsp_store
 146                            .update(cx, |lsp_store, cx| {
 147                                if restart {
 148                                    let Some(workspace) = state.read(cx).workspace.upgrade() else {
 149                                        return;
 150                                    };
 151                                    let project = workspace.read(cx).project().clone();
 152                                    let path_style = project.read(cx).path_style(cx);
 153                                    let buffer_store = project.read(cx).buffer_store().clone();
 154                                    let buffers = state
 155                                        .read(cx)
 156                                        .language_servers
 157                                        .servers_per_buffer_abs_path
 158                                        .iter()
 159                                        .filter_map(|(abs_path, servers)| {
 160                                            let worktree =
 161                                                servers.worktree.as_ref()?.upgrade()?.read(cx);
 162                                            let relative_path =
 163                                                abs_path.strip_prefix(&worktree.abs_path()).ok()?;
 164                                            let relative_path =
 165                                                RelPath::new(relative_path, path_style)
 166                                                    .log_err()?;
 167                                            let entry = worktree.entry_for_path(&relative_path)?;
 168                                            let project_path =
 169                                                project.read(cx).path_for_entry(entry.id, cx)?;
 170                                            buffer_store.read(cx).get_by_path(&project_path)
 171                                        })
 172                                        .collect();
 173                                    let selectors = state
 174                                        .read(cx)
 175                                        .items
 176                                        .iter()
 177                                        // Do not try to use IDs as we have stopped all servers already, when allowing to restart them all
 178                                        .flat_map(|item| match item {
 179                                            LspMenuItem::Header { .. } => None,
 180                                            LspMenuItem::ToggleServersButton { .. } => None,
 181                                            LspMenuItem::WithHealthCheck { health, .. } => Some(
 182                                                LanguageServerSelector::Name(health.name.clone()),
 183                                            ),
 184                                            LspMenuItem::WithBinaryStatus {
 185                                                server_name, ..
 186                                            } => Some(LanguageServerSelector::Name(
 187                                                server_name.clone(),
 188                                            )),
 189                                        })
 190                                        .collect();
 191                                    lsp_store.restart_language_servers_for_buffers(
 192                                        buffers, selectors, cx,
 193                                    );
 194                                } else {
 195                                    lsp_store.stop_all_language_servers(cx);
 196                                }
 197                            })
 198                            .ok();
 199                    }
 200                });
 201                if !first_button_encountered {
 202                    menu = menu.separator();
 203                    first_button_encountered = true;
 204                }
 205                menu = menu.item(button);
 206                continue;
 207            } else if let LspMenuItem::Header { header, separator } = item {
 208                menu = menu
 209                    .when(*separator, |menu| menu.separator())
 210                    .when_some(header.as_ref(), |menu, header| menu.header(header));
 211                continue;
 212            }
 213
 214            let Some(server_info) = item.server_info() else {
 215                continue;
 216            };
 217
 218            let server_selector = server_info.server_selector();
 219            let is_remote = self
 220                .lsp_store
 221                .update(cx, |lsp_store, _| lsp_store.as_remote().is_some())
 222                .unwrap_or(false);
 223            let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector);
 224
 225            let status_color = server_info
 226                .binary_status
 227                .as_ref()
 228                .and_then(|binary_status| match binary_status.status {
 229                    BinaryStatus::None => None,
 230                    BinaryStatus::CheckingForUpdate
 231                    | BinaryStatus::Downloading
 232                    | BinaryStatus::Starting => Some(Color::Modified),
 233                    BinaryStatus::Stopping => Some(Color::Disabled),
 234                    BinaryStatus::Stopped => Some(Color::Disabled),
 235                    BinaryStatus::Failed { .. } => Some(Color::Error),
 236                })
 237                .or_else(|| {
 238                    Some(match server_info.health? {
 239                        ServerHealth::Ok => Color::Success,
 240                        ServerHealth::Warning => Color::Warning,
 241                        ServerHealth::Error => Color::Error,
 242                    })
 243                })
 244                .unwrap_or(Color::Success);
 245
 246            let message = server_info
 247                .message
 248                .as_ref()
 249                .or_else(|| server_info.binary_status.as_ref()?.message.as_ref())
 250                .cloned();
 251            let hover_label = if message.is_some() {
 252                Some("View Message")
 253            } else if has_logs {
 254                Some("View Logs")
 255            } else {
 256                None
 257            };
 258
 259            let server_name = server_info.name.clone();
 260            menu = menu.item(ContextMenuItem::custom_entry(
 261                move |_, _| {
 262                    h_flex()
 263                        .group("menu_item")
 264                        .w_full()
 265                        .gap_2()
 266                        .justify_between()
 267                        .child(
 268                            h_flex()
 269                                .gap_2()
 270                                .child(Indicator::dot().color(status_color))
 271                                .child(Label::new(server_name.0.clone())),
 272                        )
 273                        .when_some(hover_label, |div, hover_label| {
 274                            div.child(
 275                                h_flex()
 276                                    .visible_on_hover("menu_item")
 277                                    .child(
 278                                        Label::new(hover_label)
 279                                            .size(LabelSize::Small)
 280                                            .color(Color::Muted),
 281                                    )
 282                                    .child(
 283                                        Icon::new(IconName::ChevronRight)
 284                                            .size(IconSize::Small)
 285                                            .color(Color::Muted),
 286                                    ),
 287                            )
 288                        })
 289                        .into_any_element()
 290                },
 291                {
 292                    let lsp_logs = lsp_logs.clone();
 293                    let message = message.clone();
 294                    let server_selector = server_selector.clone();
 295                    let server_name = server_info.name.clone();
 296                    let workspace = self.workspace.clone();
 297                    move |window, cx| {
 298                        if let Some(message) = &message {
 299                            let Some(create_buffer) = workspace
 300                                .update(cx, |workspace, cx| {
 301                                    workspace
 302                                        .project()
 303                                        .update(cx, |project, cx| project.create_buffer(false, cx))
 304                                })
 305                                .ok()
 306                            else {
 307                                return;
 308                            };
 309
 310                            let window = window.window_handle();
 311                            let workspace = workspace.clone();
 312                            let message = message.clone();
 313                            let server_name = server_name.clone();
 314                            cx.spawn(async move |cx| {
 315                                let buffer = create_buffer.await?;
 316                                buffer.update(cx, |buffer, cx| {
 317                                    buffer.edit(
 318                                        [(
 319                                            0..0,
 320                                            format!("Language server {server_name}:\n\n{message}"),
 321                                        )],
 322                                        None,
 323                                        cx,
 324                                    );
 325                                    buffer.set_capability(language::Capability::ReadOnly, cx);
 326                                })?;
 327
 328                                workspace.update(cx, |workspace, cx| {
 329                                    window.update(cx, |_, window, cx| {
 330                                        workspace.add_item_to_active_pane(
 331                                            Box::new(cx.new(|cx| {
 332                                                let mut editor =
 333                                                    Editor::for_buffer(buffer, None, window, cx);
 334                                                editor.set_read_only(true);
 335                                                editor
 336                                            })),
 337                                            None,
 338                                            true,
 339                                            window,
 340                                            cx,
 341                                        );
 342                                    })
 343                                })??;
 344
 345                                anyhow::Ok(())
 346                            })
 347                            .detach();
 348                        } else if has_logs {
 349                            lsp_log_view::open_server_trace(
 350                                &lsp_logs,
 351                                workspace.clone(),
 352                                server_selector.clone(),
 353                                window,
 354                                cx,
 355                            );
 356                        } else {
 357                            cx.propagate();
 358                        }
 359                    }
 360                },
 361                message.map(|server_message| {
 362                    DocumentationAside::new(
 363                        DocumentationSide::Right,
 364                        DocumentationEdge::Bottom,
 365                        Rc::new(move |_| Label::new(server_message.clone()).into_any_element()),
 366                    )
 367                }),
 368            ));
 369        }
 370        menu
 371    }
 372}
 373
 374impl LanguageServers {
 375    fn update_binary_status(
 376        &mut self,
 377        binary_status: BinaryStatus,
 378        message: Option<&str>,
 379        name: LanguageServerName,
 380    ) {
 381        let binary_status_message = message.map(SharedString::new);
 382        if matches!(
 383            binary_status,
 384            BinaryStatus::Stopped | BinaryStatus::Failed { .. }
 385        ) {
 386            self.health_statuses.retain(|_, server| server.name != name);
 387        }
 388        self.binary_statuses.insert(
 389            name,
 390            LanguageServerBinaryStatus {
 391                status: binary_status,
 392                message: binary_status_message,
 393            },
 394        );
 395    }
 396
 397    fn update_server_health(
 398        &mut self,
 399        id: LanguageServerId,
 400        health: ServerHealth,
 401        message: Option<&str>,
 402        name: Option<LanguageServerName>,
 403    ) {
 404        if let Some(state) = self.health_statuses.get_mut(&id) {
 405            state.health = Some((message.map(SharedString::new), health));
 406            if let Some(name) = name {
 407                state.name = name;
 408            }
 409        } else if let Some(name) = name {
 410            self.health_statuses.insert(
 411                id,
 412                LanguageServerHealthStatus {
 413                    health: Some((message.map(SharedString::new), health)),
 414                    name,
 415                },
 416            );
 417        }
 418    }
 419
 420    fn is_empty(&self) -> bool {
 421        self.binary_statuses.is_empty() && self.health_statuses.is_empty()
 422    }
 423}
 424
 425#[derive(Debug)]
 426enum ServerData<'a> {
 427    WithHealthCheck {
 428        server_id: LanguageServerId,
 429        health: &'a LanguageServerHealthStatus,
 430        binary_status: Option<&'a LanguageServerBinaryStatus>,
 431    },
 432    WithBinaryStatus {
 433        server_id: Option<LanguageServerId>,
 434        server_name: &'a LanguageServerName,
 435        binary_status: &'a LanguageServerBinaryStatus,
 436    },
 437}
 438
 439#[derive(Debug)]
 440enum LspMenuItem {
 441    WithHealthCheck {
 442        server_id: LanguageServerId,
 443        health: LanguageServerHealthStatus,
 444        binary_status: Option<LanguageServerBinaryStatus>,
 445    },
 446    WithBinaryStatus {
 447        server_id: Option<LanguageServerId>,
 448        server_name: LanguageServerName,
 449        binary_status: LanguageServerBinaryStatus,
 450    },
 451    ToggleServersButton {
 452        restart: bool,
 453    },
 454    Header {
 455        header: Option<SharedString>,
 456        separator: bool,
 457    },
 458}
 459
 460impl LspMenuItem {
 461    fn server_info(&self) -> Option<ServerInfo> {
 462        match self {
 463            Self::Header { .. } => None,
 464            Self::ToggleServersButton { .. } => None,
 465            Self::WithHealthCheck {
 466                server_id,
 467                health,
 468                binary_status,
 469                ..
 470            } => Some(ServerInfo {
 471                name: health.name.clone(),
 472                id: Some(*server_id),
 473                health: health.health(),
 474                binary_status: binary_status.clone(),
 475                message: health.message(),
 476            }),
 477            Self::WithBinaryStatus {
 478                server_id,
 479                server_name,
 480                binary_status,
 481                ..
 482            } => Some(ServerInfo {
 483                name: server_name.clone(),
 484                id: *server_id,
 485                health: None,
 486                binary_status: Some(binary_status.clone()),
 487                message: binary_status.message.clone(),
 488            }),
 489        }
 490    }
 491}
 492
 493impl ServerData<'_> {
 494    fn into_lsp_item(self) -> LspMenuItem {
 495        match self {
 496            Self::WithHealthCheck {
 497                server_id,
 498                health,
 499                binary_status,
 500                ..
 501            } => LspMenuItem::WithHealthCheck {
 502                server_id,
 503                health: health.clone(),
 504                binary_status: binary_status.cloned(),
 505            },
 506            Self::WithBinaryStatus {
 507                server_id,
 508                server_name,
 509                binary_status,
 510                ..
 511            } => LspMenuItem::WithBinaryStatus {
 512                server_id,
 513                server_name: server_name.clone(),
 514                binary_status: binary_status.clone(),
 515            },
 516        }
 517    }
 518}
 519
 520impl LspButton {
 521    pub fn new(
 522        workspace: &Workspace,
 523        popover_menu_handle: PopoverMenuHandle<ContextMenu>,
 524        window: &mut Window,
 525        cx: &mut Context<Self>,
 526    ) -> Self {
 527        let settings_subscription =
 528            cx.observe_global_in::<SettingsStore>(window, move |lsp_button, window, cx| {
 529                if ProjectSettings::get_global(cx).global_lsp_settings.button {
 530                    if lsp_button.lsp_menu.is_none() {
 531                        lsp_button.refresh_lsp_menu(true, window, cx);
 532                    }
 533                } else if lsp_button.lsp_menu.take().is_some() {
 534                    cx.notify();
 535                }
 536            });
 537
 538        let lsp_store = workspace.project().read(cx).lsp_store();
 539        let mut language_servers = LanguageServers::default();
 540        for (_, status) in lsp_store.read(cx).language_server_statuses() {
 541            language_servers.binary_statuses.insert(
 542                status.name.clone(),
 543                LanguageServerBinaryStatus {
 544                    status: BinaryStatus::None,
 545                    message: None,
 546                },
 547            );
 548        }
 549
 550        let lsp_store_subscription =
 551            cx.subscribe_in(&lsp_store, window, |lsp_button, _, e, window, cx| {
 552                lsp_button.on_lsp_store_event(e, window, cx)
 553            });
 554
 555        let server_state = cx.new(|_| LanguageServerState {
 556            workspace: workspace.weak_handle(),
 557            items: Vec::new(),
 558            lsp_store: lsp_store.downgrade(),
 559            active_editor: None,
 560            language_servers,
 561        });
 562
 563        let mut lsp_button = Self {
 564            server_state,
 565            popover_menu_handle,
 566            lsp_menu: None,
 567            lsp_menu_refresh: Task::ready(()),
 568            _subscriptions: vec![settings_subscription, lsp_store_subscription],
 569        };
 570        if !lsp_button
 571            .server_state
 572            .read(cx)
 573            .language_servers
 574            .binary_statuses
 575            .is_empty()
 576        {
 577            lsp_button.refresh_lsp_menu(true, window, cx);
 578        }
 579
 580        lsp_button
 581    }
 582
 583    fn on_lsp_store_event(
 584        &mut self,
 585        e: &LspStoreEvent,
 586        window: &mut Window,
 587        cx: &mut Context<Self>,
 588    ) {
 589        if self.lsp_menu.is_none() {
 590            return;
 591        };
 592        let mut updated = false;
 593
 594        match e {
 595            LspStoreEvent::LanguageServerUpdate {
 596                language_server_id,
 597                name,
 598                message: proto::update_language_server::Variant::StatusUpdate(status_update),
 599            } => match &status_update.status {
 600                Some(proto::status_update::Status::Binary(binary_status)) => {
 601                    let Some(name) = name.as_ref() else {
 602                        return;
 603                    };
 604                    if let Some(binary_status) = proto::ServerBinaryStatus::from_i32(*binary_status)
 605                    {
 606                        let binary_status = match binary_status {
 607                            proto::ServerBinaryStatus::None => BinaryStatus::None,
 608                            proto::ServerBinaryStatus::CheckingForUpdate => {
 609                                BinaryStatus::CheckingForUpdate
 610                            }
 611                            proto::ServerBinaryStatus::Downloading => BinaryStatus::Downloading,
 612                            proto::ServerBinaryStatus::Starting => BinaryStatus::Starting,
 613                            proto::ServerBinaryStatus::Stopping => BinaryStatus::Stopping,
 614                            proto::ServerBinaryStatus::Stopped => BinaryStatus::Stopped,
 615                            proto::ServerBinaryStatus::Failed => {
 616                                let Some(error) = status_update.message.clone() else {
 617                                    return;
 618                                };
 619                                BinaryStatus::Failed { error }
 620                            }
 621                        };
 622                        self.server_state.update(cx, |state, _| {
 623                            state.language_servers.update_binary_status(
 624                                binary_status,
 625                                status_update.message.as_deref(),
 626                                name.clone(),
 627                            );
 628                        });
 629                        updated = true;
 630                    };
 631                }
 632                Some(proto::status_update::Status::Health(health_status)) => {
 633                    if let Some(health) = proto::ServerHealth::from_i32(*health_status) {
 634                        let health = match health {
 635                            proto::ServerHealth::Ok => ServerHealth::Ok,
 636                            proto::ServerHealth::Warning => ServerHealth::Warning,
 637                            proto::ServerHealth::Error => ServerHealth::Error,
 638                        };
 639                        self.server_state.update(cx, |state, _| {
 640                            state.language_servers.update_server_health(
 641                                *language_server_id,
 642                                health,
 643                                status_update.message.as_deref(),
 644                                name.clone(),
 645                            );
 646                        });
 647                        updated = true;
 648                    }
 649                }
 650                None => {}
 651            },
 652            LspStoreEvent::LanguageServerUpdate {
 653                language_server_id,
 654                name,
 655                message: proto::update_language_server::Variant::RegisteredForBuffer(update),
 656                ..
 657            } => {
 658                self.server_state.update(cx, |state, cx| {
 659                    let Ok(worktree) = state.workspace.update(cx, |workspace, cx| {
 660                        workspace
 661                            .project()
 662                            .read(cx)
 663                            .find_worktree(Path::new(&update.buffer_abs_path), cx)
 664                            .map(|(worktree, _)| worktree.downgrade())
 665                    }) else {
 666                        return;
 667                    };
 668                    let entry = state
 669                        .language_servers
 670                        .servers_per_buffer_abs_path
 671                        .entry(PathBuf::from(&update.buffer_abs_path))
 672                        .or_insert_with(|| ServersForPath {
 673                            servers: HashMap::default(),
 674                            worktree: worktree.clone(),
 675                        });
 676                    entry.servers.insert(*language_server_id, name.clone());
 677                    if worktree.is_some() {
 678                        entry.worktree = worktree;
 679                    }
 680                });
 681                updated = true;
 682            }
 683            _ => {}
 684        };
 685
 686        if updated {
 687            self.refresh_lsp_menu(false, window, cx);
 688        }
 689    }
 690
 691    fn regenerate_items(&mut self, cx: &mut App) {
 692        self.server_state.update(cx, |state, cx| {
 693            let active_worktrees = state
 694                .active_editor
 695                .as_ref()
 696                .into_iter()
 697                .flat_map(|active_editor| {
 698                    active_editor
 699                        .editor
 700                        .upgrade()
 701                        .into_iter()
 702                        .flat_map(|active_editor| {
 703                            active_editor
 704                                .read(cx)
 705                                .buffer()
 706                                .read(cx)
 707                                .all_buffers()
 708                                .into_iter()
 709                                .filter_map(|buffer| {
 710                                    project::File::from_dyn(buffer.read(cx).file())
 711                                })
 712                                .map(|buffer_file| buffer_file.worktree.clone())
 713                        })
 714                })
 715                .collect::<HashSet<_>>();
 716
 717            let mut server_ids_to_worktrees =
 718                HashMap::<LanguageServerId, Entity<Worktree>>::default();
 719            let mut server_names_to_worktrees = HashMap::<
 720                LanguageServerName,
 721                HashSet<(Entity<Worktree>, LanguageServerId)>,
 722            >::default();
 723            for servers_for_path in state.language_servers.servers_per_buffer_abs_path.values() {
 724                if let Some(worktree) = servers_for_path
 725                    .worktree
 726                    .as_ref()
 727                    .and_then(|worktree| worktree.upgrade())
 728                {
 729                    for (server_id, server_name) in &servers_for_path.servers {
 730                        server_ids_to_worktrees.insert(*server_id, worktree.clone());
 731                        if let Some(server_name) = server_name {
 732                            server_names_to_worktrees
 733                                .entry(server_name.clone())
 734                                .or_default()
 735                                .insert((worktree.clone(), *server_id));
 736                        }
 737                    }
 738                }
 739            }
 740            state
 741                .lsp_store
 742                .update(cx, |lsp_store, cx| {
 743                    for (server_id, status) in lsp_store.language_server_statuses() {
 744                        if let Some(worktree) = status.worktree.and_then(|worktree_id| {
 745                            lsp_store
 746                                .worktree_store()
 747                                .read(cx)
 748                                .worktree_for_id(worktree_id, cx)
 749                        }) {
 750                            server_ids_to_worktrees.insert(server_id, worktree.clone());
 751                            server_names_to_worktrees
 752                                .entry(status.name.clone())
 753                                .or_default()
 754                                .insert((worktree, server_id));
 755                        }
 756                    }
 757                })
 758                .ok();
 759
 760            let mut servers_per_worktree = BTreeMap::<SharedString, Vec<ServerData>>::new();
 761            let mut servers_without_worktree = Vec::<ServerData>::new();
 762            let mut servers_with_health_checks = HashSet::default();
 763
 764            for (server_id, health) in &state.language_servers.health_statuses {
 765                let worktree = server_ids_to_worktrees.get(server_id).or_else(|| {
 766                    let worktrees = server_names_to_worktrees.get(&health.name)?;
 767                    worktrees
 768                        .iter()
 769                        .find(|(worktree, _)| active_worktrees.contains(worktree))
 770                        .or_else(|| worktrees.iter().next())
 771                        .map(|(worktree, _)| worktree)
 772                });
 773                servers_with_health_checks.insert(&health.name);
 774                let worktree_name =
 775                    worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name_str()));
 776
 777                let binary_status = state.language_servers.binary_statuses.get(&health.name);
 778                let server_data = ServerData::WithHealthCheck {
 779                    server_id: *server_id,
 780                    health,
 781                    binary_status,
 782                };
 783                match worktree_name {
 784                    Some(worktree_name) => servers_per_worktree
 785                        .entry(worktree_name.clone())
 786                        .or_default()
 787                        .push(server_data),
 788                    None => servers_without_worktree.push(server_data),
 789                }
 790            }
 791
 792            let mut can_stop_all = !state.language_servers.health_statuses.is_empty();
 793            let mut can_restart_all = state.language_servers.health_statuses.is_empty();
 794            for (server_name, binary_status) in state
 795                .language_servers
 796                .binary_statuses
 797                .iter()
 798                .filter(|(name, _)| !servers_with_health_checks.contains(name))
 799            {
 800                match binary_status.status {
 801                    BinaryStatus::None => {
 802                        can_restart_all = false;
 803                        can_stop_all |= true;
 804                    }
 805                    BinaryStatus::CheckingForUpdate => {
 806                        can_restart_all = false;
 807                        can_stop_all = false;
 808                    }
 809                    BinaryStatus::Downloading => {
 810                        can_restart_all = false;
 811                        can_stop_all = false;
 812                    }
 813                    BinaryStatus::Starting => {
 814                        can_restart_all = false;
 815                        can_stop_all = false;
 816                    }
 817                    BinaryStatus::Stopping => {
 818                        can_restart_all = false;
 819                        can_stop_all = false;
 820                    }
 821                    BinaryStatus::Stopped => {}
 822                    BinaryStatus::Failed { .. } => {}
 823                }
 824
 825                match server_names_to_worktrees.get(server_name) {
 826                    Some(worktrees_for_name) => {
 827                        match worktrees_for_name
 828                            .iter()
 829                            .find(|(worktree, _)| active_worktrees.contains(worktree))
 830                            .or_else(|| worktrees_for_name.iter().next())
 831                        {
 832                            Some((worktree, server_id)) => {
 833                                let worktree_name =
 834                                    SharedString::new(worktree.read(cx).root_name_str());
 835                                servers_per_worktree
 836                                    .entry(worktree_name.clone())
 837                                    .or_default()
 838                                    .push(ServerData::WithBinaryStatus {
 839                                        server_name,
 840                                        binary_status,
 841                                        server_id: Some(*server_id),
 842                                    });
 843                            }
 844                            None => servers_without_worktree.push(ServerData::WithBinaryStatus {
 845                                server_name,
 846                                binary_status,
 847                                server_id: None,
 848                            }),
 849                        }
 850                    }
 851                    None => servers_without_worktree.push(ServerData::WithBinaryStatus {
 852                        server_name,
 853                        binary_status,
 854                        server_id: None,
 855                    }),
 856                }
 857            }
 858
 859            let mut new_lsp_items =
 860                Vec::with_capacity(servers_per_worktree.len() + servers_without_worktree.len() + 2);
 861            for (worktree_name, worktree_servers) in servers_per_worktree {
 862                if worktree_servers.is_empty() {
 863                    continue;
 864                }
 865                new_lsp_items.push(LspMenuItem::Header {
 866                    header: Some(worktree_name),
 867                    separator: false,
 868                });
 869                new_lsp_items.extend(worktree_servers.into_iter().map(ServerData::into_lsp_item));
 870            }
 871            if !servers_without_worktree.is_empty() {
 872                new_lsp_items.push(LspMenuItem::Header {
 873                    header: Some(SharedString::from("Unknown worktree")),
 874                    separator: false,
 875                });
 876                new_lsp_items.extend(
 877                    servers_without_worktree
 878                        .into_iter()
 879                        .map(ServerData::into_lsp_item),
 880                );
 881            }
 882            if !new_lsp_items.is_empty() {
 883                if can_stop_all {
 884                    new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: true });
 885                    new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: false });
 886                } else if can_restart_all {
 887                    new_lsp_items.push(LspMenuItem::ToggleServersButton { restart: true });
 888                }
 889            }
 890
 891            state.items = new_lsp_items;
 892        });
 893    }
 894
 895    fn refresh_lsp_menu(
 896        &mut self,
 897        create_if_empty: bool,
 898        window: &mut Window,
 899        cx: &mut Context<Self>,
 900    ) {
 901        if create_if_empty || self.lsp_menu.is_some() {
 902            let state = self.server_state.clone();
 903            self.lsp_menu_refresh = cx.spawn_in(window, async move |lsp_button, cx| {
 904                cx.background_executor()
 905                    .timer(Duration::from_millis(30))
 906                    .await;
 907                lsp_button
 908                    .update_in(cx, |lsp_button, window, cx| {
 909                        lsp_button.regenerate_items(cx);
 910                        let menu = ContextMenu::build(window, cx, |menu, _, cx| {
 911                            state.update(cx, |state, cx| state.fill_menu(menu, cx))
 912                        });
 913                        lsp_button.lsp_menu = Some(menu.clone());
 914                        lsp_button.popover_menu_handle.refresh_menu(
 915                            window,
 916                            cx,
 917                            Rc::new(move |_, _| Some(menu.clone())),
 918                        );
 919                        cx.notify();
 920                    })
 921                    .ok();
 922            });
 923        }
 924    }
 925}
 926
 927impl StatusItemView for LspButton {
 928    fn set_active_pane_item(
 929        &mut self,
 930        active_pane_item: Option<&dyn workspace::ItemHandle>,
 931        window: &mut Window,
 932        cx: &mut Context<Self>,
 933    ) {
 934        if ProjectSettings::get_global(cx).global_lsp_settings.button {
 935            if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
 936                if Some(&editor)
 937                    != self
 938                        .server_state
 939                        .read(cx)
 940                        .active_editor
 941                        .as_ref()
 942                        .and_then(|active_editor| active_editor.editor.upgrade())
 943                        .as_ref()
 944                {
 945                    let editor_buffers =
 946                        HashSet::from_iter(editor.read(cx).buffer().read(cx).excerpt_buffer_ids());
 947                    let _editor_subscription = cx.subscribe_in(
 948                        &editor,
 949                        window,
 950                        |lsp_button, _, e: &EditorEvent, window, cx| match e {
 951                            EditorEvent::ExcerptsAdded { buffer, .. } => {
 952                                let updated = lsp_button.server_state.update(cx, |state, cx| {
 953                                    if let Some(active_editor) = state.active_editor.as_mut() {
 954                                        let buffer_id = buffer.read(cx).remote_id();
 955                                        active_editor.editor_buffers.insert(buffer_id)
 956                                    } else {
 957                                        false
 958                                    }
 959                                });
 960                                if updated {
 961                                    lsp_button.refresh_lsp_menu(false, window, cx);
 962                                }
 963                            }
 964                            EditorEvent::ExcerptsRemoved {
 965                                removed_buffer_ids, ..
 966                            } => {
 967                                let removed = lsp_button.server_state.update(cx, |state, _| {
 968                                    let mut removed = false;
 969                                    if let Some(active_editor) = state.active_editor.as_mut() {
 970                                        for id in removed_buffer_ids {
 971                                            active_editor.editor_buffers.retain(|buffer_id| {
 972                                                let retain = buffer_id != id;
 973                                                removed |= !retain;
 974                                                retain
 975                                            });
 976                                        }
 977                                    }
 978                                    removed
 979                                });
 980                                if removed {
 981                                    lsp_button.refresh_lsp_menu(false, window, cx);
 982                                }
 983                            }
 984                            _ => {}
 985                        },
 986                    );
 987                    self.server_state.update(cx, |state, _| {
 988                        state.active_editor = Some(ActiveEditor {
 989                            editor: editor.downgrade(),
 990                            _editor_subscription,
 991                            editor_buffers,
 992                        });
 993                    });
 994                    self.refresh_lsp_menu(true, window, cx);
 995                }
 996            } else if self.server_state.read(cx).active_editor.is_some() {
 997                self.server_state.update(cx, |state, _| {
 998                    state.active_editor = None;
 999                });
1000                self.refresh_lsp_menu(false, window, cx);
1001            }
1002        } else if self.server_state.read(cx).active_editor.is_some() {
1003            self.server_state.update(cx, |state, _| {
1004                state.active_editor = None;
1005            });
1006            self.refresh_lsp_menu(false, window, cx);
1007        }
1008    }
1009}
1010
1011impl Render for LspButton {
1012    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
1013        if self.server_state.read(cx).language_servers.is_empty() || self.lsp_menu.is_none() {
1014            return div().hidden();
1015        }
1016
1017        let mut has_errors = false;
1018        let mut has_warnings = false;
1019        let mut has_other_notifications = false;
1020        let state = self.server_state.read(cx);
1021        for binary_status in state.language_servers.binary_statuses.values() {
1022            has_errors |= matches!(binary_status.status, BinaryStatus::Failed { .. });
1023            has_other_notifications |= binary_status.message.is_some();
1024        }
1025
1026        for server in state.language_servers.health_statuses.values() {
1027            if let Some((message, health)) = &server.health {
1028                has_other_notifications |= message.is_some();
1029                match health {
1030                    ServerHealth::Ok => {}
1031                    ServerHealth::Warning => has_warnings = true,
1032                    ServerHealth::Error => has_errors = true,
1033                }
1034            }
1035        }
1036
1037        let (indicator, description) = if has_errors {
1038            (
1039                Some(Indicator::dot().color(Color::Error)),
1040                "Server with errors",
1041            )
1042        } else if has_warnings {
1043            (
1044                Some(Indicator::dot().color(Color::Warning)),
1045                "Server with warnings",
1046            )
1047        } else if has_other_notifications {
1048            (
1049                Some(Indicator::dot().color(Color::Modified)),
1050                "Server with notifications",
1051            )
1052        } else {
1053            (None, "All Servers Operational")
1054        };
1055
1056        let lsp_button = cx.entity();
1057
1058        div().child(
1059            PopoverMenu::new("lsp-tool")
1060                .menu(move |_, cx| lsp_button.read(cx).lsp_menu.clone())
1061                .anchor(Corner::BottomLeft)
1062                .with_handle(self.popover_menu_handle.clone())
1063                .trigger_with_tooltip(
1064                    IconButton::new("zed-lsp-tool-button", IconName::BoltOutlined)
1065                        .when_some(indicator, IconButton::indicator)
1066                        .icon_size(IconSize::Small)
1067                        .indicator_border_color(Some(cx.theme().colors().status_bar_background)),
1068                    move |_window, cx| {
1069                        Tooltip::with_meta("Language Servers", Some(&ToggleMenu), description, cx)
1070                    },
1071                ),
1072        )
1073    }
1074}