917 lines · 38.1 KB · Rust
RawDownload
use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration}; use client::proto;use collections::{HashMap, HashSet};use editor::{Editor, EditorEvent};use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions};use language::{BinaryStatus, BufferId, LocalFile, ServerHealth};use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu};use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings};use settings::{Settings as _, SettingsStore};use ui::{Context, IconButtonShape, Indicator, Tooltip, Window, prelude::*}; use workspace::{StatusItemView, Workspace}; use crate::lsp_log::GlobalLogStore; actions!(lsp_tool, [ToggleMenu]); pub struct LspTool {    state: Entity<PickerState>,    lsp_picker: Option<Entity<Picker<LspPickerDelegate>>>,    _subscriptions: Vec<Subscription>,} struct PickerState {    workspace: WeakEntity<Workspace>,    lsp_store: WeakEntity<LspStore>,    active_editor: Option<ActiveEditor>,    language_servers: LanguageServers,} #[derive(Debug)]struct LspPickerDelegate {    state: Entity<PickerState>,    selected_index: usize,    items: Vec<LspItem>,    other_servers_start_index: Option<usize>,} struct ActiveEditor {    editor: WeakEntity<Editor>,    _editor_subscription: Subscription,    editor_buffers: HashSet<BufferId>,} #[derive(Debug, Default, Clone)]struct LanguageServers {    health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,    binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,    servers_per_buffer_abs_path:        HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>,} #[derive(Debug, Clone)]struct LanguageServerHealthStatus {    name: LanguageServerName,    health: Option<(Option<SharedString>, ServerHealth)>,} #[derive(Debug, Clone)]struct LanguageServerBinaryStatus {    status: BinaryStatus,    message: Option<SharedString>,} 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 LspPickerDelegate {    fn regenerate_items(&mut self, cx: &mut Context<Picker<Self>>) {        self.state.update(cx, |state, cx| {            let editor_buffers = state                .active_editor                .as_ref()                .map(|active_editor| active_editor.editor_buffers.clone())                .unwrap_or_default();            let editor_buffer_paths = editor_buffers                .iter()                .filter_map(|buffer_id| {                    let buffer_path = state                        .lsp_store                        .update(cx, |lsp_store, cx| {                            Some(                                project::File::from_dyn(                                    lsp_store                                        .buffer_store()                                        .read(cx)                                        .get(*buffer_id)?                                        .read(cx)                                        .file(),                                )?                                .abs_path(cx),                            )                        })                        .ok()??;                    Some(buffer_path)                })                .collect::<Vec<_>>();             let mut servers_with_health_checks = HashSet::default();            let mut server_ids_with_health_checks = HashSet::default();            let mut buffer_servers =                Vec::with_capacity(state.language_servers.health_statuses.len());            let mut other_servers =                Vec::with_capacity(state.language_servers.health_statuses.len());            let buffer_server_ids = editor_buffer_paths                .iter()                .filter_map(|buffer_path| {                    state                        .language_servers                        .servers_per_buffer_abs_path                        .get(buffer_path)                })                .flatten()                .fold(HashMap::default(), |mut acc, (server_id, name)| {                    match acc.entry(*server_id) {                        hash_map::Entry::Occupied(mut o) => {                            let old_name: &mut Option<&LanguageServerName> = o.get_mut();                            if old_name.is_none() {                                *old_name = name.as_ref();                            }                        }                        hash_map::Entry::Vacant(v) => {                            v.insert(name.as_ref());                        }                    }                    acc                });            for (server_id, server_state) in &state.language_servers.health_statuses {                let binary_status = state                    .language_servers                    .binary_statuses                    .get(&server_state.name);                servers_with_health_checks.insert(&server_state.name);                server_ids_with_health_checks.insert(*server_id);                if buffer_server_ids.contains_key(server_id) {                    buffer_servers.push(ServerData::WithHealthCheck(                        *server_id,                        server_state,                        binary_status,                    ));                } else {                    other_servers.push(ServerData::WithHealthCheck(                        *server_id,                        server_state,                        binary_status,                    ));                }            }             for (server_name, status) in state                .language_servers                .binary_statuses                .iter()                .filter(|(name, _)| !servers_with_health_checks.contains(name))            {                let has_matching_server = state                    .language_servers                    .servers_per_buffer_abs_path                    .iter()                    .filter(|(path, _)| editor_buffer_paths.contains(path))                    .flat_map(|(_, server_associations)| server_associations.iter())                    .any(|(_, name)| name.as_ref() == Some(server_name));                if has_matching_server {                    buffer_servers.push(ServerData::WithBinaryStatus(server_name, status));                } else {                    other_servers.push(ServerData::WithBinaryStatus(server_name, status));                }            }             buffer_servers.sort_by_key(|data| data.name().clone());            other_servers.sort_by_key(|data| data.name().clone());            let mut other_servers_start_index = None;            let mut new_lsp_items =                Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2);            if !buffer_servers.is_empty() {                new_lsp_items.push(LspItem::Header(SharedString::new("Current Buffer")));                new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item));            }            if !other_servers.is_empty() {                other_servers_start_index = Some(new_lsp_items.len());                new_lsp_items.push(LspItem::Header(SharedString::new("Other Active Servers")));                new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item));            }             self.items = new_lsp_items;            self.other_servers_start_index = other_servers_start_index;        });    }} 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,                },            );        }    }} #[derive(Debug)]enum ServerData<'a> {    WithHealthCheck(        LanguageServerId,        &'a LanguageServerHealthStatus,        Option<&'a LanguageServerBinaryStatus>,    ),    WithBinaryStatus(&'a LanguageServerName, &'a LanguageServerBinaryStatus),} #[derive(Debug)]enum LspItem {    WithHealthCheck(        LanguageServerId,        LanguageServerHealthStatus,        Option<LanguageServerBinaryStatus>,    ),    WithBinaryStatus(LanguageServerName, LanguageServerBinaryStatus),    Header(SharedString),} impl ServerData<'_> {    fn name(&self) -> &LanguageServerName {        match self {            Self::WithHealthCheck(_, state, _) => &state.name,            Self::WithBinaryStatus(name, ..) => name,        }    }     fn into_lsp_item(self) -> LspItem {        match self {            Self::WithHealthCheck(id, name, status) => {                LspItem::WithHealthCheck(id, name.clone(), status.cloned())            }            Self::WithBinaryStatus(name, status) => {                LspItem::WithBinaryStatus(name.clone(), status.clone())            }        }    }} impl PickerDelegate for LspPickerDelegate {    type ListItem = AnyElement;     fn match_count(&self) -> usize {        self.items.len()    }     fn selected_index(&self) -> usize {        self.selected_index    }     fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {        self.selected_index = ix;        cx.notify();    }     fn update_matches(        &mut self,        _: String,        _: &mut Window,        cx: &mut Context<Picker<Self>>,    ) -> Task<()> {        cx.spawn(async move |lsp_picker, cx| {            cx.background_executor()                .timer(Duration::from_millis(30))                .await;            lsp_picker                .update(cx, |lsp_picker, cx| {                    lsp_picker.delegate.regenerate_items(cx);                })                .ok();        })    }     fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {        Arc::default()    }     fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {}     fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {        cx.emit(DismissEvent);    }     fn render_match(        &self,        ix: usize,        _: bool,        _: &mut Window,        cx: &mut Context<Picker<Self>>,    ) -> Option<Self::ListItem> {        let is_other_server = self            .other_servers_start_index            .map_or(false, |start| ix >= start);        let server_binary_status;        let server_health;        let server_message;        let server_id;        let server_name;        match self.items.get(ix)? {            LspItem::WithHealthCheck(                language_server_id,                language_server_health_status,                language_server_binary_status,            ) => {                server_binary_status = language_server_binary_status.as_ref();                server_health = language_server_health_status.health();                server_message = language_server_health_status.message();                server_id = Some(*language_server_id);                server_name = language_server_health_status.name.clone();            }            LspItem::WithBinaryStatus(language_server_name, language_server_binary_status) => {                server_binary_status = Some(language_server_binary_status);                server_health = None;                server_message = language_server_binary_status.message.clone();                server_id = None;                server_name = language_server_name.clone();            }            LspItem::Header(header) => {                return Some(                    h_flex()                        .justify_center()                        .child(Label::new(header.clone()))                        .into_any_element(),                );            }        };         let workspace = self.state.read(cx).workspace.clone();        let lsp_logs = cx.global::<GlobalLogStore>().0.upgrade()?;        let lsp_store = self.state.read(cx).lsp_store.upgrade()?;        let server_selector = server_id            .map(LanguageServerSelector::Id)            .unwrap_or_else(|| LanguageServerSelector::Name(server_name.clone()));        let can_stop = server_binary_status.is_none_or(|status| {            matches!(status.status, BinaryStatus::None | BinaryStatus::Starting)        });        // TODO currently, Zed remote does not work well with the LSP logs        // https://github.com/zed-industries/zed/issues/28557        let has_logs = lsp_store.read(cx).as_local().is_some()            && lsp_logs.read(cx).has_server_logs(&server_selector);        let status_color = server_binary_status            .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_health? {                    ServerHealth::Ok => Color::Success,                    ServerHealth::Warning => Color::Warning,                    ServerHealth::Error => Color::Error,                })            })            .unwrap_or(Color::Success);         Some(            h_flex()                .w_full()                .justify_between()                .gap_2()                .child(                    h_flex()                        .id("server-status-indicator")                        .gap_2()                        .child(Indicator::dot().color(status_color))                        .child(Label::new(server_name.0.clone()))                        .when_some(server_message.clone(), |div, server_message| {                            div.tooltip(move |_, cx| Tooltip::simple(server_message.clone(), cx))                        }),                )                .child(                    h_flex()                        .gap_1()                        .when(has_logs, |div| {                            div.child(                                IconButton::new("debug-language-server", IconName::MessageBubbles)                                    .icon_size(IconSize::XSmall)                                    .tooltip(|_, cx| Tooltip::simple("Debug Language Server", cx))                                    .on_click({                                        let workspace = workspace.clone();                                        let lsp_logs = lsp_logs.downgrade();                                        let server_selector = server_selector.clone();                                        move |_, window, cx| {                                            lsp_logs                                                .update(cx, |lsp_logs, cx| {                                                    lsp_logs.open_server_trace(                                                        workspace.clone(),                                                        server_selector.clone(),                                                        window,                                                        cx,                                                    );                                                })                                                .ok();                                        }                                    }),                            )                        })                        .when(can_stop, |div| {                            div.child(                                IconButton::new("stop-server", IconName::Stop)                                    .icon_size(IconSize::Small)                                    .tooltip(|_, cx| Tooltip::simple("Stop server", cx))                                    .on_click({                                        let lsp_store = lsp_store.downgrade();                                        let server_selector = server_selector.clone();                                        move |_, _, cx| {                                            lsp_store                                                .update(cx, |lsp_store, cx| {                                                    lsp_store.stop_language_servers_for_buffers(                                                        Vec::new(),                                                        HashSet::from_iter([                                                            server_selector.clone()                                                        ]),                                                        cx,                                                    );                                                })                                                .ok();                                        }                                    }),                            )                        })                        .child(                            IconButton::new("restart-server", IconName::Rerun)                                .icon_size(IconSize::XSmall)                                .tooltip(|_, cx| Tooltip::simple("Restart server", cx))                                .on_click({                                    let state = self.state.clone();                                    let workspace = workspace.clone();                                    let lsp_store = lsp_store.downgrade();                                    let editor_buffers = state                                        .read(cx)                                        .active_editor                                        .as_ref()                                        .map(|active_editor| active_editor.editor_buffers.clone())                                        .unwrap_or_default();                                    let server_selector = server_selector.clone();                                    move |_, _, cx| {                                        if let Some(workspace) = workspace.upgrade() {                                            let project = workspace.read(cx).project().clone();                                            let buffer_store =                                                project.read(cx).buffer_store().clone();                                            let buffers = if is_other_server {                                                let worktree_store =                                                    project.read(cx).worktree_store();                                                state                                                    .read(cx)

Lines 1–500 of 917.