From 6650be8e0ff35e2e9c0f15764e1b192f943f97a7 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:56:23 -0400 Subject: [PATCH] debugger: Improve logging of debug sessions (#32718) This PR fixes a common issue where a debug session won't start up and user's weren't able to get any logs from the debug session. We now do these three things 1. We know store a history of debug sessions 2. We added a new option to only look at the initialization sequence 3. We default to selecting a session in dap log view in stead of none Release Notes: - debugger: Add history to debug session logging --------- Co-authored-by: Cole Miller Co-authored-by: Remco Smits --- crates/dap/src/client.rs | 2 +- crates/dap/src/transport.rs | 33 +- crates/debugger_tools/src/dap_log.rs | 475 +++++++++++++++-------- crates/project/src/debugger/dap_store.rs | 2 + 4 files changed, 347 insertions(+), 165 deletions(-) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 587bdd9f5e..90ee7340d3 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -218,7 +218,7 @@ impl DebugAdapterClient { pub fn add_log_handler(&self, f: F, kind: LogKind) where - F: 'static + Send + FnMut(IoKind, &str), + F: 'static + Send + FnMut(IoKind, Option<&str>, &str), { self.transport_delegate.add_log_handler(f, kind); } diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 77cbb92deb..19d4c1674e 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -25,7 +25,9 @@ use util::ConnectionResult; use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings}; -pub type IoHandler = Box; +pub(crate) type IoMessage = str; +pub(crate) type Command = str; +pub type IoHandler = Box, &IoMessage)>; #[derive(PartialEq, Eq, Clone, Copy)] pub enum LogKind { @@ -296,7 +298,7 @@ impl TransportDelegate { if let Some(log_handlers) = log_handlers.as_ref() { for (kind, handler) in log_handlers.lock().iter_mut() { if matches!(kind, LogKind::Adapter) { - handler(IoKind::StdOut, line.as_str()); + handler(IoKind::StdOut, None, line.as_str()); } } } @@ -330,6 +332,12 @@ impl TransportDelegate { } } + let command = match &message { + Message::Request(request) => Some(request.command.as_str()), + Message::Response(response) => Some(response.command.as_str()), + _ => None, + }; + let message = match serde_json::to_string(&message) { Ok(message) => message, Err(e) => break Err(e.into()), @@ -338,7 +346,7 @@ impl TransportDelegate { if let Some(log_handlers) = log_handlers.as_ref() { for (kind, log_handler) in log_handlers.lock().iter_mut() { if matches!(kind, LogKind::Rpc) { - log_handler(IoKind::StdIn, &message); + log_handler(IoKind::StdIn, command, &message); } } } @@ -423,7 +431,7 @@ impl TransportDelegate { Ok(_) => { for (kind, log_handler) in log_handlers.lock().iter_mut() { if matches!(kind, LogKind::Adapter) { - log_handler(IoKind::StdErr, buffer.as_str()); + log_handler(IoKind::StdErr, None, buffer.as_str()); } } @@ -512,17 +520,24 @@ impl TransportDelegate { Err(e) => return ConnectionResult::Result(Err(e)), }; + let message = + serde_json::from_str::(message_str).context("deserializing server message"); + if let Some(log_handlers) = log_handlers { + let command = match &message { + Ok(Message::Request(request)) => Some(request.command.as_str()), + Ok(Message::Response(response)) => Some(response.command.as_str()), + _ => None, + }; + for (kind, log_handler) in log_handlers.lock().iter_mut() { if matches!(kind, LogKind::Rpc) { - log_handler(IoKind::StdOut, message_str); + log_handler(IoKind::StdOut, command, message_str); } } } - ConnectionResult::Result( - serde_json::from_str::(message_str).context("deserializing server message"), - ) + ConnectionResult::Result(message) } pub async fn shutdown(&self) -> Result<()> { @@ -558,7 +573,7 @@ impl TransportDelegate { pub fn add_log_handler(&self, f: F, kind: LogKind) where - F: 'static + Send + FnMut(IoKind, &str), + F: 'static + Send + FnMut(IoKind, Option<&Command>, &IoMessage), { let mut log_handlers = self.log_handlers.lock(); log_handlers.push((kind, Box::new(f))); diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index 24b82321ed..fb5a345725 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -1,4 +1,5 @@ use dap::{ + adapters::DebugAdapterName, client::SessionId, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind}, @@ -31,6 +32,13 @@ use workspace::{ ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex}, }; +// TODO: +// - [x] stop sorting by session ID +// - [x] pick the most recent session by default (logs if available, RPC messages otherwise) +// - [ ] dump the launch/attach request somewhere (logs?) + +const MAX_SESSIONS: usize = 10; + struct DapLogView { editor: Entity, focus_handle: FocusHandle, @@ -43,9 +51,9 @@ struct DapLogView { pub struct LogStore { projects: HashMap, ProjectState>, - debug_clients: HashMap, - rpc_tx: UnboundedSender<(SessionId, IoKind, String)>, - adapter_log_tx: UnboundedSender<(SessionId, IoKind, String)>, + debug_sessions: VecDeque, + rpc_tx: UnboundedSender<(SessionId, IoKind, Option, SharedString)>, + adapter_log_tx: UnboundedSender<(SessionId, IoKind, Option, SharedString)>, } struct ProjectState { @@ -53,13 +61,19 @@ struct ProjectState { } struct DebugAdapterState { - log_messages: VecDeque, + id: SessionId, + log_messages: VecDeque, rpc_messages: RpcMessages, + adapter_name: DebugAdapterName, + has_adapter_logs: bool, + is_terminated: bool, } struct RpcMessages { - messages: VecDeque, + messages: VecDeque, last_message_kind: Option, + initialization_sequence: Vec, + last_init_message_kind: Option, } impl RpcMessages { @@ -68,7 +82,9 @@ impl RpcMessages { fn new() -> Self { Self { last_message_kind: None, + last_init_message_kind: None, messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT), + initialization_sequence: Vec::new(), } } } @@ -92,22 +108,27 @@ impl MessageKind { } impl DebugAdapterState { - fn new() -> Self { + fn new(id: SessionId, adapter_name: DebugAdapterName, has_adapter_logs: bool) -> Self { Self { + id, log_messages: VecDeque::new(), rpc_messages: RpcMessages::new(), + adapter_name, + has_adapter_logs, + is_terminated: false, } } } impl LogStore { pub fn new(cx: &Context) -> Self { - let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>(); + let (rpc_tx, mut rpc_rx) = + unbounded::<(SessionId, IoKind, Option, SharedString)>(); cx.spawn(async move |this, cx| { - while let Some((client_id, io_kind, message)) = rpc_rx.next().await { + while let Some((session_id, io_kind, command, 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); + this.add_debug_adapter_message(session_id, io_kind, command, message, cx); })?; } @@ -117,12 +138,13 @@ impl LogStore { }) .detach_and_log_err(cx); - let (adapter_log_tx, mut adapter_log_rx) = unbounded::<(SessionId, IoKind, String)>(); + let (adapter_log_tx, mut adapter_log_rx) = + unbounded::<(SessionId, IoKind, Option, SharedString)>(); cx.spawn(async move |this, cx| { - while let Some((client_id, io_kind, message)) = adapter_log_rx.next().await { + while let Some((session_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); + this.add_debug_adapter_log(session_id, io_kind, message, cx); })?; } @@ -135,30 +157,10 @@ impl LogStore { rpc_tx, adapter_log_tx, projects: HashMap::new(), - debug_clients: HashMap::new(), + debug_sessions: Default::default(), } } - fn on_rpc_log( - &mut self, - client_id: SessionId, - io_kind: IoKind, - message: &str, - cx: &mut Context, - ) { - 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.add_debug_client_log(client_id, io_kind, message.to_string(), cx); - } - pub fn add_project(&mut self, project: &Entity, cx: &mut Context) { let weak_project = project.downgrade(); self.projects.insert( @@ -174,13 +176,15 @@ impl LogStore { 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); + this.add_debug_session(*session_id, session, cx); } } dap_store::DapStoreEvent::DebugClientShutdown(session_id) => { - this.remove_debug_client(*session_id, cx); + this.get_debug_adapter_state(*session_id) + .iter_mut() + .for_each(|state| state.is_terminated = true); + this.clean_sessions(cx); } - _ => {} }, ), @@ -190,63 +194,88 @@ impl LogStore { } fn get_debug_adapter_state(&mut self, id: SessionId) -> Option<&mut DebugAdapterState> { - self.debug_clients.get_mut(&id) + self.debug_sessions + .iter_mut() + .find(|adapter_state| adapter_state.id == id) } - fn add_debug_client_message( + fn add_debug_adapter_message( &mut self, id: SessionId, io_kind: IoKind, - message: String, + command: Option, + message: SharedString, cx: &mut Context, ) { let Some(debug_client_state) = self.get_debug_adapter_state(id) else { return; }; + let is_init_seq = command.as_ref().is_some_and(|command| { + matches!( + command.as_ref(), + "attach" | "launch" | "initialize" | "configurationDone" + ) + }); + let kind = match io_kind { IoKind::StdOut | IoKind::StdErr => MessageKind::Receive, IoKind::StdIn => MessageKind::Send, }; let rpc_messages = &mut debug_client_state.rpc_messages; + + // Push a separator if the kind has changed if rpc_messages.last_message_kind != Some(kind) { - Self::add_debug_client_entry( + Self::get_debug_adapter_entry( &mut rpc_messages.messages, id, - kind.label().to_string(), + kind.label().into(), LogKind::Rpc, cx, ); rpc_messages.last_message_kind = Some(kind); } - Self::add_debug_client_entry(&mut rpc_messages.messages, id, message, LogKind::Rpc, cx); + + let entry = Self::get_debug_adapter_entry( + &mut rpc_messages.messages, + id, + message, + LogKind::Rpc, + cx, + ); + + if is_init_seq { + if rpc_messages.last_init_message_kind != Some(kind) { + rpc_messages + .initialization_sequence + .push(SharedString::from(kind.label())); + rpc_messages.last_init_message_kind = Some(kind); + } + rpc_messages.initialization_sequence.push(entry); + } cx.notify(); } - fn add_debug_client_log( + fn add_debug_adapter_log( &mut self, id: SessionId, io_kind: IoKind, - message: String, + message: SharedString, cx: &mut Context, ) { - let Some(debug_client_state) = self.get_debug_adapter_state(id) else { + let Some(debug_adapter_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 - } + IoKind::StdErr => format!("stderr: {message}").into(), _ => message, }; - Self::add_debug_client_entry( - &mut debug_client_state.log_messages, + Self::get_debug_adapter_entry( + &mut debug_adapter_state.log_messages, id, message, LogKind::Adapter, @@ -255,13 +284,13 @@ impl LogStore { cx.notify(); } - fn add_debug_client_entry( - log_lines: &mut VecDeque, + fn get_debug_adapter_entry( + log_lines: &mut VecDeque, id: SessionId, - message: String, + message: SharedString, kind: LogKind, cx: &mut Context, - ) { + ) -> SharedString { while log_lines.len() >= RpcMessages::MESSAGE_QUEUE_LIMIT { log_lines.pop_front(); } @@ -275,33 +304,69 @@ impl LogStore { ) .ok() }) + .map(SharedString::from) .unwrap_or(message) } else { message }; log_lines.push_back(entry.clone()); - cx.emit(Event::NewLogEntry { id, entry, kind }); + cx.emit(Event::NewLogEntry { + id, + entry: entry.clone(), + kind, + }); + + entry } - fn add_debug_client( + fn add_debug_session( &mut self, - client_id: SessionId, - client: Entity, - cx: &App, - ) -> Option<&mut DebugAdapterState> { - let client_state = self - .debug_clients - .entry(client_id) - .or_insert_with(DebugAdapterState::new); + session_id: SessionId, + session: Entity, + cx: &mut Context, + ) { + if self + .debug_sessions + .iter_mut() + .any(|adapter_state| adapter_state.id == session_id) + { + return; + } + + let (adapter_name, has_adapter_logs) = session.read_with(cx, |session, _| { + ( + session.adapter(), + session + .adapter_client() + .map(|client| client.has_adapter_logs()) + .unwrap_or(false), + ) + }); + + self.debug_sessions.push_back(DebugAdapterState::new( + session_id, + adapter_name, + has_adapter_logs, + )); + + self.clean_sessions(cx); let io_tx = self.rpc_tx.clone(); - let client = client.read(cx).adapter_client()?; + let Some(client) = session.read(cx).adapter_client() else { + return; + }; + client.add_log_handler( - move |io_kind, message| { + move |io_kind, command, message| { io_tx - .unbounded_send((client_id, io_kind, message.to_string())) + .unbounded_send(( + session_id, + io_kind, + command.map(|command| command.to_owned().into()), + message.to_owned().into(), + )) .ok(); }, LogKind::Rpc, @@ -309,34 +374,66 @@ impl LogStore { let log_io_tx = self.adapter_log_tx.clone(); client.add_log_handler( - move |io_kind, message| { + move |io_kind, command, message| { log_io_tx - .unbounded_send((client_id, io_kind, message.to_string())) + .unbounded_send(( + session_id, + io_kind, + command.map(|command| command.to_owned().into()), + message.to_owned().into(), + )) .ok(); }, LogKind::Adapter, ); - - Some(client_state) } - fn remove_debug_client(&mut self, client_id: SessionId, cx: &mut Context) { - self.debug_clients.remove(&client_id); + fn clean_sessions(&mut self, cx: &mut Context) { + let mut to_remove = self.debug_sessions.len().saturating_sub(MAX_SESSIONS); + self.debug_sessions.retain(|session| { + if to_remove > 0 && session.is_terminated { + to_remove -= 1; + return false; + } + true + }); cx.notify(); } - fn log_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque> { - Some(&mut self.debug_clients.get_mut(&client_id)?.log_messages) + fn log_messages_for_session( + &mut self, + session_id: SessionId, + ) -> Option<&mut VecDeque> { + self.debug_sessions + .iter_mut() + .find(|session| session.id == session_id) + .map(|state| &mut state.log_messages) } - fn rpc_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque> { - Some( - &mut self - .debug_clients - .get_mut(&client_id)? - .rpc_messages - .messages, - ) + fn rpc_messages_for_session( + &mut self, + session_id: SessionId, + ) -> Option<&mut VecDeque> { + self.debug_sessions.iter_mut().find_map(|state| { + if state.id == session_id { + Some(&mut state.rpc_messages.messages) + } else { + None + } + }) + } + + fn initialization_sequence_for_session( + &mut self, + session_id: SessionId, + ) -> Option<&mut Vec> { + self.debug_sessions.iter_mut().find_map(|state| { + if state.id == session_id { + Some(&mut state.rpc_messages.initialization_sequence) + } else { + None + } + }) } } @@ -356,18 +453,15 @@ impl Render for DapLogToolbarItemView { return Empty.into_any_element(); }; - let (menu_rows, current_client_id) = log_view.update(cx, |log_view, cx| { + let (menu_rows, current_session_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), + log_view.menu_items(cx), + log_view.current_view.map(|(session_id, _)| session_id), ) }); - let current_client = current_client_id.and_then(|current_client_id| { - menu_rows - .iter() - .find(|row| row.client_id == current_client_id) - }); + let current_client = current_session_id + .and_then(|session_id| menu_rows.iter().find(|row| row.session_id == session_id)); let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView") .anchor(gpui::Corner::TopLeft) @@ -377,8 +471,8 @@ impl Render for DapLogToolbarItemView { .map(|sub_item| { Cow::Owned(format!( "{} ({}) - {}", - sub_item.client_name, - sub_item.client_id.0, + sub_item.adapter_name, + sub_item.session_id.0, match sub_item.selected_entry { LogKind::Adapter => ADAPTER_LOGS, LogKind::Rpc => RPC_MESSAGES, @@ -397,9 +491,10 @@ impl Render for DapLogToolbarItemView { .w_full() .pl_2() .child( - Label::new( - format!("{}. {}", row.client_id.0, row.client_name,), - ) + Label::new(format!( + "{}. {}", + row.session_id.0, row.adapter_name, + )) .color(workspace::ui::Color::Muted), ) .into_any_element() @@ -415,23 +510,40 @@ impl Render for DapLogToolbarItemView { .into_any_element() }, window.handler_for(&log_view, move |view, window, cx| { - view.show_log_messages_for_adapter(row.client_id, window, cx); + view.show_log_messages_for_adapter(row.session_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 = 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.session_id, window, cx); + }), + ) + .custom_entry( + move |_window, _cx| { + div() + .w_full() + .pl_4() + .child(Label::new(INITIALIZATION_SEQUENCE)) + .into_any_element() + }, + window.handler_for(&log_view, move |view, window, cx| { + view.show_initialization_sequence_for_server( + row.session_id, + window, + cx, + ); + }), + ); } menu @@ -518,7 +630,13 @@ impl DapLogView { } }); - Self { + let state_info = log_store + .read(cx) + .debug_sessions + .back() + .map(|session| (session.id, session.has_adapter_logs)); + + let mut this = Self { editor, focus_handle, project, @@ -526,7 +644,17 @@ impl DapLogView { editor_subscriptions, current_view: None, _subscriptions: vec![events_subscriptions], + }; + + if let Some((session_id, have_adapter_logs)) = state_info { + if have_adapter_logs { + this.show_log_messages_for_adapter(session_id, window, cx); + } else { + this.show_rpc_trace_for_server(session_id, window, cx); + } } + + this } fn editor_for_logs( @@ -559,42 +687,34 @@ impl DapLogView { (editor, vec![editor_subscription, search_subscription]) } - fn menu_items(&self, cx: &App) -> Option> { - let mut menu_items = self - .project + fn menu_items(&self, cx: &App) -> Vec { + self.log_store .read(cx) - .dap_store() - .read(cx) - .sessions() - .filter_map(|session| { - let session = session.read(cx); - session.adapter(); - let client = session.adapter_client()?; - Some(DapMenuItem { - client_id: client.id(), - client_name: session.adapter().to_string(), - has_adapter_logs: client.has_adapter_logs(), - selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind), - }) + .debug_sessions + .iter() + .rev() + .map(|state| DapMenuItem { + session_id: state.id, + adapter_name: state.adapter_name.clone(), + has_adapter_logs: state.has_adapter_logs, + selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind), }) - .collect::>(); - menu_items.sort_by_key(|item| item.client_id.0); - Some(menu_items) + .collect::>() } fn show_rpc_trace_for_server( &mut self, - client_id: SessionId, + session_id: SessionId, window: &mut Window, cx: &mut Context, ) { let rpc_log = self.log_store.update(cx, |log_store, _| { log_store - .rpc_messages_for_client(client_id) - .map(|state| log_contents(&state)) + .rpc_messages_for_session(session_id) + .map(|state| log_contents(state.iter().cloned())) }); if let Some(rpc_log) = rpc_log { - self.current_view = Some((client_id, LogKind::Rpc)); + self.current_view = Some((session_id, LogKind::Rpc)); let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx); let language = self.project.read(cx).languages().language_for_name("JSON"); editor @@ -626,17 +746,17 @@ impl DapLogView { fn show_log_messages_for_adapter( &mut self, - client_id: SessionId, + session_id: SessionId, window: &mut Window, cx: &mut Context, ) { let message_log = self.log_store.update(cx, |log_store, _| { log_store - .log_messages_for_client(client_id) - .map(|state| log_contents(&state)) + .log_messages_for_session(session_id) + .map(|state| log_contents(state.iter().cloned())) }); if let Some(message_log) = message_log { - self.current_view = Some((client_id, LogKind::Adapter)); + self.current_view = Some((session_id, LogKind::Adapter)); let (editor, editor_subscriptions) = Self::editor_for_logs(message_log, window, cx); editor .read(cx) @@ -652,14 +772,53 @@ impl DapLogView { cx.focus_self(window); } + + fn show_initialization_sequence_for_server( + &mut self, + session_id: SessionId, + window: &mut Window, + cx: &mut Context, + ) { + let rpc_log = self.log_store.update(cx, |log_store, _| { + log_store + .initialization_sequence_for_session(session_id) + .map(|state| log_contents(state.iter().cloned())) + }); + if let Some(rpc_log) = rpc_log { + self.current_view = Some((session_id, LogKind::Rpc)); + let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx); + let language = self.project.read(cx).languages().language_for_name("JSON"); + editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("log buffer should be a singleton") + .update(cx, |_, cx| { + cx.spawn({ + let buffer = cx.entity(); + async move |_, cx| { + let language = language.await.ok(); + buffer.update(cx, |buffer, cx| { + buffer.set_language(language, cx); + }) + } + }) + .detach_and_log_err(cx); + }); + + self.editor = editor; + self.editor_subscriptions = editor_subscriptions; + cx.notify(); + } + + cx.focus_self(window); + } } -fn log_contents(lines: &VecDeque) -> String { - let (a, b) = lines.as_slices(); - let a = a.iter().map(move |v| v.as_ref()); - let b = b.iter().map(move |v| v.as_ref()); - a.chain(b).fold(String::new(), |mut acc, el| { - acc.push_str(el); +fn log_contents(lines: impl Iterator) -> String { + lines.fold(String::new(), |mut acc, el| { + acc.push_str(&el); acc.push('\n'); acc }) @@ -667,14 +826,15 @@ fn log_contents(lines: &VecDeque) -> String { #[derive(Clone, PartialEq)] pub(crate) struct DapMenuItem { - pub client_id: SessionId, - pub client_name: String, + pub session_id: SessionId, + pub adapter_name: DebugAdapterName, pub has_adapter_logs: bool, pub selected_entry: LogKind, } const ADAPTER_LOGS: &str = "Adapter Logs"; const RPC_MESSAGES: &str = "RPC Messages"; +const INITIALIZATION_SEQUENCE: &str = "Initialization Sequence"; impl Render for DapLogView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { @@ -836,7 +996,7 @@ impl Focusable for DapLogView { pub enum Event { NewLogEntry { id: SessionId, - entry: String, + entry: SharedString, kind: LogKind, }, } @@ -849,12 +1009,16 @@ impl EventEmitter for DapLogView {} #[cfg(any(test, feature = "test-support"))] impl LogStore { pub fn contained_session_ids(&self) -> Vec { - self.debug_clients.keys().cloned().collect() + self.debug_sessions + .iter() + .map(|session| session.id) + .collect() } - pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec { - self.debug_clients - .get(&session_id) + pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec { + self.debug_sessions + .iter() + .find(|adapter_state| adapter_state.id == session_id) .expect("This session should exist if a test is calling") .rpc_messages .messages @@ -862,9 +1026,10 @@ impl LogStore { .into() } - pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec { - self.debug_clients - .get(&session_id) + pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec { + self.debug_sessions + .iter() + .find(|adapter_state| adapter_state.id == session_id) .expect("This session should exist if a test is calling") .log_messages .clone() diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 68053952e0..5d6c6952d8 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -706,6 +706,8 @@ impl DapStore { let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx)); + cx.emit(DapStoreEvent::DebugClientShutdown(session_id)); + cx.background_spawn(async move { if shutdown_children.len() > 0 { let _ = join_all(shutdown_children).await;