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