use dap::{ client::SessionId, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind},};use editor::{Editor, EditorEvent};use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt,};use gpui::{ actions, div, App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window,};use project::{ debugger::{dap_store, session::Session}, search::SearchQuery, Project,};use settings::Settings as _;use std::{ borrow::Cow, collections::{HashMap, VecDeque}, sync::Arc,};use util::maybe;use workspace::{ item::Item, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, ui::{h_flex, Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu}, ToolbarItemEvent, ToolbarItemView, Workspace,}; 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>,} 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 { 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 845. Load more
use dap::{ client::SessionId, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind}, }; use editor::{Editor, EditorEvent}; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt, }; use gpui::{ actions, div, App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; use project::{ debugger::{dap_store, session::Session}, search::SearchQuery, Project, }; use settings::Settings as _; use std::{ borrow::Cow, collections::{HashMap, VecDeque}, sync::Arc, }; use util::maybe; use workspace::{ item::Item, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, ui::{h_flex, Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu}, ToolbarItemEvent, ToolbarItemView, Workspace, }; 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>, } 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 { 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();