Debugger UI: Dynamic session contents (#28033)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-04-07 23:22:09 +02:00 committed by GitHub
parent fdaf2a27bf
commit 22b937f27f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 663 additions and 537 deletions

View file

@ -30,9 +30,8 @@ use task::DebugTaskDefinition;
use terminal_view::terminal_panel::TerminalPanel;
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
use workspace::{
Pane, Workspace,
Workspace,
dock::{DockPosition, Panel, PanelEvent},
pane,
};
pub enum DebugPanelEvent {
@ -55,11 +54,13 @@ pub enum DebugPanelEvent {
actions!(debug_panel, [ToggleFocus]);
pub struct DebugPanel {
size: Pixels,
pane: Entity<Pane>,
sessions: Vec<Entity<DebugSession>>,
active_session: Option<Entity<DebugSession>>,
/// This represents the last debug definition that was created in the new session modal
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
project: WeakEntity<Project>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
}
@ -72,36 +73,17 @@ impl DebugPanel {
cx.new(|cx| {
let project = workspace.project().clone();
let dap_store = project.read(cx).dap_store();
let pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
project.clone(),
Default::default(),
None,
gpui::NoAction.boxed_clone(),
window,
cx,
);
pane.set_can_split(None);
pane.set_can_navigate(true, cx);
pane.display_nav_history_buttons(None);
pane.set_should_display_tab_bar(|_window, _cx| false);
pane.set_close_pane_if_empty(true, cx);
pane
});
let _subscriptions = vec![
cx.observe(&pane, |_, _, cx| cx.notify()),
cx.subscribe_in(&pane, window, Self::handle_pane_event),
cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),
];
let _subscriptions =
vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
let debug_panel = Self {
pane,
size: px(300.),
sessions: vec![],
active_session: None,
_subscriptions,
past_debug_definition: None,
focus_handle: cx.focus_handle(),
project: project.downgrade(),
workspace: workspace.weak_handle(),
};
@ -130,7 +112,7 @@ impl DebugPanel {
cx.observe(&debug_panel, |_, debug_panel, cx| {
let (has_active_session, supports_restart, support_step_back) = debug_panel
.update(cx, |this, cx| {
this.active_session(cx)
this.active_session()
.map(|item| {
let running = item.read(cx).mode().as_running().cloned();
@ -192,11 +174,8 @@ impl DebugPanel {
})
}
pub fn active_session(&self, cx: &App) -> Option<Entity<DebugSession>> {
self.pane
.read(cx)
.active_item()
.and_then(|panel| panel.downcast::<DebugSession>())
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
self.active_session.clone()
}
pub fn debug_panel_items_by_client(
@ -204,10 +183,8 @@ impl DebugPanel {
client_id: &SessionId,
cx: &Context<Self>,
) -> Vec<Entity<DebugSession>> {
self.pane
.read(cx)
.items()
.filter_map(|item| item.downcast::<DebugSession>())
self.sessions
.iter()
.filter(|item| item.read(cx).session_id(cx) == Some(*client_id))
.map(|item| item.clone())
.collect()
@ -218,15 +195,14 @@ impl DebugPanel {
client_id: SessionId,
cx: &mut Context<Self>,
) -> Option<Entity<DebugSession>> {
self.pane
.read(cx)
.items()
.filter_map(|item| item.downcast::<DebugSession>())
self.sessions
.iter()
.find(|item| {
let item = item.read(cx);
item.session_id(cx) == Some(client_id)
})
.cloned()
}
fn handle_dap_store_event(
@ -248,10 +224,11 @@ impl DebugPanel {
return log::error!("Debug Panel out lived it's weak reference to Project");
};
if self.pane.read_with(cx, |pane, cx| {
pane.items_of_type::<DebugSession>()
.any(|item| item.read(cx).session_id(cx) == Some(*session_id))
}) {
if self
.sessions
.iter()
.any(|item| item.read(cx).session_id(cx) == Some(*session_id))
{
// We already have an item for this session.
return;
}
@ -264,11 +241,8 @@ impl DebugPanel {
cx,
);
self.pane.update(cx, |pane, cx| {
pane.add_item(Box::new(session_item), true, true, None, window, cx);
window.focus(&pane.focus_handle(cx));
cx.notify();
});
self.sessions.push(session_item.clone());
self.activate_session(session_item, window, cx);
}
dap_store::DapStoreEvent::RunInTerminal {
title,
@ -362,63 +336,9 @@ impl DebugPanel {
})
}
fn handle_pane_event(
&mut self,
_: &Entity<Pane>,
event: &pane::Event,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
pane::Event::Remove { .. } => cx.emit(PanelEvent::Close),
pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
pane::Event::AddItem { item } => {
self.workspace
.update(cx, |workspace, cx| {
item.added_to_pane(workspace, self.pane.clone(), window, cx)
})
.ok();
}
pane::Event::RemovedItem { item } => {
if let Some(debug_session) = item.downcast::<DebugSession>() {
debug_session.update(cx, |session, cx| {
session.shutdown(cx);
})
}
}
pane::Event::ActivateItem {
local: _,
focus_changed,
} => {
if *focus_changed {
if let Some(debug_session) = self
.pane
.read(cx)
.active_item()
.and_then(|item| item.downcast::<DebugSession>())
{
if let Some(running) = debug_session
.read_with(cx, |session, _| session.mode().as_running().cloned())
{
running.update(cx, |running, cx| {
running.go_to_selected_stack_frame(window, cx);
});
}
}
}
}
_ => {}
}
}
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let active_session = self
.pane
.read(cx)
.active_item()
.and_then(|item| item.downcast::<DebugSession>());
let active_session = self.active_session.clone();
Some(
h_flex()
.border_b_1()
@ -609,34 +529,29 @@ impl DebugPanel {
},
)
.when_some(active_session.as_ref(), |this, session| {
let pane = self.pane.downgrade();
let sessions = self.sessions.clone();
let weak = cx.weak_entity();
let label = session.read(cx).label(cx);
this.child(DropdownMenu::new(
"debugger-session-list",
label,
ContextMenu::build(window, cx, move |mut this, _, cx| {
let sessions = pane
.read_with(cx, |pane, _| {
pane.items().map(|item| item.boxed_clone()).collect()
})
.ok()
.unwrap_or_else(Vec::new);
for (index, item) in sessions.into_iter().enumerate() {
if let Some(session) = item.downcast::<DebugSession>() {
let pane = pane.clone();
this = this.entry(
session.read(cx).label(cx),
None,
move |window, cx| {
pane.update(cx, |pane, cx| {
pane.activate_item(
index, true, true, window, cx,
);
})
.ok();
},
);
}
for item in sessions {
let weak = weak.clone();
this = this.entry(
session.read(cx).label(cx),
None,
move |window, cx| {
weak.update(cx, |panel, cx| {
panel.activate_session(
item.clone(),
window,
cx,
);
})
.ok();
},
);
}
this
}),
@ -680,6 +595,25 @@ impl DebugPanel {
),
)
}
fn activate_session(
&mut self,
session_item: Entity<DebugSession>,
window: &mut Window,
cx: &mut Context<Self>,
) {
debug_assert!(self.sessions.contains(&session_item));
session_item.focus_handle(cx).focus(window);
session_item.update(cx, |this, cx| {
if let Some(running) = this.mode().as_running() {
running.update(cx, |this, cx| {
this.go_to_selected_stack_frame(window, cx);
});
}
});
self.active_session = Some(session_item);
cx.notify();
}
}
impl EventEmitter<PanelEvent> for DebugPanel {}
@ -687,16 +621,12 @@ impl EventEmitter<DebugPanelEvent> for DebugPanel {}
impl EventEmitter<project::Event> for DebugPanel {}
impl Focusable for DebugPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.pane.focus_handle(cx)
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Panel for DebugPanel {
fn pane(&self) -> Option<Entity<Pane>> {
Some(self.pane.clone())
}
fn persistent_name() -> &'static str {
"DebugPanel"
}
@ -753,7 +683,9 @@ impl Panel for DebugPanel {
impl Render for DebugPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_sessions = self.pane.read(cx).items_len() > 0;
let has_sessions = self.sessions.len() > 0;
debug_assert_eq!(has_sessions, self.active_session.is_some());
v_flex()
.size_full()
.key_context("DebugPanel")
@ -761,7 +693,7 @@ impl Render for DebugPanel {
.track_focus(&self.focus_handle(cx))
.map(|this| {
if has_sessions {
this.child(self.pane.clone())
this.children(self.active_session.clone())
} else {
this.child(
v_flex()