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
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue