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 <cole@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid 2025-06-13 16:56:23 -04:00 committed by GitHub
parent 4425d58d72
commit 6650be8e0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 347 additions and 165 deletions

View file

@ -218,7 +218,7 @@ impl DebugAdapterClient {
pub fn add_log_handler<F>(&self, f: F, kind: LogKind) pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
where where
F: 'static + Send + FnMut(IoKind, &str), F: 'static + Send + FnMut(IoKind, Option<&str>, &str),
{ {
self.transport_delegate.add_log_handler(f, kind); self.transport_delegate.add_log_handler(f, kind);
} }

View file

@ -25,7 +25,9 @@ use util::ConnectionResult;
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings}; use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
pub type IoHandler = Box<dyn Send + FnMut(IoKind, &str)>; pub(crate) type IoMessage = str;
pub(crate) type Command = str;
pub type IoHandler = Box<dyn Send + FnMut(IoKind, Option<&Command>, &IoMessage)>;
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy)]
pub enum LogKind { pub enum LogKind {
@ -296,7 +298,7 @@ impl TransportDelegate {
if let Some(log_handlers) = log_handlers.as_ref() { if let Some(log_handlers) = log_handlers.as_ref() {
for (kind, handler) in log_handlers.lock().iter_mut() { for (kind, handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) { 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) { let message = match serde_json::to_string(&message) {
Ok(message) => message, Ok(message) => message,
Err(e) => break Err(e.into()), Err(e) => break Err(e.into()),
@ -338,7 +346,7 @@ impl TransportDelegate {
if let Some(log_handlers) = log_handlers.as_ref() { if let Some(log_handlers) = log_handlers.as_ref() {
for (kind, log_handler) in log_handlers.lock().iter_mut() { for (kind, log_handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Rpc) { if matches!(kind, LogKind::Rpc) {
log_handler(IoKind::StdIn, &message); log_handler(IoKind::StdIn, command, &message);
} }
} }
} }
@ -423,7 +431,7 @@ impl TransportDelegate {
Ok(_) => { Ok(_) => {
for (kind, log_handler) in log_handlers.lock().iter_mut() { for (kind, log_handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) { 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)), Err(e) => return ConnectionResult::Result(Err(e)),
}; };
let message =
serde_json::from_str::<Message>(message_str).context("deserializing server message");
if let Some(log_handlers) = log_handlers { 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() { for (kind, log_handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Rpc) { if matches!(kind, LogKind::Rpc) {
log_handler(IoKind::StdOut, message_str); log_handler(IoKind::StdOut, command, message_str);
} }
} }
} }
ConnectionResult::Result( ConnectionResult::Result(message)
serde_json::from_str::<Message>(message_str).context("deserializing server message"),
)
} }
pub async fn shutdown(&self) -> Result<()> { pub async fn shutdown(&self) -> Result<()> {
@ -558,7 +573,7 @@ impl TransportDelegate {
pub fn add_log_handler<F>(&self, f: F, kind: LogKind) pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
where where
F: 'static + Send + FnMut(IoKind, &str), F: 'static + Send + FnMut(IoKind, Option<&Command>, &IoMessage),
{ {
let mut log_handlers = self.log_handlers.lock(); let mut log_handlers = self.log_handlers.lock();
log_handlers.push((kind, Box::new(f))); log_handlers.push((kind, Box::new(f)));

View file

@ -1,4 +1,5 @@
use dap::{ use dap::{
adapters::DebugAdapterName,
client::SessionId, client::SessionId,
debugger_settings::DebuggerSettings, debugger_settings::DebuggerSettings,
transport::{IoKind, LogKind}, transport::{IoKind, LogKind},
@ -31,6 +32,13 @@ use workspace::{
ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex}, 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 { struct DapLogView {
editor: Entity<Editor>, editor: Entity<Editor>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
@ -43,9 +51,9 @@ struct DapLogView {
pub struct LogStore { pub struct LogStore {
projects: HashMap<WeakEntity<Project>, ProjectState>, projects: HashMap<WeakEntity<Project>, ProjectState>,
debug_clients: HashMap<SessionId, DebugAdapterState>, debug_sessions: VecDeque<DebugAdapterState>,
rpc_tx: UnboundedSender<(SessionId, IoKind, String)>, rpc_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
adapter_log_tx: UnboundedSender<(SessionId, IoKind, String)>, adapter_log_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
} }
struct ProjectState { struct ProjectState {
@ -53,13 +61,19 @@ struct ProjectState {
} }
struct DebugAdapterState { struct DebugAdapterState {
log_messages: VecDeque<String>, id: SessionId,
log_messages: VecDeque<SharedString>,
rpc_messages: RpcMessages, rpc_messages: RpcMessages,
adapter_name: DebugAdapterName,
has_adapter_logs: bool,
is_terminated: bool,
} }
struct RpcMessages { struct RpcMessages {
messages: VecDeque<String>, messages: VecDeque<SharedString>,
last_message_kind: Option<MessageKind>, last_message_kind: Option<MessageKind>,
initialization_sequence: Vec<SharedString>,
last_init_message_kind: Option<MessageKind>,
} }
impl RpcMessages { impl RpcMessages {
@ -68,7 +82,9 @@ impl RpcMessages {
fn new() -> Self { fn new() -> Self {
Self { Self {
last_message_kind: None, last_message_kind: None,
last_init_message_kind: None,
messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT), messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT),
initialization_sequence: Vec::new(),
} }
} }
} }
@ -92,22 +108,27 @@ impl MessageKind {
} }
impl DebugAdapterState { impl DebugAdapterState {
fn new() -> Self { fn new(id: SessionId, adapter_name: DebugAdapterName, has_adapter_logs: bool) -> Self {
Self { Self {
id,
log_messages: VecDeque::new(), log_messages: VecDeque::new(),
rpc_messages: RpcMessages::new(), rpc_messages: RpcMessages::new(),
adapter_name,
has_adapter_logs,
is_terminated: false,
} }
} }
} }
impl LogStore { impl LogStore {
pub fn new(cx: &Context<Self>) -> Self { pub fn new(cx: &Context<Self>) -> Self {
let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>(); let (rpc_tx, mut rpc_rx) =
unbounded::<(SessionId, IoKind, Option<SharedString>, SharedString)>();
cx.spawn(async move |this, cx| { 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() { if let Some(this) = this.upgrade() {
this.update(cx, |this, cx| { 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); .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>, SharedString)>();
cx.spawn(async move |this, cx| { 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() { if let Some(this) = this.upgrade() {
this.update(cx, |this, cx| { 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, rpc_tx,
adapter_log_tx, adapter_log_tx,
projects: HashMap::new(), 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>,
) {
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>) { pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
let weak_project = project.downgrade(); let weak_project = project.downgrade();
self.projects.insert( self.projects.insert(
@ -174,13 +176,15 @@ impl LogStore {
dap_store::DapStoreEvent::DebugClientStarted(session_id) => { dap_store::DapStoreEvent::DebugClientStarted(session_id) => {
let session = dap_store.read(cx).session_by_id(session_id); let session = dap_store.read(cx).session_by_id(session_id);
if let Some(session) = session { 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) => { 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> { 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, &mut self,
id: SessionId, id: SessionId,
io_kind: IoKind, io_kind: IoKind,
message: String, command: Option<SharedString>,
message: SharedString,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let Some(debug_client_state) = self.get_debug_adapter_state(id) else { let Some(debug_client_state) = self.get_debug_adapter_state(id) else {
return; 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 { let kind = match io_kind {
IoKind::StdOut | IoKind::StdErr => MessageKind::Receive, IoKind::StdOut | IoKind::StdErr => MessageKind::Receive,
IoKind::StdIn => MessageKind::Send, IoKind::StdIn => MessageKind::Send,
}; };
let rpc_messages = &mut debug_client_state.rpc_messages; 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) { if rpc_messages.last_message_kind != Some(kind) {
Self::add_debug_client_entry( Self::get_debug_adapter_entry(
&mut rpc_messages.messages, &mut rpc_messages.messages,
id, id,
kind.label().to_string(), kind.label().into(),
LogKind::Rpc, LogKind::Rpc,
cx, cx,
); );
rpc_messages.last_message_kind = Some(kind); 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(); cx.notify();
} }
fn add_debug_client_log( fn add_debug_adapter_log(
&mut self, &mut self,
id: SessionId, id: SessionId,
io_kind: IoKind, io_kind: IoKind,
message: String, message: SharedString,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
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; return;
}; };
let message = match io_kind { let message = match io_kind {
IoKind::StdErr => { IoKind::StdErr => format!("stderr: {message}").into(),
let mut message = message.clone();
message.insert_str(0, "stderr: ");
message
}
_ => message, _ => message,
}; };
Self::add_debug_client_entry( Self::get_debug_adapter_entry(
&mut debug_client_state.log_messages, &mut debug_adapter_state.log_messages,
id, id,
message, message,
LogKind::Adapter, LogKind::Adapter,
@ -255,13 +284,13 @@ impl LogStore {
cx.notify(); cx.notify();
} }
fn add_debug_client_entry( fn get_debug_adapter_entry(
log_lines: &mut VecDeque<String>, log_lines: &mut VecDeque<SharedString>,
id: SessionId, id: SessionId,
message: String, message: SharedString,
kind: LogKind, kind: LogKind,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) -> SharedString {
while log_lines.len() >= RpcMessages::MESSAGE_QUEUE_LIMIT { while log_lines.len() >= RpcMessages::MESSAGE_QUEUE_LIMIT {
log_lines.pop_front(); log_lines.pop_front();
} }
@ -275,33 +304,69 @@ impl LogStore {
) )
.ok() .ok()
}) })
.map(SharedString::from)
.unwrap_or(message) .unwrap_or(message)
} else { } else {
message message
}; };
log_lines.push_back(entry.clone()); 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, &mut self,
client_id: SessionId, session_id: SessionId,
client: Entity<Session>, session: Entity<Session>,
cx: &App, cx: &mut Context<Self>,
) -> Option<&mut DebugAdapterState> { ) {
let client_state = self if self
.debug_clients .debug_sessions
.entry(client_id) .iter_mut()
.or_insert_with(DebugAdapterState::new); .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 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( client.add_log_handler(
move |io_kind, message| { move |io_kind, command, message| {
io_tx 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(); .ok();
}, },
LogKind::Rpc, LogKind::Rpc,
@ -309,34 +374,66 @@ impl LogStore {
let log_io_tx = self.adapter_log_tx.clone(); let log_io_tx = self.adapter_log_tx.clone();
client.add_log_handler( client.add_log_handler(
move |io_kind, message| { move |io_kind, command, message| {
log_io_tx 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(); .ok();
}, },
LogKind::Adapter, LogKind::Adapter,
); );
Some(client_state)
} }
fn remove_debug_client(&mut self, client_id: SessionId, cx: &mut Context<Self>) { fn clean_sessions(&mut self, cx: &mut Context<Self>) {
self.debug_clients.remove(&client_id); 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(); cx.notify();
} }
fn log_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque<String>> { fn log_messages_for_session(
Some(&mut self.debug_clients.get_mut(&client_id)?.log_messages) &mut self,
session_id: SessionId,
) -> Option<&mut VecDeque<SharedString>> {
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<String>> { fn rpc_messages_for_session(
Some( &mut self,
&mut self session_id: SessionId,
.debug_clients ) -> Option<&mut VecDeque<SharedString>> {
.get_mut(&client_id)? self.debug_sessions.iter_mut().find_map(|state| {
.rpc_messages if state.id == session_id {
.messages, Some(&mut state.rpc_messages.messages)
) } else {
None
}
})
}
fn initialization_sequence_for_session(
&mut self,
session_id: SessionId,
) -> Option<&mut Vec<SharedString>> {
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(); 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.menu_items(cx),
log_view.current_view.map(|(client_id, _)| client_id), log_view.current_view.map(|(session_id, _)| session_id),
) )
}); });
let current_client = current_client_id.and_then(|current_client_id| { let current_client = current_session_id
menu_rows .and_then(|session_id| menu_rows.iter().find(|row| row.session_id == session_id));
.iter()
.find(|row| row.client_id == current_client_id)
});
let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView") let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView")
.anchor(gpui::Corner::TopLeft) .anchor(gpui::Corner::TopLeft)
@ -377,8 +471,8 @@ impl Render for DapLogToolbarItemView {
.map(|sub_item| { .map(|sub_item| {
Cow::Owned(format!( Cow::Owned(format!(
"{} ({}) - {}", "{} ({}) - {}",
sub_item.client_name, sub_item.adapter_name,
sub_item.client_id.0, sub_item.session_id.0,
match sub_item.selected_entry { match sub_item.selected_entry {
LogKind::Adapter => ADAPTER_LOGS, LogKind::Adapter => ADAPTER_LOGS,
LogKind::Rpc => RPC_MESSAGES, LogKind::Rpc => RPC_MESSAGES,
@ -397,9 +491,10 @@ impl Render for DapLogToolbarItemView {
.w_full() .w_full()
.pl_2() .pl_2()
.child( .child(
Label::new( Label::new(format!(
format!("{}. {}", row.client_id.0, row.client_name,), "{}. {}",
) row.session_id.0, row.adapter_name,
))
.color(workspace::ui::Color::Muted), .color(workspace::ui::Color::Muted),
) )
.into_any_element() .into_any_element()
@ -415,23 +510,40 @@ impl Render for DapLogToolbarItemView {
.into_any_element() .into_any_element()
}, },
window.handler_for(&log_view, move |view, window, cx| { 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( menu = menu
move |_window, _cx| { .custom_entry(
div() move |_window, _cx| {
.w_full() div()
.pl_4() .w_full()
.child(Label::new(RPC_MESSAGES)) .pl_4()
.into_any_element() .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); 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 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, editor,
focus_handle, focus_handle,
project, project,
@ -526,7 +644,17 @@ impl DapLogView {
editor_subscriptions, editor_subscriptions,
current_view: None, current_view: None,
_subscriptions: vec![events_subscriptions], _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( fn editor_for_logs(
@ -559,42 +687,34 @@ impl DapLogView {
(editor, vec![editor_subscription, search_subscription]) (editor, vec![editor_subscription, search_subscription])
} }
fn menu_items(&self, cx: &App) -> Option<Vec<DapMenuItem>> { fn menu_items(&self, cx: &App) -> Vec<DapMenuItem> {
let mut menu_items = self self.log_store
.project
.read(cx) .read(cx)
.dap_store() .debug_sessions
.read(cx) .iter()
.sessions() .rev()
.filter_map(|session| { .map(|state| DapMenuItem {
let session = session.read(cx); session_id: state.id,
session.adapter(); adapter_name: state.adapter_name.clone(),
let client = session.adapter_client()?; has_adapter_logs: state.has_adapter_logs,
Some(DapMenuItem { selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
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),
})
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>()
menu_items.sort_by_key(|item| item.client_id.0);
Some(menu_items)
} }
fn show_rpc_trace_for_server( fn show_rpc_trace_for_server(
&mut self, &mut self,
client_id: SessionId, session_id: SessionId,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let rpc_log = self.log_store.update(cx, |log_store, _| { let rpc_log = self.log_store.update(cx, |log_store, _| {
log_store log_store
.rpc_messages_for_client(client_id) .rpc_messages_for_session(session_id)
.map(|state| log_contents(&state)) .map(|state| log_contents(state.iter().cloned()))
}); });
if let Some(rpc_log) = rpc_log { 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 (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
let language = self.project.read(cx).languages().language_for_name("JSON"); let language = self.project.read(cx).languages().language_for_name("JSON");
editor editor
@ -626,17 +746,17 @@ impl DapLogView {
fn show_log_messages_for_adapter( fn show_log_messages_for_adapter(
&mut self, &mut self,
client_id: SessionId, session_id: SessionId,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let message_log = self.log_store.update(cx, |log_store, _| { let message_log = self.log_store.update(cx, |log_store, _| {
log_store log_store
.log_messages_for_client(client_id) .log_messages_for_session(session_id)
.map(|state| log_contents(&state)) .map(|state| log_contents(state.iter().cloned()))
}); });
if let Some(message_log) = message_log { 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); let (editor, editor_subscriptions) = Self::editor_for_logs(message_log, window, cx);
editor editor
.read(cx) .read(cx)
@ -652,14 +772,53 @@ impl DapLogView {
cx.focus_self(window); cx.focus_self(window);
} }
fn show_initialization_sequence_for_server(
&mut self,
session_id: SessionId,
window: &mut Window,
cx: &mut Context<Self>,
) {
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>) -> String { fn log_contents(lines: impl Iterator<Item = SharedString>) -> String {
let (a, b) = lines.as_slices(); lines.fold(String::new(), |mut acc, el| {
let a = a.iter().map(move |v| v.as_ref()); acc.push_str(&el);
let b = b.iter().map(move |v| v.as_ref());
a.chain(b).fold(String::new(), |mut acc, el| {
acc.push_str(el);
acc.push('\n'); acc.push('\n');
acc acc
}) })
@ -667,14 +826,15 @@ fn log_contents(lines: &VecDeque<String>) -> String {
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub(crate) struct DapMenuItem { pub(crate) struct DapMenuItem {
pub client_id: SessionId, pub session_id: SessionId,
pub client_name: String, pub adapter_name: DebugAdapterName,
pub has_adapter_logs: bool, pub has_adapter_logs: bool,
pub selected_entry: LogKind, pub selected_entry: LogKind,
} }
const ADAPTER_LOGS: &str = "Adapter Logs"; const ADAPTER_LOGS: &str = "Adapter Logs";
const RPC_MESSAGES: &str = "RPC Messages"; const RPC_MESSAGES: &str = "RPC Messages";
const INITIALIZATION_SEQUENCE: &str = "Initialization Sequence";
impl Render for DapLogView { impl Render for DapLogView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
@ -836,7 +996,7 @@ impl Focusable for DapLogView {
pub enum Event { pub enum Event {
NewLogEntry { NewLogEntry {
id: SessionId, id: SessionId,
entry: String, entry: SharedString,
kind: LogKind, kind: LogKind,
}, },
} }
@ -849,12 +1009,16 @@ impl EventEmitter<SearchEvent> for DapLogView {}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
impl LogStore { impl LogStore {
pub fn contained_session_ids(&self) -> Vec<SessionId> { pub fn contained_session_ids(&self) -> Vec<SessionId> {
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<String> { pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
self.debug_clients self.debug_sessions
.get(&session_id) .iter()
.find(|adapter_state| adapter_state.id == session_id)
.expect("This session should exist if a test is calling") .expect("This session should exist if a test is calling")
.rpc_messages .rpc_messages
.messages .messages
@ -862,9 +1026,10 @@ impl LogStore {
.into() .into()
} }
pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<String> { pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
self.debug_clients self.debug_sessions
.get(&session_id) .iter()
.find(|adapter_state| adapter_state.id == session_id)
.expect("This session should exist if a test is calling") .expect("This session should exist if a test is calling")
.log_messages .log_messages
.clone() .clone()

View file

@ -706,6 +706,8 @@ impl DapStore {
let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx)); let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
cx.emit(DapStoreEvent::DebugClientShutdown(session_id));
cx.background_spawn(async move { cx.background_spawn(async move {
if shutdown_children.len() > 0 { if shutdown_children.len() > 0 {
let _ = join_all(shutdown_children).await; let _ = join_all(shutdown_children).await;