873 lines · 27.0 KB · Rust
RawDownload
use dap::{    client::SessionId,    debugger_settings::DebuggerSettings,    transport::{IoKind, LogKind},};use editor::{Editor, EditorEvent};use futures::{    StreamExt,    channel::mpsc::{UnboundedSender, unbounded},};use gpui::{    App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,    ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div,};use project::{    Project,    debugger::{dap_store, session::Session},    search::SearchQuery,};use settings::Settings as _;use std::{    borrow::Cow,    collections::{HashMap, VecDeque},    sync::Arc,};use util::maybe;use workspace::{    ToolbarItemEvent, ToolbarItemView, Workspace,    item::Item,    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},    ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex},}; struct DapLogView {    editor: Entity<Editor>,    focus_handle: FocusHandle,    log_store: Entity<LogStore>,    editor_subscriptions: Vec<Subscription>,    current_view: Option<(SessionId, LogKind)>,    project: Entity<Project>,    _subscriptions: Vec<Subscription>,} pub struct LogStore {    projects: HashMap<WeakEntity<Project>, ProjectState>,    debug_clients: HashMap<SessionId, DebugAdapterState>,    rpc_tx: UnboundedSender<(SessionId, IoKind, String)>,    adapter_log_tx: UnboundedSender<(SessionId, IoKind, String)>,} struct ProjectState {    _subscriptions: [gpui::Subscription; 2],} struct DebugAdapterState {    log_messages: VecDeque<String>,    rpc_messages: RpcMessages,} struct RpcMessages {    messages: VecDeque<String>,    last_message_kind: Option<MessageKind>,} impl RpcMessages {    const MESSAGE_QUEUE_LIMIT: usize = 255;     fn new() -> Self {        Self {            last_message_kind: None,            messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT),        }    }} const SEND: &str = "// Send";const RECEIVE: &str = "// Receive"; #[derive(Clone, Copy, PartialEq, Eq)]enum MessageKind {    Send,    Receive,} impl MessageKind {    fn label(&self) -> &'static str {        match self {            Self::Send => SEND,            Self::Receive => RECEIVE,        }    }} impl DebugAdapterState {    fn new() -> Self {        Self {            log_messages: VecDeque::new(),            rpc_messages: RpcMessages::new(),        }    }} impl LogStore {    pub fn new(cx: &Context<Self>) -> Self {        let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>();        cx.spawn(async move |this, cx| {            while let Some((client_id, io_kind, message)) = rpc_rx.next().await {                if let Some(this) = this.upgrade() {                    this.update(cx, |this, cx| {                        this.on_rpc_log(client_id, io_kind, &message, cx);                    })?;                }                 smol::future::yield_now().await;            }            anyhow::Ok(())        })        .detach_and_log_err(cx);         let (adapter_log_tx, mut adapter_log_rx) = unbounded::<(SessionId, IoKind, String)>();        cx.spawn(async move |this, cx| {            while let Some((client_id, io_kind, message)) = adapter_log_rx.next().await {                if let Some(this) = this.upgrade() {                    this.update(cx, |this, cx| {                        this.on_adapter_log(client_id, io_kind, &message, cx);                    })?;                }                 smol::future::yield_now().await;            }            anyhow::Ok(())        })        .detach_and_log_err(cx);        Self {            rpc_tx,            adapter_log_tx,            projects: HashMap::new(),            debug_clients: HashMap::new(),        }    }     fn on_rpc_log(        &mut self,        client_id: SessionId,        io_kind: IoKind,        message: &str,        cx: &mut Context<Self>,    ) {        self.add_debug_client_message(client_id, io_kind, message.to_string(), cx);    }     fn on_adapter_log(        &mut self,        client_id: SessionId,        io_kind: IoKind,        message: &str,        cx: &mut Context<Self>,    ) {        self.add_debug_client_log(client_id, io_kind, message.to_string(), cx);    }     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);                    }),                    cx.subscribe(                        &project.read(cx).dap_store(),                        |this, dap_store, event, cx| match event {                            dap_store::DapStoreEvent::DebugClientStarted(session_id) => {                                let session = dap_store.read(cx).session_by_id(session_id);                                if let Some(session) = session {                                    this.add_debug_client(*session_id, session, cx);                                }                            }                            dap_store::DapStoreEvent::DebugClientShutdown(session_id) => {                                this.remove_debug_client(*session_id, cx);                            }                             _ => {}                        },                    ),                ],            },        );    }     fn get_debug_adapter_state(&mut self, id: SessionId) -> Option<&mut DebugAdapterState> {        self.debug_clients.get_mut(&id)    }     fn add_debug_client_message(        &mut self,        id: SessionId,        io_kind: IoKind,        message: String,        cx: &mut Context<Self>,    ) {        let Some(debug_client_state) = self.get_debug_adapter_state(id) else {            return;        };         let kind = match io_kind {            IoKind::StdOut | IoKind::StdErr => MessageKind::Receive,            IoKind::StdIn => MessageKind::Send,        };         let rpc_messages = &mut debug_client_state.rpc_messages;        if rpc_messages.last_message_kind != Some(kind) {            Self::add_debug_client_entry(                &mut rpc_messages.messages,                id,                kind.label().to_string(),                LogKind::Rpc,                cx,            );            rpc_messages.last_message_kind = Some(kind);        }        Self::add_debug_client_entry(&mut rpc_messages.messages, id, message, LogKind::Rpc, cx);         cx.notify();    }     fn add_debug_client_log(        &mut self,        id: SessionId,        io_kind: IoKind,        message: String,        cx: &mut Context<Self>,    ) {        let Some(debug_client_state) = self.get_debug_adapter_state(id) else {            return;        };         let message = match io_kind {            IoKind::StdErr => {                let mut message = message.clone();                message.insert_str(0, "stderr: ");                message            }            _ => message,        };         Self::add_debug_client_entry(            &mut debug_client_state.log_messages,            id,            message,            LogKind::Adapter,            cx,        );        cx.notify();    }     fn add_debug_client_entry(        log_lines: &mut VecDeque<String>,        id: SessionId,        message: String,        kind: LogKind,        cx: &mut Context<Self>,    ) {        while log_lines.len() >= RpcMessages::MESSAGE_QUEUE_LIMIT {            log_lines.pop_front();        }         let format_messages = DebuggerSettings::get_global(cx).format_dap_log_messages;         let entry = if format_messages {            maybe!({                serde_json::to_string_pretty::<serde_json::Value>(                    &serde_json::from_str(&message).ok()?,                )                .ok()            })            .unwrap_or(message)        } else {            message        };        log_lines.push_back(entry.clone());         cx.emit(Event::NewLogEntry { id, entry, kind });    }     fn add_debug_client(        &mut self,        client_id: SessionId,        client: Entity<Session>,        cx: &App,    ) -> Option<&mut DebugAdapterState> {        let client_state = self            .debug_clients            .entry(client_id)            .or_insert_with(DebugAdapterState::new);         let io_tx = self.rpc_tx.clone();         let client = client.read(cx).adapter_client()?;        client.add_log_handler(            move |io_kind, message| {                io_tx                    .unbounded_send((client_id, io_kind, message.to_string()))                    .ok();            },            LogKind::Rpc,        );         let log_io_tx = self.adapter_log_tx.clone();        client.add_log_handler(            move |io_kind, message| {                log_io_tx                    .unbounded_send((client_id, io_kind, message.to_string()))                    .ok();            },            LogKind::Adapter,        );         Some(client_state)    }     fn remove_debug_client(&mut self, client_id: SessionId, cx: &mut Context<Self>) {        self.debug_clients.remove(&client_id);        cx.notify();    }     fn log_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque<String>> {        Some(&mut self.debug_clients.get_mut(&client_id)?.log_messages)    }     fn rpc_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque<String>> {        Some(            &mut self                .debug_clients                .get_mut(&client_id)?                .rpc_messages                .messages,        )    }} pub struct DapLogToolbarItemView {    log_view: Option<Entity<DapLogView>>,} impl DapLogToolbarItemView {    pub fn new() -> Self {        Self { log_view: None }    }} impl Render for DapLogToolbarItemView {    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        let Some(log_view) = self.log_view.clone() else {            return Empty.into_any_element();        };         let (menu_rows, current_client_id) = log_view.update(cx, |log_view, cx| {            (                log_view.menu_items(cx).unwrap_or_default(),                log_view.current_view.map(|(client_id, _)| client_id),            )        });         let current_client = current_client_id.and_then(|current_client_id| {            menu_rows                .iter()                .find(|row| row.client_id == current_client_id)        });         let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView")            .anchor(gpui::Corner::TopLeft)            .trigger(Button::new(                "debug_client_menu_header",                current_client                    .map(|sub_item| {                        Cow::Owned(format!(                            "{} ({}) - {}",                            sub_item.client_name,                            sub_item.client_id.0,                            match sub_item.selected_entry {                                LogKind::Adapter => ADAPTER_LOGS,                                LogKind::Rpc => RPC_MESSAGES,                            }                        ))                    })                    .unwrap_or_else(|| "No adapter selected".into()),            ))            .menu(move |mut window, cx| {                let log_view = log_view.clone();                let menu_rows = menu_rows.clone();                ContextMenu::build(&mut window, cx, move |mut menu, window, _cx| {                    for row in menu_rows.into_iter() {                        menu = menu.custom_row(move |_window, _cx| {                            div()                                .w_full()                                .pl_2()                                .child(                                    Label::new(                                        format!("{}. {}", row.client_id.0, row.client_name,),                                    )                                    .color(workspace::ui::Color::Muted),                                )                                .into_any_element()                        });                         if row.has_adapter_logs {                            menu = menu.custom_entry(                                move |_window, _cx| {                                    div()                                        .w_full()                                        .pl_4()                                        .child(Label::new(ADAPTER_LOGS))                                        .into_any_element()                                },                                window.handler_for(&log_view, move |view, window, cx| {                                    view.show_log_messages_for_adapter(row.client_id, window, cx);                                }),                            );                        }                         menu = menu.custom_entry(                            move |_window, _cx| {                                div()                                    .w_full()                                    .pl_4()                                    .child(Label::new(RPC_MESSAGES))                                    .into_any_element()                            },                            window.handler_for(&log_view, move |view, window, cx| {                                view.show_rpc_trace_for_server(row.client_id, window, cx);                            }),                        );                    }                     menu                })                .into()            });         h_flex()            .size_full()            .child(dap_menu)            .child(                div()                    .child(                        Button::new("clear_log_button", "Clear").on_click(cx.listener(                            |this, _, window, cx| {                                if let Some(log_view) = this.log_view.as_ref() {                                    log_view.update(cx, |log_view, cx| {                                        log_view.editor.update(cx, |editor, cx| {                                            editor.set_read_only(false);                                            editor.clear(window, cx);                                            editor.set_read_only(true);                                        });                                    })                                }                            },                        )),                    )                    .ml_2(),            )            .into_any_element()    }} impl EventEmitter<ToolbarItemEvent> for DapLogToolbarItemView {} impl ToolbarItemView for DapLogToolbarItemView {    fn set_active_pane_item(        &mut self,        active_pane_item: Option<&dyn workspace::item::ItemHandle>,        _window: &mut Window,        cx: &mut Context<Self>,    ) -> workspace::ToolbarItemLocation {        if let Some(item) = active_pane_item {            if let Some(log_view) = item.downcast::<DapLogView>() {                self.log_view = Some(log_view.clone());                return workspace::ToolbarItemLocation::PrimaryLeft;            }        }        self.log_view = None;         cx.notify();         workspace::ToolbarItemLocation::Hidden    }} impl DapLogView {    pub fn new(        project: Entity<Project>,        log_store: Entity<LogStore>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);         let focus_handle = cx.focus_handle();

Lines 1–500 of 873.