1383 lines · 54.6 KB · Rust
RawDownload
use collections::VecDeque;use edit_prediction::EditPredictionStore;use editor::{Editor, EditorEvent, MultiBufferOffset, actions::MoveToEnd, scroll::Autoscroll};use gpui::{    Anchor, App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement,    Render, Styled, Subscription, Task, WeakEntity, Window, actions, div,};use itertools::Itertools as _;use language::{LanguageServerId, language_settings::SoftWrap};use lsp::{    LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams,    TraceValue, notification::SetTrace,};use project::{    LanguageServerStatus, Project,    lsp_store::log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message},    search::SearchQuery,};use proto::toggle_lsp_logs::LogType;use settings::SeedQuerySetting;use std::{any::TypeId, borrow::Cow, sync::Arc};use ui::{Checkbox, ContextMenu, PopoverMenu, ToggleState, prelude::*};use util::ResultExt as _;use workspace::{    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,    item::{Item, ItemHandle},    searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},}; use crate::get_or_create_tool; pub fn open(    log_store: &Entity<LogStore>,    workspace: WeakEntity<Workspace>,    server: LanguageServerSelector,    window: &mut Window,    cx: &mut App,) {    log_store.update(cx, |_, cx| {        cx.spawn_in(window, async move |log_store, cx| {            let Some(log_store) = log_store.upgrade() else {                return;            };            workspace                .update_in(cx, |workspace, window, cx| {                    let project = workspace.project().clone();                    let tool_log_store = log_store.clone();                    let log_view = get_or_create_tool(                        workspace,                        SplitDirection::Right,                        window,                        cx,                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),                    );                    log_view.update(cx, |log_view, cx| {                        let server_id = match server {                            LanguageServerSelector::Id(id) => Some(id),                            LanguageServerSelector::Name(name) => {                                log_store.read(cx).language_servers.iter().find_map(                                    |(id, state)| {                                        if state.name.as_ref() == Some(&name) {                                            Some(*id)                                        } else {                                            None                                        }                                    },                                )                            }                        };                        if let Some(server_id) = server_id {                            log_view.show_logs_for_server(server_id, window, cx);                        }                    });                })                .ok();        })        .detach();    })} pub struct LspLogView {    pub(crate) editor: Entity<Editor>,    editor_subscriptions: Vec<Subscription>,    log_store: Entity<LogStore>,    current_server_id: Option<LanguageServerId>,    active_entry_kind: LogKind,    project: Entity<Project>,    focus_handle: FocusHandle,    _log_store_subscriptions: Vec<Subscription>,} pub struct LspLogToolbarItemView {    log_view: Option<Entity<LspLogView>>,    _log_view_subscription: Option<Subscription>,} #[derive(Clone, Debug, PartialEq)]pub(crate) struct LogMenuItem {    pub server_id: LanguageServerId,    pub server_name: LanguageServerName,    pub worktree_root_name: String,    pub rpc_trace_enabled: bool,    pub selected_entry: LogKind,    pub trace_level: lsp::TraceValue,    pub server_kind: LanguageServerKind,} actions!(    dev,    [        /// Opens the language server protocol logs viewer.        OpenLanguageServerLogs    ]); pub fn init(on_headless_host: bool, cx: &mut App) {    let log_store = log_store::init(on_headless_host, cx);     cx.observe_new(move |workspace: &mut Workspace, _, cx| {        log_store.update(cx, |store, cx| {            store.add_project(workspace.project(), cx);        });         let log_store = log_store.clone();        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {            let log_store = log_store.clone();            let project = workspace.project().clone();            get_or_create_tool(                workspace,                SplitDirection::Right,                window,                cx,                move |window, cx| LspLogView::new(project, log_store, window, cx),            );        });    })    .detach();} impl LspLogView {    pub fn new(        project: Entity<Project>,        log_store: Entity<LogStore>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let server_id = log_store            .read(cx)            .language_servers            .iter()            .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))            .map(|(id, _)| *id);         let weak_project = project.downgrade();        let model_changes_subscription =            cx.observe_in(&log_store, window, move |this, store, window, cx| {                let first_server_id_for_project =                    store.read(cx).server_ids_for_project(&weak_project).next();                if let Some(current_lsp) = this.current_server_id {                    if !store.read(cx).language_servers.contains_key(&current_lsp)                        && let Some(server_id) = first_server_id_for_project                    {                        match this.active_entry_kind {                            LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),                            LogKind::Trace => this.show_trace_for_server(server_id, window, cx),                            LogKind::Logs => this.show_logs_for_server(server_id, window, cx),                            LogKind::ServerInfo => this.show_server_info(server_id, window, cx),                        }                    }                } else if let Some(server_id) = first_server_id_for_project {                    match this.active_entry_kind {                        LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),                        LogKind::Trace => this.show_trace_for_server(server_id, window, cx),                        LogKind::Logs => this.show_logs_for_server(server_id, window, cx),                        LogKind::ServerInfo => this.show_server_info(server_id, window, cx),                    }                }                 cx.notify();            });         let events_subscriptions = cx.subscribe_in(            &log_store,            window,            move |log_view, _, e, window, cx| match e {                Event::NewServerLogEntry { id, kind, text } => {                    if log_view.current_server_id == Some(*id)                        && LogKind::from_server_log_type(kind) == log_view.active_entry_kind                    {                        log_view.editor.update(cx, |editor, cx| {                            editor.set_read_only(false);                            let last_offset = editor.buffer().read(cx).len(cx);                            let newest_cursor_is_at_end = editor                                .selections                                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))                                .start                                >= last_offset;                            editor.edit(                                vec![                                    (last_offset..last_offset, text.as_str()),                                    (last_offset..last_offset, "\n"),                                ],                                cx,                            );                            if text.len() > 1024 {                                let b = editor.buffer().read(cx).as_singleton().unwrap().read(cx);                                let fold_offset =                                    b.as_rope().ceil_char_boundary(last_offset.0 + 1024);                                editor.fold_ranges(                                    vec![                                        MultiBufferOffset(fold_offset)                                            ..MultiBufferOffset(b.as_rope().len()),                                    ],                                    false,                                    window,                                    cx,                                );                            }                             if newest_cursor_is_at_end {                                editor.request_autoscroll(Autoscroll::bottom(), cx);                            }                            editor.set_read_only(true);                        });                    }                }            },        );        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);         let focus_handle = cx.focus_handle();        let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| {            window.focus(&log_view.editor.focus_handle(cx), cx);        });         cx.on_release(|log_view, cx| {            log_view.log_store.update(cx, |log_store, cx| {                for (server_id, state) in &log_store.language_servers {                    if let Some(log_kind) = state.toggled_log_kind {                        if let Some(log_type) = log_type(log_kind) {                            send_toggle_log_message(state, *server_id, false, log_type, cx);                        }                    }                }            });        })        .detach();         let mut lsp_log_view = Self {            focus_handle,            editor,            editor_subscriptions,            project,            log_store,            current_server_id: None,            active_entry_kind: LogKind::Logs,            _log_store_subscriptions: vec![                model_changes_subscription,                events_subscriptions,                focus_subscription,            ],        };        if let Some(server_id) = server_id {            lsp_log_view.show_logs_for_server(server_id, window, cx);        }        lsp_log_view    }     fn editor_for_logs(        log_contents: String,        window: &mut Window,        cx: &mut Context<Self>,    ) -> (Entity<Editor>, Vec<Subscription>) {        let editor = initialize_new_editor(log_contents, true, window, cx);        let editor_subscription = cx.subscribe(            &editor,            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),        );        let search_subscription = cx.subscribe(            &editor,            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),        );        (editor, vec![editor_subscription, search_subscription])    }     fn editor_for_server_info(        info: ServerInfo,        window: &mut Window,        cx: &mut Context<Self>,    ) -> (Entity<Editor>, Vec<Subscription>) {        let server_info = format!(            "* Server: {NAME} (id {ID}) * Version: {VERSION} * Binary: {BINARY} * Registered workspace folders:{WORKSPACE_FOLDERS} * Capabilities: {CAPABILITIES} * Configuration: {CONFIGURATION}",            NAME = info.status.name,            ID = info.id,            VERSION = info                .status                .server_version                .as_ref()                .map(|version| version.as_ref())                .unwrap_or("Unknown"),            BINARY = info                .status                .binary                .as_ref()                .map_or_else(|| "Unknown".to_string(), |binary| format!("{:#?}", binary)),            WORKSPACE_FOLDERS = info                .status                .workspace_folders                .iter()                .filter_map(|uri| uri.to_file_path().ok())                .map(|path| path.to_string_lossy().into_owned())                .join(", "),            CAPABILITIES = serde_json::to_string_pretty(&info.capabilities)                .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),            CONFIGURATION = info                .status                .configuration                .map(|configuration| serde_json::to_string_pretty(&configuration))                .transpose()                .unwrap_or_else(|e| Some(format!("Failed to serialize configuration: {e}")))                .unwrap_or_else(|| "Unknown".to_string()),        );        let editor = initialize_new_editor(server_info, false, window, cx);        let editor_subscription = cx.subscribe(            &editor,            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),        );        let search_subscription = cx.subscribe(            &editor,            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),        );        (editor, vec![editor_subscription, search_subscription])    }    pub(crate) fn try_ensure_copilot_for_project(&self, cx: &mut App) {        self.log_store.update(cx, |this, cx| {            let copilot = EditPredictionStore::try_global(cx)                .and_then(|store| store.read(cx).copilot_for_project(&self.project))?;            let server = copilot.read(cx).language_server()?.clone();            let log_subscription = this.copilot_state_for_project(&self.project.downgrade());            if let Some(subscription_slot @ None) = log_subscription {                let weak_lsp_store = cx.weak_entity();                let server_id = server.server_id();                 let name = LanguageServerName::new_static("copilot");                *subscription_slot =                    Some(server.on_notification::<lsp::notification::LogMessage, _>(                        move |params, cx| {                            weak_lsp_store                                .update(cx, |lsp_store, cx| {                                    lsp_store.add_language_server_log(                                        server_id,                                        MessageType::LOG,                                        &params.message,                                        cx,                                    );                                })                                .ok();                        },                    ));                this.add_language_server(                    LanguageServerKind::Global,                    server.server_id(),                    Some(name),                    None,                    Some(server.clone()),                    cx,                );            }             Some(())        });    }    pub(crate) fn menu_items(&self, cx: &mut App) -> Option<Vec<LogMenuItem>> {        self.try_ensure_copilot_for_project(cx);        let log_store = self.log_store.read(cx);         let unknown_server = LanguageServerName::new_static("unknown server");         let mut rows = log_store            .language_servers            .iter()            .map(|(server_id, state)| match &state.kind {                LanguageServerKind::Local { .. }                | LanguageServerKind::Remote { .. }                | LanguageServerKind::LocalSsh { .. } => {                    let worktree_root_name = state                        .worktree_id                        .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))                        .map(|worktree| worktree.read(cx).root_name_str().to_string())                        .unwrap_or_else(|| "Unknown worktree".to_string());                     LogMenuItem {                        server_id: *server_id,                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),                        server_kind: state.kind.clone(),                        worktree_root_name,                        rpc_trace_enabled: state.rpc_state.is_some(),                        selected_entry: self.active_entry_kind,                        trace_level: lsp::TraceValue::Off,                    }                }                 LanguageServerKind::Global => LogMenuItem {                    server_id: *server_id,                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),                    server_kind: state.kind.clone(),                    worktree_root_name: "supplementary".to_string(),                    rpc_trace_enabled: state.rpc_state.is_some(),                    selected_entry: self.active_entry_kind,                    trace_level: lsp::TraceValue::Off,                },            })            .chain(                self.project                    .read(cx)                    .supplementary_language_servers(cx)                    .filter_map(|(server_id, name)| {                        let state = log_store.language_servers.get(&server_id)?;                        Some(LogMenuItem {                            server_id,                            server_name: name,                            server_kind: state.kind.clone(),                            worktree_root_name: "supplementary".to_string(),                            rpc_trace_enabled: state.rpc_state.is_some(),                            selected_entry: self.active_entry_kind,                            trace_level: lsp::TraceValue::Off,                        })                    }),            )            .collect::<Vec<_>>();        rows.sort_by_key(|row| row.server_id);        rows.dedup_by_key(|row| row.server_id);        Some(rows)    }     fn show_logs_for_server(        &mut self,        server_id: LanguageServerId,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let typ = self            .log_store            .read(cx)            .language_servers            .get(&server_id)            .map(|v| v.log_level)            .unwrap_or(MessageType::LOG);        let log_contents = self            .log_store            .read(cx)            .server_logs(server_id)            .map(|v| log_contents(v, typ));        if let Some(log_contents) = log_contents {            self.current_server_id = Some(server_id);            self.active_entry_kind = LogKind::Logs;            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);            self.editor = editor;            self.editor_subscriptions = editor_subscriptions;            cx.notify();        }        self.editor.read(cx).focus_handle(cx).focus(window, cx);        self.log_store.update(cx, |log_store, cx| {            let state = log_store.get_language_server_state(server_id)?;            state.toggled_log_kind = Some(LogKind::Logs);            send_toggle_log_message(state, server_id, true, LogType::Log, cx);            Some(())        });    }     fn update_log_level(        &self,        server_id: LanguageServerId,        level: MessageType,        window: &mut Window,        cx: &mut Context<Self>,    ) {        let log_contents = self.log_store.update(cx, |this, _| {            if let Some(state) = this.get_language_server_state(server_id) {                state.log_level = level;            }             this.server_logs(server_id).map(|v| log_contents(v, level))        });         if let Some(log_contents) = log_contents {            self.editor.update(cx, |editor, cx| {                editor.set_text(log_contents, window, cx);                editor.move_to_end(&MoveToEnd, window, cx);

Lines 1–500 of 1383.