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:
parent
4425d58d72
commit
6650be8e0f
4 changed files with 347 additions and 165 deletions
|
@ -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<Editor>,
|
||||
focus_handle: FocusHandle,
|
||||
|
@ -43,9 +51,9 @@ struct DapLogView {
|
|||
|
||||
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)>,
|
||||
debug_sessions: VecDeque<DebugAdapterState>,
|
||||
rpc_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
|
||||
adapter_log_tx: UnboundedSender<(SessionId, IoKind, Option<SharedString>, SharedString)>,
|
||||
}
|
||||
|
||||
struct ProjectState {
|
||||
|
@ -53,13 +61,19 @@ struct ProjectState {
|
|||
}
|
||||
|
||||
struct DebugAdapterState {
|
||||
log_messages: VecDeque<String>,
|
||||
id: SessionId,
|
||||
log_messages: VecDeque<SharedString>,
|
||||
rpc_messages: RpcMessages,
|
||||
adapter_name: DebugAdapterName,
|
||||
has_adapter_logs: bool,
|
||||
is_terminated: bool,
|
||||
}
|
||||
|
||||
struct RpcMessages {
|
||||
messages: VecDeque<String>,
|
||||
messages: VecDeque<SharedString>,
|
||||
last_message_kind: Option<MessageKind>,
|
||||
initialization_sequence: Vec<SharedString>,
|
||||
last_init_message_kind: Option<MessageKind>,
|
||||
}
|
||||
|
||||
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>) -> 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| {
|
||||
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>, 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>,
|
||||
) {
|
||||
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(
|
||||
|
@ -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<SharedString>,
|
||||
message: SharedString,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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<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;
|
||||
};
|
||||
|
||||
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<String>,
|
||||
fn get_debug_adapter_entry(
|
||||
log_lines: &mut VecDeque<SharedString>,
|
||||
id: SessionId,
|
||||
message: String,
|
||||
message: SharedString,
|
||||
kind: LogKind,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
) -> 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<Session>,
|
||||
cx: &App,
|
||||
) -> Option<&mut DebugAdapterState> {
|
||||
let client_state = self
|
||||
.debug_clients
|
||||
.entry(client_id)
|
||||
.or_insert_with(DebugAdapterState::new);
|
||||
session_id: SessionId,
|
||||
session: Entity<Session>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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>) {
|
||||
self.debug_clients.remove(&client_id);
|
||||
fn clean_sessions(&mut self, cx: &mut Context<Self>) {
|
||||
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<String>> {
|
||||
Some(&mut self.debug_clients.get_mut(&client_id)?.log_messages)
|
||||
fn log_messages_for_session(
|
||||
&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>> {
|
||||
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<SharedString>> {
|
||||
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<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();
|
||||
};
|
||||
|
||||
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<Vec<DapMenuItem>> {
|
||||
let mut menu_items = self
|
||||
.project
|
||||
fn menu_items(&self, cx: &App) -> Vec<DapMenuItem> {
|
||||
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::<Vec<_>>();
|
||||
menu_items.sort_by_key(|item| item.client_id.0);
|
||||
Some(menu_items)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn show_rpc_trace_for_server(
|
||||
&mut self,
|
||||
client_id: SessionId,
|
||||
session_id: SessionId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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<Self>,
|
||||
) {
|
||||
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<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 {
|
||||
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<Item = SharedString>) -> 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>) -> 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<Self>) -> 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<SearchEvent> for DapLogView {}
|
|||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LogStore {
|
||||
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> {
|
||||
self.debug_clients
|
||||
.get(&session_id)
|
||||
pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
|
||||
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<String> {
|
||||
self.debug_clients
|
||||
.get(&session_id)
|
||||
pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<SharedString> {
|
||||
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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue