1207 lines · 50.6 KB · Rust
RawDownload
use std::{    collections::{BTreeMap, HashMap},    path::{Path, PathBuf},    rc::Rc,    time::Duration,}; use client::proto;use collections::HashSet;use editor::{Editor, EditorEvent};use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};use language::{BinaryStatus, BufferId, ServerHealth};use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};use project::{    LspStore, LspStoreEvent, Worktree, lsp_store::log_store::GlobalLogStore,    project_settings::ProjectSettings,};use settings::{Settings as _, SettingsStore};use ui::{    ContextMenu, ContextMenuEntry, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*,}; use util::{ResultExt, rel_path::RelPath};use workspace::{StatusItemView, Workspace}; use crate::lsp_log_view; actions!(    lsp_tool,    [        /// Toggles the language server tool menu.        ToggleMenu    ]); pub struct LspButton {    server_state: Entity<LanguageServerState>,    popover_menu_handle: PopoverMenuHandle<ContextMenu>,    lsp_menu: Option<Entity<ContextMenu>>,    lsp_menu_refresh: Task<()>,    _subscriptions: Vec<Subscription>,} #[derive(Debug)]struct LanguageServerState {    items: Vec<LspMenuItem>,    workspace: WeakEntity<Workspace>,    lsp_store: WeakEntity<LspStore>,    active_editor: Option<ActiveEditor>,    language_servers: LanguageServers,} struct ActiveEditor {    editor: WeakEntity<Editor>,    _editor_subscription: Subscription,    editor_buffers: HashSet<BufferId>,} impl std::fmt::Debug for ActiveEditor {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        f.debug_struct("ActiveEditor")            .field("editor", &self.editor)            .field("editor_buffers", &self.editor_buffers)            .finish_non_exhaustive()    }} #[derive(Debug, Default, Clone)]struct LanguageServers {    health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,    binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,    servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,} #[derive(Debug, Clone)]struct ServersForPath {    servers: HashMap<LanguageServerId, Option<LanguageServerName>>,    worktree: Option<WeakEntity<Worktree>>,} #[derive(Debug, Clone)]struct LanguageServerHealthStatus {    name: LanguageServerName,    health: Option<(Option<SharedString>, ServerHealth)>,} #[derive(Debug, Clone)]struct LanguageServerBinaryStatus {    status: BinaryStatus,    message: Option<SharedString>,} #[derive(Debug, Clone)]struct ServerInfo {    name: LanguageServerName,    id: LanguageServerId,    health: Option<ServerHealth>,    binary_status: Option<LanguageServerBinaryStatus>,    message: Option<SharedString>,} impl ServerInfo {    fn server_selector(&self) -> LanguageServerSelector {        LanguageServerSelector::Id(self.id)    }     fn can_stop(&self) -> bool {        self.binary_status.as_ref().is_none_or(|status| {            matches!(status.status, BinaryStatus::None | BinaryStatus::Starting)        })    }} impl LanguageServerHealthStatus {    fn health(&self) -> Option<ServerHealth> {        self.health.as_ref().map(|(_, health)| *health)    }     fn message(&self) -> Option<SharedString> {        self.health            .as_ref()            .and_then(|(message, _)| message.clone())    }} impl LanguageServerState {    fn fill_menu(&self, mut menu: ContextMenu, cx: &mut Context<Self>) -> ContextMenu {        let lsp_logs = cx            .try_global::<GlobalLogStore>()            .map(|lsp_logs| lsp_logs.0.clone());        let Some(lsp_logs) = lsp_logs else {            return menu;        };         let server_versions = self            .lsp_store            .update(cx, |lsp_store, _| {                lsp_store                    .language_server_statuses()                    .map(|(server_id, status)| (server_id, status.server_version.clone()))                    .collect::<HashMap<_, _>>()            })            .unwrap_or_default();         let mut first_button_encountered = false;        for item in &self.items {            if let LspMenuItem::ToggleServersButton { restart } = item {                let label = if *restart {                    "Restart All Servers"                } else {                    "Stop All Servers"                };                 let restart = *restart;                 let button = ContextMenuEntry::new(label).handler({                    let state = cx.entity();                    move |_, cx| {                        let lsp_store = state.read(cx).lsp_store.clone();                        lsp_store                            .update(cx, |lsp_store, cx| {                                if restart {                                    let Some(workspace) = state.read(cx).workspace.upgrade() else {                                        return;                                    };                                    let project = workspace.read(cx).project().clone();                                    let path_style = project.read(cx).path_style(cx);                                    let buffer_store = project.read(cx).buffer_store().clone();                                    let buffers = state                                        .read(cx)                                        .language_servers                                        .servers_per_buffer_abs_path                                        .iter()                                        .filter_map(|(abs_path, servers)| {                                            let worktree =                                                servers.worktree.as_ref()?.upgrade()?.read(cx);                                            let relative_path =                                                abs_path.strip_prefix(&worktree.abs_path()).ok()?;                                            let relative_path =                                                RelPath::new(relative_path, path_style)                                                    .log_err()?;                                            let entry = worktree.entry_for_path(&relative_path)?;                                            let project_path =                                                project.read(cx).path_for_entry(entry.id, cx)?;                                            buffer_store.read(cx).get_by_path(&project_path)                                        })                                        .collect();                                    let selectors = state                                        .read(cx)                                        .items                                        .iter()                                        // Do not try to use IDs as we have stopped all servers already, when allowing to restart them all                                        .flat_map(|item| match item {                                            LspMenuItem::Header { .. } => None,                                            LspMenuItem::ToggleServersButton { .. } => None,                                            LspMenuItem::WithHealthCheck { health, .. } => Some(                                                LanguageServerSelector::Name(health.name.clone()),                                            ),                                            LspMenuItem::WithBinaryStatus {                                                server_name, ..                                            } => Some(LanguageServerSelector::Name(                                                server_name.clone(),                                            )),                                        })                                        .collect();                                    lsp_store.restart_language_servers_for_buffers(                                        buffers, selectors, cx,                                    );                                } else {                                    lsp_store.stop_all_language_servers(cx);                                }                            })                            .ok();                    }                });                 if !first_button_encountered {                    menu = menu.separator();                    first_button_encountered = true;                }                 menu = menu.item(button);                continue;            } else if let LspMenuItem::Header { header, separator } = item {                menu = menu                    .when(*separator, |menu| menu.separator())                    .when_some(header.as_ref(), |menu, header| menu.header(header));                continue;            }             let Some(server_info) = item.server_info() else {                continue;            };            let server_selector = server_info.server_selector();            let is_remote = self                .lsp_store                .update(cx, |lsp_store, _| lsp_store.as_remote().is_some())                .unwrap_or(false);            let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector);             let (status_color, status_label) = server_info                .binary_status                .as_ref()                .and_then(|binary_status| match binary_status.status {                    BinaryStatus::None => None,                    BinaryStatus::CheckingForUpdate                    | BinaryStatus::Downloading                    | BinaryStatus::Starting => Some((Color::Modified, "Starting…")),                    BinaryStatus::Stopping | BinaryStatus::Stopped => {                        Some((Color::Disabled, "Stopped"))                    }                    BinaryStatus::Failed { .. } => Some((Color::Error, "Error")),                })                .or_else(|| {                    Some(match server_info.health? {                        ServerHealth::Ok => (Color::Success, "Running"),                        ServerHealth::Warning => (Color::Warning, "Warning"),                        ServerHealth::Error => (Color::Error, "Error"),                    })                })                .unwrap_or((Color::Success, "Running"));             let message = server_info                .message                .as_ref()                .or_else(|| server_info.binary_status.as_ref()?.message.as_ref())                .cloned();             let server_version = server_versions                .get(&server_info.id)                .and_then(|version| version.clone());             let metadata_label = match (&server_version, &message) {                (None, None) => None,                (Some(version), None) => Some(SharedString::from(format!("v{}", version.as_ref()))),                (None, Some(message)) => Some(message.clone()),                (Some(version), Some(message)) => Some(SharedString::from(format!(                    "v{}\n\n{}",                    version.as_ref(),                    message.as_ref()                ))),            };             let submenu_server_name = server_info.name.clone();            let submenu_server_info = server_info.clone();             menu = menu.submenu_with_colored_icon(                server_info.name.0.clone(),                IconName::Circle,                status_color,                {                    let lsp_logs = lsp_logs.clone();                    let message = message.clone();                    let server_selector = server_selector.clone();                    let workspace = self.workspace.clone();                    let lsp_store = self.lsp_store.clone();                    let state = cx.entity().downgrade();                    let can_stop = submenu_server_info.can_stop();                     move |menu, _window, _cx| {                        let mut submenu = menu;                         if let Some(ref message) = message {                            let workspace_for_message = workspace.clone();                            let message_for_handler = message.clone();                            let server_name_for_message = submenu_server_name.clone();                            submenu = submenu.entry("View Message", None, move |window, cx| {                                let Some(create_buffer) = workspace_for_message                                    .update(cx, |workspace, cx| {                                        workspace.project().update(cx, |project, cx| {                                            project.create_buffer(false, cx)                                        })                                    })                                    .ok()                                else {                                    return;                                };                                 let window_handle = window.window_handle();                                let workspace = workspace_for_message.clone();                                let message = message_for_handler.clone();                                let server_name = server_name_for_message.clone();                                cx.spawn(async move |cx| {                                    let buffer = create_buffer.await?;                                    buffer.update(cx, |buffer, cx| {                                        buffer.edit(                                            [(                                                0..0,                                                format!(                                                    "Language server {server_name}:\n\n{message}"                                                ),                                            )],                                            None,                                            cx,                                        );                                        buffer.set_capability(language::Capability::ReadOnly, cx);                                    })?;                                     workspace.update(cx, |workspace, cx| {                                        window_handle.update(cx, |_, window, cx| {                                            workspace.add_item_to_active_pane(                                                Box::new(cx.new(|cx| {                                                    let mut editor = Editor::for_buffer(                                                        buffer, None, window, cx,                                                    );                                                    editor.set_read_only(true);                                                    editor                                                })),                                                None,                                                true,                                                window,                                                cx,                                            );                                        })                                    })??;                                     anyhow::Ok(())                                })                                .detach();                            });                        }                         if has_logs {                            let lsp_logs_for_debug = lsp_logs.clone();                            let workspace_for_debug = workspace.clone();                            let server_selector_for_debug = server_selector.clone();                            submenu = submenu.entry("View Logs", None, move |window, cx| {                                lsp_log_view::open_server_trace(                                    &lsp_logs_for_debug,                                    workspace_for_debug.clone(),                                    server_selector_for_debug.clone(),                                    window,                                    cx,                                );                            });                        }                         let state_for_restart = state.clone();                        let workspace_for_restart = workspace.clone();                        let lsp_store_for_restart = lsp_store.clone();                        let server_name_for_restart = submenu_server_name.clone();                        submenu = submenu.entry("Restart Server", None, move |_window, cx| {                            let Some(workspace) = workspace_for_restart.upgrade() else {                                return;                            };                             let project = workspace.read(cx).project().clone();                            let path_style = project.read(cx).path_style(cx);                            let buffer_store = project.read(cx).buffer_store().clone();                             let buffers = state_for_restart                                .update(cx, |state, cx| {                                    let server_buffers = state                                        .language_servers                                        .servers_per_buffer_abs_path                                        .iter()                                        .filter_map(|(abs_path, servers)| {                                            // Check if this server is associated with this path                                            let has_server = servers.servers.values().any(|name| {                                                name.as_ref() == Some(&server_name_for_restart)                                            });                                             if !has_server {                                                return None;                                            }                                             let worktree = servers.worktree.as_ref()?.upgrade()?;                                            let worktree_ref = worktree.read(cx);                                            let relative_path = abs_path                                                .strip_prefix(&worktree_ref.abs_path())                                                .ok()?;                                            let relative_path =                                                RelPath::new(relative_path, path_style)                                                    .log_err()?;                                            let entry =                                                worktree_ref.entry_for_path(&relative_path)?;                                            let project_path =                                                project.read(cx).path_for_entry(entry.id, cx)?;                                             buffer_store.read(cx).get_by_path(&project_path)                                        })                                        .collect::<Vec<_>>();                                     if server_buffers.is_empty() {                                        state                                            .language_servers                                            .servers_per_buffer_abs_path                                            .iter()                                            .filter_map(|(abs_path, servers)| {                                                let worktree =                                                    servers.worktree.as_ref()?.upgrade()?.read(cx);                                                let relative_path = abs_path                                                    .strip_prefix(&worktree.abs_path())                                                    .ok()?;                                                let relative_path =                                                    RelPath::new(relative_path, path_style)                                                        .log_err()?;                                                let entry =                                                    worktree.entry_for_path(&relative_path)?;                                                let project_path = project                                                    .read(cx)                                                    .path_for_entry(entry.id, cx)?;                                                buffer_store.read(cx).get_by_path(&project_path)                                            })                                            .collect()                                    } else {                                        server_buffers                                    }                                })                                .unwrap_or_default();                             if !buffers.is_empty() {                                lsp_store_for_restart                                    .update(cx, |lsp_store, cx| {                                        lsp_store.restart_language_servers_for_buffers(                                            buffers,                                            HashSet::from_iter([LanguageServerSelector::Name(                                                server_name_for_restart.clone(),                                            )]),                                            cx,                                        );                                    })                                    .ok();                            }                        });                         if can_stop {                            let lsp_store_for_stop = lsp_store.clone();                            let server_selector_for_stop = server_selector.clone();                             submenu = submenu.entry("Stop Server", None, move |_window, cx| {                                lsp_store_for_stop                                    .update(cx, |lsp_store, cx| {                                        lsp_store                                            .stop_language_servers_for_buffers(                                                Vec::new(),                                                HashSet::from_iter([                                                    server_selector_for_stop.clone()                                                ]),                                                cx,                                            )                                            .detach_and_log_err(cx);                                    })                                    .ok();                            });                        }                         submenu = submenu.separator().custom_row({                            let metadata_label = metadata_label.clone();                            move |_, _| {                                h_flex()                                    .ml_neg_1()                                    .gap_1()                                    .child(                                        Icon::new(IconName::Circle)                                            .color(status_color)                                            .size(IconSize::Small),                                    )                                    .child(                                        Label::new(status_label)

Lines 1–500 of 1207.