1074 lines · 42.9 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::{    Context, ContextMenu, ContextMenuEntry, ContextMenuItem, DocumentationAside, DocumentationEdge,    DocumentationSide, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, Window, 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)]struct ServerInfo {    name: LanguageServerName,    id: Option<LanguageServerId>,    health: Option<ServerHealth>,    binary_status: Option<LanguageServerBinaryStatus>,    message: Option<SharedString>,} impl ServerInfo {    fn server_selector(&self) -> LanguageServerSelector {        self.id            .map(LanguageServerSelector::Id)            .unwrap_or_else(|| LanguageServerSelector::Name(self.name.clone()))    }} 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 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 = 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),                    BinaryStatus::Stopping => Some(Color::Disabled),                    BinaryStatus::Stopped => Some(Color::Disabled),                    BinaryStatus::Failed { .. } => Some(Color::Error),                })                .or_else(|| {                    Some(match server_info.health? {                        ServerHealth::Ok => Color::Success,                        ServerHealth::Warning => Color::Warning,                        ServerHealth::Error => Color::Error,                    })                })                .unwrap_or(Color::Success);             let message = server_info                .message                .as_ref()                .or_else(|| server_info.binary_status.as_ref()?.message.as_ref())                .cloned();            let hover_label = if message.is_some() {                Some("View Message")            } else if has_logs {                Some("View Logs")            } else {                None            };             let server_name = server_info.name.clone();            menu = menu.item(ContextMenuItem::custom_entry(                move |_, _| {                    h_flex()                        .group("menu_item")                        .w_full()                        .gap_2()                        .justify_between()                        .child(                            h_flex()                                .gap_2()                                .child(Indicator::dot().color(status_color))                                .child(Label::new(server_name.0.clone())),                        )                        .when_some(hover_label, |div, hover_label| {                            div.child(                                h_flex()                                    .visible_on_hover("menu_item")                                    .child(                                        Label::new(hover_label)                                            .size(LabelSize::Small)                                            .color(Color::Muted),                                    )                                    .child(                                        Icon::new(IconName::ChevronRight)                                            .size(IconSize::Small)                                            .color(Color::Muted),                                    ),                            )                        })                        .into_any_element()                },                {                    let lsp_logs = lsp_logs.clone();                    let message = message.clone();                    let server_selector = server_selector.clone();                    let server_name = server_info.name.clone();                    let workspace = self.workspace.clone();                    move |window, cx| {                        if let Some(message) = &message {                            let Some(create_buffer) = workspace                                .update(cx, |workspace, cx| {                                    workspace                                        .project()                                        .update(cx, |project, cx| project.create_buffer(false, cx))                                })                                .ok()                            else {                                return;                            };                             let window = window.window_handle();                            let workspace = workspace.clone();                            let message = message.clone();                            let server_name = server_name.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.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();                        } else if has_logs {                            lsp_log_view::open_server_trace(                                &lsp_logs,                                workspace.clone(),                                server_selector.clone(),                                window,                                cx,                            );                        } else {                            cx.propagate();                        }                    }                },                message.map(|server_message| {                    DocumentationAside::new(                        DocumentationSide::Right,                        DocumentationEdge::Bottom,                        Rc::new(move |_| Label::new(server_message.clone()).into_any_element()),                    )                }),            ));        }        menu    }} impl LanguageServers {    fn update_binary_status(        &mut self,        binary_status: BinaryStatus,        message: Option<&str>,        name: LanguageServerName,    ) {        let binary_status_message = message.map(SharedString::new);        if matches!(            binary_status,            BinaryStatus::Stopped | BinaryStatus::Failed { .. }        ) {            self.health_statuses.retain(|_, server| server.name != name);        }        self.binary_statuses.insert(            name,            LanguageServerBinaryStatus {                status: binary_status,                message: binary_status_message,            },        );    }     fn update_server_health(        &mut self,        id: LanguageServerId,        health: ServerHealth,        message: Option<&str>,        name: Option<LanguageServerName>,    ) {        if let Some(state) = self.health_statuses.get_mut(&id) {            state.health = Some((message.map(SharedString::new), health));            if let Some(name) = name {                state.name = name;            }        } else if let Some(name) = name {            self.health_statuses.insert(                id,                LanguageServerHealthStatus {                    health: Some((message.map(SharedString::new), health)),                    name,                },            );        }    }     fn is_empty(&self) -> bool {        self.binary_statuses.is_empty() && self.health_statuses.is_empty()    }} #[derive(Debug)]enum ServerData<'a> {    WithHealthCheck {        server_id: LanguageServerId,        health: &'a LanguageServerHealthStatus,        binary_status: Option<&'a LanguageServerBinaryStatus>,    },    WithBinaryStatus {        server_id: Option<LanguageServerId>,        server_name: &'a LanguageServerName,        binary_status: &'a LanguageServerBinaryStatus,    },} #[derive(Debug)]enum LspMenuItem {    WithHealthCheck {        server_id: LanguageServerId,        health: LanguageServerHealthStatus,        binary_status: Option<LanguageServerBinaryStatus>,    },    WithBinaryStatus {        server_id: Option<LanguageServerId>,        server_name: LanguageServerName,        binary_status: LanguageServerBinaryStatus,    },    ToggleServersButton {        restart: bool,    },    Header {        header: Option<SharedString>,        separator: bool,    },} impl LspMenuItem {    fn server_info(&self) -> Option<ServerInfo> {        match self {            Self::Header { .. } => None,            Self::ToggleServersButton { .. } => None,            Self::WithHealthCheck {                server_id,                health,                binary_status,                ..            } => Some(ServerInfo {                name: health.name.clone(),                id: Some(*server_id),                health: health.health(),                binary_status: binary_status.clone(),                message: health.message(),            }),            Self::WithBinaryStatus {                server_id,                server_name,                binary_status,                ..            } => Some(ServerInfo {                name: server_name.clone(),                id: *server_id,                health: None,                binary_status: Some(binary_status.clone()),                message: binary_status.message.clone(),            }),        }    }} impl ServerData<'_> {    fn into_lsp_item(self) -> LspMenuItem {        match self {            Self::WithHealthCheck {                server_id,                health,                binary_status,                ..

Lines 1–500 of 1074.