debugger: Improve appearance of session list for JavaScript debugging (#34322)
This PR updates the debugger panel's session list to be more useful in some cases that are commonly hit when using the JavaScript adapter. We make two adjustments, which only apply to JavaScript sessions: - For a child session that's the only child of a root session, we collapse it with its parent. This imitates what VS Code does in the "call stack" view for JavaScript sessions. - When a session has exactly one thread, we label the session with that thread's name, instead of the session label provided by the DAP. VS Code also makes this adjustment, which surfaces more useful information when working with browser sessions. Closes #33072 Release Notes: - debugger: Improved the appearance of JavaScript sessions in the debug panel's session list. --------- Co-authored-by: Julia <julia@zed.dev> Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
parent
13ddd5e4cb
commit
a8cc927303
11 changed files with 392 additions and 236 deletions
|
@ -9,6 +9,7 @@ use crate::{
|
|||
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::IndexMap;
|
||||
use dap::adapters::DebugAdapterName;
|
||||
use dap::debugger_settings::DebugPanelDockPosition;
|
||||
use dap::{
|
||||
|
@ -26,7 +27,7 @@ use text::ToPoint as _;
|
|||
|
||||
use itertools::Itertools as _;
|
||||
use language::Buffer;
|
||||
use project::debugger::session::{Session, SessionStateEvent};
|
||||
use project::debugger::session::{Session, SessionQuirks, SessionStateEvent};
|
||||
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
|
||||
use project::{Project, debugger::session::ThreadStatus};
|
||||
use rpc::proto::{self};
|
||||
|
@ -63,13 +64,14 @@ pub enum DebugPanelEvent {
|
|||
|
||||
pub struct DebugPanel {
|
||||
size: Pixels,
|
||||
sessions: Vec<Entity<DebugSession>>,
|
||||
active_session: Option<Entity<DebugSession>>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
debug_scenario_scheduled_last: bool,
|
||||
pub(crate) sessions_with_children:
|
||||
IndexMap<Entity<DebugSession>, Vec<WeakEntity<DebugSession>>>,
|
||||
pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
fs: Arc<dyn Fs>,
|
||||
|
@ -100,7 +102,7 @@ impl DebugPanel {
|
|||
|
||||
Self {
|
||||
size: px(300.),
|
||||
sessions: vec![],
|
||||
sessions_with_children: Default::default(),
|
||||
active_session: None,
|
||||
focus_handle,
|
||||
breakpoint_list: BreakpointList::new(
|
||||
|
@ -138,8 +140,9 @@ impl DebugPanel {
|
|||
});
|
||||
}
|
||||
|
||||
pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
|
||||
self.sessions.clone()
|
||||
#[cfg(test)]
|
||||
pub(crate) fn sessions(&self) -> impl Iterator<Item = Entity<DebugSession>> {
|
||||
self.sessions_with_children.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||
|
@ -185,12 +188,20 @@ impl DebugPanel {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let dap_store = self.project.read(cx).dap_store();
|
||||
let Some(adapter) = DapRegistry::global(cx).adapter(&scenario.adapter) else {
|
||||
return;
|
||||
};
|
||||
let quirks = SessionQuirks {
|
||||
compact: adapter.compact_child_session(),
|
||||
prefer_thread_name: adapter.prefer_thread_name(),
|
||||
};
|
||||
let session = dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.new_session(
|
||||
scenario.label.clone(),
|
||||
Some(scenario.label.clone()),
|
||||
DebugAdapterName(scenario.adapter.clone()),
|
||||
task_context.clone(),
|
||||
None,
|
||||
quirks,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -363,14 +374,15 @@ impl DebugPanel {
|
|||
};
|
||||
|
||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||
let label = curr_session.read(cx).label().clone();
|
||||
let label = curr_session.read(cx).label();
|
||||
let quirks = curr_session.read(cx).quirks();
|
||||
let adapter = curr_session.read(cx).adapter().clone();
|
||||
let binary = curr_session.read(cx).binary().cloned().unwrap();
|
||||
let task_context = curr_session.read(cx).task_context().clone();
|
||||
|
||||
let curr_session_id = curr_session.read(cx).session_id();
|
||||
self.sessions
|
||||
.retain(|session| session.read(cx).session_id(cx) != curr_session_id);
|
||||
self.sessions_with_children
|
||||
.retain(|session, _| session.read(cx).session_id(cx) != curr_session_id);
|
||||
let task = dap_store_handle.update(cx, |dap_store, cx| {
|
||||
dap_store.shutdown_session(curr_session_id, cx)
|
||||
});
|
||||
|
@ -379,7 +391,7 @@ impl DebugPanel {
|
|||
task.await.log_err();
|
||||
|
||||
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
||||
let session = dap_store.new_session(label, adapter, task_context, None, cx);
|
||||
let session = dap_store.new_session(label, adapter, task_context, None, quirks, cx);
|
||||
|
||||
let task = session.update(cx, |session, cx| {
|
||||
session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
|
||||
|
@ -425,6 +437,7 @@ impl DebugPanel {
|
|||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||
let label = self.label_for_child_session(&parent_session, request, cx);
|
||||
let adapter = parent_session.read(cx).adapter().clone();
|
||||
let quirks = parent_session.read(cx).quirks();
|
||||
let Some(mut binary) = parent_session.read(cx).binary().cloned() else {
|
||||
log::error!("Attempted to start a child-session without a binary");
|
||||
return;
|
||||
|
@ -438,6 +451,7 @@ impl DebugPanel {
|
|||
adapter,
|
||||
task_context,
|
||||
Some(parent_session.clone()),
|
||||
quirks,
|
||||
cx,
|
||||
);
|
||||
|
||||
|
@ -463,8 +477,8 @@ impl DebugPanel {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
.iter()
|
||||
.sessions_with_children
|
||||
.keys()
|
||||
.find(|other| entity_id == other.entity_id())
|
||||
.cloned()
|
||||
else {
|
||||
|
@ -498,15 +512,14 @@ impl DebugPanel {
|
|||
}
|
||||
session.update(cx, |session, cx| session.shutdown(cx)).ok();
|
||||
this.update(cx, |this, cx| {
|
||||
this.sessions.retain(|other| entity_id != other.entity_id());
|
||||
|
||||
this.retain_sessions(|other| entity_id != other.entity_id());
|
||||
if let Some(active_session_id) = this
|
||||
.active_session
|
||||
.as_ref()
|
||||
.map(|session| session.entity_id())
|
||||
{
|
||||
if active_session_id == entity_id {
|
||||
this.active_session = this.sessions.first().cloned();
|
||||
this.active_session = this.sessions_with_children.keys().next().cloned();
|
||||
}
|
||||
}
|
||||
cx.notify()
|
||||
|
@ -976,8 +989,8 @@ impl DebugPanel {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(session) = self
|
||||
.sessions
|
||||
.iter()
|
||||
.sessions_with_children
|
||||
.keys()
|
||||
.find(|session| session.read(cx).session_id(cx) == session_id)
|
||||
{
|
||||
self.activate_session(session.clone(), window, cx);
|
||||
|
@ -990,7 +1003,7 @@ impl DebugPanel {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
debug_assert!(self.sessions.contains(&session_item));
|
||||
debug_assert!(self.sessions_with_children.contains_key(&session_item));
|
||||
session_item.focus_handle(cx).focus(window);
|
||||
session_item.update(cx, |this, cx| {
|
||||
this.running_state().update(cx, |this, cx| {
|
||||
|
@ -1261,18 +1274,27 @@ impl DebugPanel {
|
|||
parent_session: &Entity<Session>,
|
||||
request: &StartDebuggingRequestArguments,
|
||||
cx: &mut Context<'_, Self>,
|
||||
) -> SharedString {
|
||||
) -> Option<SharedString> {
|
||||
let adapter = parent_session.read(cx).adapter();
|
||||
if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
|
||||
if let Some(label) = adapter.label_for_child_session(request) {
|
||||
return label.into();
|
||||
return Some(label.into());
|
||||
}
|
||||
}
|
||||
let mut label = parent_session.read(cx).label().clone();
|
||||
if !label.ends_with("(child)") {
|
||||
label = format!("{label} (child)").into();
|
||||
None
|
||||
}
|
||||
|
||||
fn retain_sessions(&mut self, keep: impl Fn(&Entity<DebugSession>) -> bool) {
|
||||
self.sessions_with_children
|
||||
.retain(|session, _| keep(session));
|
||||
for children in self.sessions_with_children.values_mut() {
|
||||
children.retain(|child| {
|
||||
let Some(child) = child.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
keep(&child)
|
||||
});
|
||||
}
|
||||
label
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1302,11 +1324,11 @@ async fn register_session_inner(
|
|||
let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
|
||||
let debug_session = this.update_in(cx, |this, window, cx| {
|
||||
let parent_session = this
|
||||
.sessions
|
||||
.iter()
|
||||
.sessions_with_children
|
||||
.keys()
|
||||
.find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
|
||||
.cloned();
|
||||
this.sessions.retain(|session| {
|
||||
this.retain_sessions(|session| {
|
||||
!session
|
||||
.read(cx)
|
||||
.running_state()
|
||||
|
@ -1337,13 +1359,23 @@ async fn register_session_inner(
|
|||
)
|
||||
.detach();
|
||||
let insert_position = this
|
||||
.sessions
|
||||
.iter()
|
||||
.sessions_with_children
|
||||
.keys()
|
||||
.position(|session| Some(session) == parent_session.as_ref())
|
||||
.map(|position| position + 1)
|
||||
.unwrap_or(this.sessions.len());
|
||||
.unwrap_or(this.sessions_with_children.len());
|
||||
// Maintain topological sort order of sessions
|
||||
this.sessions.insert(insert_position, debug_session.clone());
|
||||
let (_, old) = this.sessions_with_children.insert_before(
|
||||
insert_position,
|
||||
debug_session.clone(),
|
||||
Default::default(),
|
||||
);
|
||||
debug_assert!(old.is_none());
|
||||
if let Some(parent_session) = parent_session {
|
||||
this.sessions_with_children
|
||||
.entry(parent_session)
|
||||
.and_modify(|children| children.push(debug_session.downgrade()));
|
||||
}
|
||||
|
||||
debug_session
|
||||
})?;
|
||||
|
@ -1383,7 +1415,7 @@ impl Panel for DebugPanel {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if position.axis() != self.position(window, cx).axis() {
|
||||
self.sessions.iter().for_each(|session_item| {
|
||||
self.sessions_with_children.keys().for_each(|session_item| {
|
||||
session_item.update(cx, |item, cx| {
|
||||
item.running_state()
|
||||
.update(cx, |state, _| state.invert_axies())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue