1337 lines · 54.9 KB · Rust
RawDownload
use std::{    cell::RefCell,    collections::{BTreeMap, HashMap},    path::{Path, PathBuf},    rc::Rc,    time::{Duration, Instant},}; use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System}; use language::language_settings::{EditPredictionProvider, all_language_settings}; 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, paths::PathExt, 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>,} struct LanguageServerState {    items: Vec<LspMenuItem>,    workspace: WeakEntity<Workspace>,    lsp_store: WeakEntity<LspStore>,    active_editor: Option<ActiveEditor>,    language_servers: LanguageServers,    process_memory_cache: Rc<RefCell<ProcessMemoryCache>>,} impl std::fmt::Debug for LanguageServerState {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        f.debug_struct("LanguageServerState")            .field("items", &self.items)            .field("workspace", &self.workspace)            .field("lsp_store", &self.lsp_store)            .field("active_editor", &self.active_editor)            .field("language_servers", &self.language_servers)            .finish_non_exhaustive()    }} const PROCESS_MEMORY_CACHE_DURATION: Duration = Duration::from_secs(5); struct ProcessMemoryCache {    system: System,    memory_usage: HashMap<u32, u64>,    last_refresh: Option<Instant>,} impl ProcessMemoryCache {    fn new() -> Self {        Self {            system: System::new(),            memory_usage: HashMap::new(),            last_refresh: None,        }    }     fn get_memory_usage(&mut self, process_id: u32) -> u64 {        let cache_expired = self            .last_refresh            .map(|last| last.elapsed() >= PROCESS_MEMORY_CACHE_DURATION)            .unwrap_or(true);         if cache_expired {            let refresh_kind = RefreshKind::nothing()                .with_processes(ProcessRefreshKind::nothing().without_tasks().with_memory());            self.system.refresh_specifics(refresh_kind);            self.memory_usage.clear();            self.last_refresh = Some(Instant::now());        }         if let Some(&memory) = self.memory_usage.get(&process_id) {            return memory;        }         let root_pid = Pid::from_u32(process_id);         let parent_map: HashMap<Pid, Pid> = self            .system            .processes()            .iter()            .filter_map(|(&pid, process)| Some((pid, process.parent()?)))            .collect();         let total_memory = self            .system            .processes()            .iter()            .filter(|(pid, _)| self.is_descendant_of(**pid, root_pid, &parent_map))            .map(|(_, process)| process.memory())            .sum();         self.memory_usage.insert(process_id, total_memory);        total_memory    }     fn is_descendant_of(&self, pid: Pid, root_pid: Pid, parent_map: &HashMap<Pid, Pid>) -> bool {        let mut current = pid;        let mut visited = HashSet::default();        while current != root_pid {            if !visited.insert(current) {                return false;            }            match parent_map.get(&current) {                Some(&parent) => current = parent,                None => return false,            }        }        true    }} 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_metadata = self            .lsp_store            .update(cx, |lsp_store, _| {                lsp_store                    .language_server_statuses()                    .map(|(server_id, status)| {                        (                            server_id,                            (                                status.server_readable_version.clone(),                                status.binary.as_ref().map(|b| b.path.clone()),                                status.process_id,                            ),                        )                    })                    .collect::<HashMap<_, _>>()            })            .unwrap_or_default();         let process_memory_cache = self.process_memory_cache.clone();         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 {                                    lsp_store.restart_all_language_servers(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, binary_path, process_id) = server_metadata                .get(&server_info.id)                .map(|(version, path, process_id)| {                    (                        version.clone(),                        path.as_ref()                            .map(|p| SharedString::from(p.compact().to_string_lossy().to_string())),                        *process_id,                    )                })                .unwrap_or((None, None, None));             let server_message = message.clone();             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();                    let process_memory_cache = process_memory_cache.clone();                     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(None, 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()

Lines 1–500 of 1337.