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