1608 lines · 60.0 KB · Rust
RawDownload
use collections::{HashMap, VecDeque};use copilot::Copilot;use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};use futures::{StreamExt, channel::mpsc};use gpui::{    AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,    ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,};use language::{LanguageServerId, language_settings::SoftWrap};use lsp::{    IoKind, LanguageServer, LanguageServerName, MessageType, SetTraceParams, TraceValue,    notification::SetTrace,};use project::{Project, WorktreeId, search::SearchQuery};use std::{any::TypeId, borrow::Cow, sync::Arc};use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*};use workspace::{    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,    item::{Item, ItemHandle},    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},}; const SEND_LINE: &str = "// Send:\n";const RECEIVE_LINE: &str = "// Receive:\n";const MAX_STORED_LOG_ENTRIES: usize = 2000; pub struct LogStore {    projects: HashMap<WeakEntity<Project>, ProjectState>,    language_servers: HashMap<LanguageServerId, LanguageServerState>,    copilot_log_subscription: Option<lsp::Subscription>,    _copilot_subscription: Option<gpui::Subscription>,    io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String)>,} struct ProjectState {    _subscriptions: [gpui::Subscription; 2],} trait Message: AsRef<str> {    type Level: Copy + std::fmt::Debug;    fn should_include(&self, _: Self::Level) -> bool {        true    }} struct LogMessage {    message: String,    typ: MessageType,} impl AsRef<str> for LogMessage {    fn as_ref(&self) -> &str {        &self.message    }} impl Message for LogMessage {    type Level = MessageType;     fn should_include(&self, level: Self::Level) -> bool {        match (self.typ, level) {            (MessageType::ERROR, _) => true,            (_, MessageType::ERROR) => false,            (MessageType::WARNING, _) => true,            (_, MessageType::WARNING) => false,            (MessageType::INFO, _) => true,            (_, MessageType::INFO) => false,            _ => true,        }    }} struct TraceMessage {    message: String,} impl AsRef<str> for TraceMessage {    fn as_ref(&self) -> &str {        &self.message    }} impl Message for TraceMessage {    type Level = ();} struct RpcMessage {    message: String,} impl AsRef<str> for RpcMessage {    fn as_ref(&self) -> &str {        &self.message    }} impl Message for RpcMessage {    type Level = ();} struct LanguageServerState {    name: Option<LanguageServerName>,    worktree_id: Option<WorktreeId>,    kind: LanguageServerKind,    log_messages: VecDeque<LogMessage>,    trace_messages: VecDeque<TraceMessage>,    rpc_state: Option<LanguageServerRpcState>,    trace_level: TraceValue,    log_level: MessageType,    io_logs_subscription: Option<lsp::Subscription>,} #[derive(PartialEq, Clone)]pub enum LanguageServerKind {    Local { project: WeakEntity<Project> },    Remote { project: WeakEntity<Project> },    Global,} impl LanguageServerKind {    fn is_remote(&self) -> bool {        matches!(self, LanguageServerKind::Remote { .. })    }} impl std::fmt::Debug for LanguageServerKind {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),            LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),            LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),        }    }} impl LanguageServerKind {    fn project(&self) -> Option<&WeakEntity<Project>> {        match self {            Self::Local { project } => Some(project),            Self::Remote { project } => Some(project),            Self::Global { .. } => None,        }    }} struct LanguageServerRpcState {    rpc_messages: VecDeque<RpcMessage>,    last_message_kind: Option<MessageKind>,} 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(Copy, Clone, PartialEq, Eq)]enum MessageKind {    Send,    Receive,} #[derive(Clone, Copy, Debug, Default, PartialEq)]pub enum LogKind {    Rpc,    Trace,    #[default]    Logs,    ServerInfo,} impl LogKind {    fn label(&self) -> &'static str {        match self {            LogKind::Rpc => RPC_MESSAGES,            LogKind::Trace => SERVER_TRACE,            LogKind::Logs => SERVER_LOGS,            LogKind::ServerInfo => SERVER_INFO,        }    }} #[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, [OpenLanguageServerLogs]); pub fn init(cx: &mut App) {    let log_store = cx.new(LogStore::new);     cx.observe_new(move |workspace: &mut Workspace, _, cx| {        let project = workspace.project();        if project.read(cx).is_local() || project.read(cx).is_via_ssh() {            log_store.update(cx, |store, cx| {                store.add_project(project, cx);            });        }         let log_store = log_store.clone();        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {            let project = workspace.project().read(cx);            if project.is_local() || project.is_via_ssh() {                workspace.split_item(                    SplitDirection::Right,                    Box::new(cx.new(|cx| {                        LspLogView::new(workspace.project().clone(), log_store.clone(), window, cx)                    })),                    window,                    cx,                );            }        });    })    .detach();} impl LogStore {    pub fn new(cx: &mut Context<Self>) -> Self {        let (io_tx, mut io_rx) = mpsc::unbounded();         let copilot_subscription = Copilot::global(cx).map(|copilot| {            let copilot = &copilot;            cx.subscribe(copilot, |this, copilot, inline_completion_event, cx| {                if let copilot::Event::CopilotLanguageServerStarted = inline_completion_event {                    if let Some(server) = copilot.read(cx).language_server() {                        let server_id = server.server_id();                        let weak_this = cx.weak_entity();                        this.copilot_log_subscription =                            Some(server.on_notification::<copilot::request::LogMessage, _>(                                move |params, cx| {                                    weak_this                                        .update(cx, |this, cx| {                                            this.add_language_server_log(                                                server_id,                                                MessageType::LOG,                                                &params.message,                                                cx,                                            );                                        })                                        .ok();                                },                            ));                        let name = LanguageServerName::new_static("copilot");                        this.add_language_server(                            LanguageServerKind::Global,                            server.server_id(),                            Some(name),                            None,                            Some(server.clone()),                            cx,                        );                    }                }            })        });         let this = Self {            copilot_log_subscription: None,            _copilot_subscription: copilot_subscription,            projects: HashMap::default(),            language_servers: HashMap::default(),            io_tx,        };         cx.spawn(async move |this, cx| {            while let Some((server_id, io_kind, message)) = io_rx.next().await {                if let Some(this) = this.upgrade() {                    this.update(cx, |this, cx| {                        this.on_io(server_id, io_kind, &message, cx);                    })?;                }            }            anyhow::Ok(())        })        .detach_and_log_err(cx);        this    }     pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {        let weak_project = project.downgrade();        self.projects.insert(            project.downgrade(),            ProjectState {                _subscriptions: [                    cx.observe_release(project, move |this, _, _| {                        this.projects.remove(&weak_project);                        this.language_servers                            .retain(|_, state| state.kind.project() != Some(&weak_project));                    }),                    cx.subscribe(project, |this, project, event, cx| {                        let server_kind = if project.read(cx).is_via_ssh() {                            LanguageServerKind::Remote {                                project: project.downgrade(),                            }                        } else {                            LanguageServerKind::Local {                                project: project.downgrade(),                            }                        };                         match event {                            project::Event::LanguageServerAdded(id, name, worktree_id) => {                                this.add_language_server(                                    server_kind,                                    *id,                                    Some(name.clone()),                                    *worktree_id,                                    project                                        .read(cx)                                        .lsp_store()                                        .read(cx)                                        .language_server_for_id(*id),                                    cx,                                );                            }                            project::Event::LanguageServerRemoved(id) => {                                this.remove_language_server(*id, cx);                            }                            project::Event::LanguageServerLog(id, typ, message) => {                                this.add_language_server(server_kind, *id, None, None, None, cx);                                match typ {                                    project::LanguageServerLogType::Log(typ) => {                                        this.add_language_server_log(*id, *typ, message, cx);                                    }                                    project::LanguageServerLogType::Trace(_) => {                                        this.add_language_server_trace(*id, message, cx);                                    }                                }                            }                            _ => {}                        }                    }),                ],            },        );    }     fn get_language_server_state(        &mut self,        id: LanguageServerId,    ) -> Option<&mut LanguageServerState> {        self.language_servers.get_mut(&id)    }     fn add_language_server(        &mut self,        kind: LanguageServerKind,        server_id: LanguageServerId,        name: Option<LanguageServerName>,        worktree_id: Option<WorktreeId>,        server: Option<Arc<LanguageServer>>,        cx: &mut Context<Self>,    ) -> Option<&mut LanguageServerState> {        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {            cx.notify();            LanguageServerState {                name: None,                worktree_id: None,                kind,                rpc_state: None,                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),                trace_level: TraceValue::Off,                log_level: MessageType::LOG,                io_logs_subscription: None,            }        });         if let Some(name) = name {            server_state.name = Some(name);        }        if let Some(worktree_id) = worktree_id {            server_state.worktree_id = Some(worktree_id);        }         if let Some(server) = server            .clone()            .filter(|_| server_state.io_logs_subscription.is_none())        {            let io_tx = self.io_tx.clone();            let server_id = server.server_id();            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {                io_tx                    .unbounded_send((server_id, io_kind, message.to_string()))                    .ok();            }));        }         Some(server_state)    }     fn add_language_server_log(        &mut self,        id: LanguageServerId,        typ: MessageType,        message: &str,        cx: &mut Context<Self>,    ) -> Option<()> {        let language_server_state = self.get_language_server_state(id)?;         let log_lines = &mut language_server_state.log_messages;        Self::add_language_server_message(            log_lines,            id,            LogMessage {                message: message.trim_end().to_string(),                typ,            },            language_server_state.log_level,            LogKind::Logs,            cx,        );        Some(())    }     fn add_language_server_trace(        &mut self,        id: LanguageServerId,        message: &str,        cx: &mut Context<Self>,    ) -> Option<()> {        let language_server_state = self.get_language_server_state(id)?;         let log_lines = &mut language_server_state.trace_messages;        Self::add_language_server_message(            log_lines,            id,            TraceMessage {                message: message.trim_end().to_string(),            },            (),            LogKind::Trace,            cx,        );        Some(())    }     fn add_language_server_message<T: Message>(        log_lines: &mut VecDeque<T>,        id: LanguageServerId,        message: T,        current_severity: <T as Message>::Level,        kind: LogKind,        cx: &mut Context<Self>,    ) {        while log_lines.len() >= MAX_STORED_LOG_ENTRIES {            log_lines.pop_front();        }        let entry = format!("{}\n", message.as_ref().trim());        let visible = message.should_include(current_severity);        log_lines.push_back(message);         if visible {            cx.emit(Event::NewServerLogEntry { id, entry, kind });            cx.notify();        }    }     fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {        self.language_servers.remove(&id);        cx.notify();    }     fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {        Some(&self.language_servers.get(&server_id)?.log_messages)    }     fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {        Some(&self.language_servers.get(&server_id)?.trace_messages)    }     fn server_ids_for_project<'a>(        &'a self,        lookup_project: &'a WeakEntity<Project>,    ) -> impl Iterator<Item = LanguageServerId> + 'a {        self.language_servers            .iter()            .filter_map(move |(id, state)| match &state.kind {                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {                    if project == lookup_project {                        Some(*id)                    } else {

Lines 1–500 of 1608.