{
let active_session = self.active_session.clone();
@@ -897,11 +951,49 @@ impl Render for DebugPanel {
let has_sessions = self.sessions.len() > 0;
debug_assert_eq!(has_sessions, self.active_session.is_some());
+ if self
+ .active_session
+ .as_ref()
+ .and_then(|session| session.read(cx).mode().as_running().cloned())
+ .map(|state| state.read(cx).has_open_context_menu(cx))
+ .unwrap_or(false)
+ {
+ self.context_menu.take();
+ }
+
v_flex()
.size_full()
.key_context("DebugPanel")
.child(h_flex().children(self.top_controls_strip(window, cx)))
.track_focus(&self.focus_handle(cx))
+ .when(self.active_session.is_some(), |this| {
+ this.on_mouse_down(
+ MouseButton::Right,
+ cx.listener(|this, event: &MouseDownEvent, window, cx| {
+ if this
+ .active_session
+ .as_ref()
+ .and_then(|session| {
+ session.read(cx).mode().as_running().map(|state| {
+ state.read(cx).has_pane_at_position(event.position)
+ })
+ })
+ .unwrap_or(false)
+ {
+ this.deploy_context_menu(event.position, window, cx);
+ }
+ }),
+ )
+ .children(self.context_menu.as_ref().map(|(menu, position, _)| {
+ deferred(
+ anchored()
+ .position(*position)
+ .anchor(gpui::Corner::TopLeft)
+ .child(menu.clone()),
+ )
+ .with_priority(1)
+ }))
+ })
.map(|this| {
if has_sessions {
this.children(self.active_session.clone())
diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs
index f77a723adf326f057e52f1acfbe986d839175abd..283efd0d76ce78d5e27fac01c8f34c832c4801c3 100644
--- a/crates/debugger_ui/src/persistence.rs
+++ b/crates/debugger_ui/src/persistence.rs
@@ -1,4 +1,5 @@
use collections::HashMap;
+use dap::Capabilities;
use db::kvp::KEY_VALUE_STORE;
use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
use project::Project;
@@ -9,19 +10,43 @@ use workspace::{Member, Pane, PaneAxis, Workspace};
use crate::session::running::{
self, RunningState, SubView, breakpoint_list::BreakpointList, console::Console,
- module_list::ModuleList, stack_frame_list::StackFrameList, variable_list::VariableList,
+ loaded_source_list::LoadedSourceList, module_list::ModuleList,
+ stack_frame_list::StackFrameList, variable_list::VariableList,
};
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Hash, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum DebuggerPaneItem {
Console,
Variables,
BreakpointList,
Frames,
Modules,
+ LoadedSources,
}
impl DebuggerPaneItem {
+ pub(crate) fn all() -> &'static [DebuggerPaneItem] {
+ static VARIANTS: &[DebuggerPaneItem] = &[
+ DebuggerPaneItem::Console,
+ DebuggerPaneItem::Variables,
+ DebuggerPaneItem::BreakpointList,
+ DebuggerPaneItem::Frames,
+ DebuggerPaneItem::Modules,
+ DebuggerPaneItem::LoadedSources,
+ ];
+ VARIANTS
+ }
+
+ pub(crate) fn is_supported(&self, capabilities: &Capabilities) -> bool {
+ match self {
+ DebuggerPaneItem::Modules => capabilities.supports_modules_request.unwrap_or_default(),
+ DebuggerPaneItem::LoadedSources => capabilities
+ .supports_loaded_sources_request
+ .unwrap_or_default(),
+ _ => true,
+ }
+ }
+
pub(crate) fn to_shared_string(self) -> SharedString {
match self {
DebuggerPaneItem::Console => SharedString::new_static("Console"),
@@ -29,10 +54,17 @@ impl DebuggerPaneItem {
DebuggerPaneItem::BreakpointList => SharedString::new_static("Breakpoints"),
DebuggerPaneItem::Frames => SharedString::new_static("Frames"),
DebuggerPaneItem::Modules => SharedString::new_static("Modules"),
+ DebuggerPaneItem::LoadedSources => SharedString::new_static("Sources"),
}
}
}
+impl From
for SharedString {
+ fn from(item: DebuggerPaneItem) -> Self {
+ item.to_shared_string()
+ }
+}
+
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct SerializedAxis(pub Axis);
@@ -136,6 +168,7 @@ pub(crate) fn deserialize_pane_layout(
module_list: &Entity,
console: &Entity,
breakpoint_list: &Entity,
+ loaded_sources: &Entity,
subscriptions: &mut HashMap,
window: &mut Window,
cx: &mut Context,
@@ -157,6 +190,7 @@ pub(crate) fn deserialize_pane_layout(
module_list,
console,
breakpoint_list,
+ loaded_sources,
subscriptions,
window,
cx,
@@ -191,7 +225,7 @@ pub(crate) fn deserialize_pane_layout(
.iter()
.map(|child| match child {
DebuggerPaneItem::Frames => Box::new(SubView::new(
- pane.focus_handle(cx),
+ stack_frame_list.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
None,
@@ -212,13 +246,19 @@ pub(crate) fn deserialize_pane_layout(
cx,
)),
DebuggerPaneItem::Modules => Box::new(SubView::new(
- pane.focus_handle(cx),
+ module_list.focus_handle(cx),
module_list.clone().into(),
DebuggerPaneItem::Modules,
None,
cx,
)),
-
+ DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
+ loaded_sources.focus_handle(cx),
+ loaded_sources.clone().into(),
+ DebuggerPaneItem::LoadedSources,
+ None,
+ cx,
+ )),
DebuggerPaneItem::Console => Box::new(SubView::new(
pane.focus_handle(cx),
console.clone().into(),
diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs
index 80ce42d2d603c10470718256e86cbf57f0183185..a099d70048f08c0ee81cc2758247c608d5ae2349 100644
--- a/crates/debugger_ui/src/session/running.rs
+++ b/crates/debugger_ui/src/session/running.rs
@@ -11,12 +11,12 @@ use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
use super::DebugPanelItemEvent;
use breakpoint_list::BreakpointList;
-use collections::HashMap;
+use collections::{HashMap, IndexMap};
use console::Console;
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
use gpui::{
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
- NoAction, Subscription, Task, WeakEntity,
+ NoAction, Pixels, Point, Subscription, Task, WeakEntity,
};
use loaded_source_list::LoadedSourceList;
use module_list::ModuleList;
@@ -49,8 +49,10 @@ pub struct RunningState {
variable_list: Entity,
_subscriptions: Vec,
stack_frame_list: Entity,
- _module_list: Entity,
+ loaded_sources_list: Entity,
+ module_list: Entity,
_console: Entity,
+ breakpoint_list: Entity,
panes: PaneGroup,
pane_close_subscriptions: HashMap,
_schedule_serialize: Option>,
@@ -383,7 +385,6 @@ impl RunningState {
let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
- #[expect(unused)]
let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
let console = cx.new(|cx| {
@@ -396,7 +397,7 @@ impl RunningState {
)
});
- let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
+ let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
let _subscriptions = vec![
cx.observe(&module_list, |_, _, cx| cx.notify()),
@@ -436,7 +437,8 @@ impl RunningState {
&variable_list,
&module_list,
&console,
- &breakpoints,
+ &breakpoint_list,
+ &loaded_source_list,
&mut pane_close_subscriptions,
window,
cx,
@@ -452,7 +454,7 @@ impl RunningState {
&variable_list,
&module_list,
&console,
- breakpoints,
+ &breakpoint_list,
&mut pane_close_subscriptions,
window,
cx,
@@ -472,13 +474,139 @@ impl RunningState {
stack_frame_list,
session_id,
panes,
- _module_list: module_list,
+ module_list,
_console: console,
+ breakpoint_list,
+ loaded_sources_list: loaded_source_list,
pane_close_subscriptions,
_schedule_serialize: None,
}
}
+ pub(crate) fn remove_pane_item(
+ &mut self,
+ item_kind: DebuggerPaneItem,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ debug_assert!(
+ item_kind.is_supported(self.session.read(cx).capabilities()),
+ "We should only allow removing supported item kinds"
+ );
+
+ if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
+ Some(pane).zip(
+ pane.read(cx)
+ .items()
+ .find(|item| {
+ item.act_as::(cx)
+ .is_some_and(|view| view.read(cx).kind == item_kind)
+ })
+ .map(|item| item.item_id()),
+ )
+ }) {
+ pane.update(cx, |pane, cx| {
+ pane.remove_item(item_id, false, true, window, cx)
+ })
+ }
+ }
+
+ pub(crate) fn has_pane_at_position(&self, position: Point) -> bool {
+ self.panes.pane_at_pixel_position(position).is_some()
+ }
+
+ pub(crate) fn add_pane_item(
+ &mut self,
+ item_kind: DebuggerPaneItem,
+ position: Point,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ debug_assert!(
+ item_kind.is_supported(self.session.read(cx).capabilities()),
+ "We should only allow adding supported item kinds"
+ );
+
+ if let Some(pane) = self.panes.pane_at_pixel_position(position) {
+ let sub_view = match item_kind {
+ DebuggerPaneItem::Console => {
+ let weak_console = self._console.clone().downgrade();
+
+ Box::new(SubView::new(
+ pane.focus_handle(cx),
+ self._console.clone().into(),
+ item_kind,
+ Some(Box::new(move |cx| {
+ weak_console
+ .read_with(cx, |console, cx| console.show_indicator(cx))
+ .unwrap_or_default()
+ })),
+ cx,
+ ))
+ }
+ DebuggerPaneItem::Variables => Box::new(SubView::new(
+ self.variable_list.focus_handle(cx),
+ self.variable_list.clone().into(),
+ item_kind,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
+ self.breakpoint_list.focus_handle(cx),
+ self.breakpoint_list.clone().into(),
+ item_kind,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::Frames => Box::new(SubView::new(
+ self.stack_frame_list.focus_handle(cx),
+ self.stack_frame_list.clone().into(),
+ item_kind,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::Modules => Box::new(SubView::new(
+ self.module_list.focus_handle(cx),
+ self.module_list.clone().into(),
+ item_kind,
+ None,
+ cx,
+ )),
+ DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
+ self.loaded_sources_list.focus_handle(cx),
+ self.loaded_sources_list.clone().into(),
+ item_kind,
+ None,
+ cx,
+ )),
+ };
+
+ pane.update(cx, |pane, cx| {
+ pane.add_item(sub_view, false, false, None, window, cx);
+ })
+ }
+ }
+
+ pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap {
+ let caps = self.session.read(cx).capabilities();
+ let mut pane_item_status = IndexMap::from_iter(
+ DebuggerPaneItem::all()
+ .iter()
+ .filter(|kind| kind.is_supported(&caps))
+ .map(|kind| (*kind, false)),
+ );
+ self.panes.panes().iter().for_each(|pane| {
+ pane.read(cx)
+ .items()
+ .filter_map(|item| item.act_as::(cx))
+ .for_each(|view| {
+ pane_item_status.insert(view.read(cx).kind, true);
+ });
+ });
+
+ pane_item_status
+ }
+
pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context) {
if self._schedule_serialize.is_none() {
self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
@@ -533,6 +661,10 @@ impl RunningState {
}
}
+ pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
+ self.variable_list.read(cx).has_open_context_menu()
+ }
+
pub fn session(&self) -> &Entity {
&self.session
}
@@ -557,7 +689,7 @@ impl RunningState {
#[cfg(test)]
pub(crate) fn module_list(&self) -> &Entity {
- &self._module_list
+ &self.module_list
}
#[cfg(test)]
@@ -793,7 +925,7 @@ impl RunningState {
variable_list: &Entity,
module_list: &Entity,
console: &Entity,
- breakpoints: Entity,
+ breakpoints: &Entity,
subscriptions: &mut HashMap,
window: &mut Window,
cx: &mut Context<'_, RunningState>,
@@ -817,7 +949,7 @@ impl RunningState {
this.add_item(
Box::new(SubView::new(
breakpoints.focus_handle(cx),
- breakpoints.into(),
+ breakpoints.clone().into(),
DebuggerPaneItem::BreakpointList,
None,
cx,
diff --git a/crates/debugger_ui/src/session/running/loaded_source_list.rs b/crates/debugger_ui/src/session/running/loaded_source_list.rs
index 0c4ad90a759a81a49679f060d3a3d050d4a5ec62..dd5487e0426ac8386a6af04d27e30d786bb29eaf 100644
--- a/crates/debugger_ui/src/session/running/loaded_source_list.rs
+++ b/crates/debugger_ui/src/session/running/loaded_source_list.rs
@@ -3,7 +3,7 @@ use project::debugger::session::{Session, SessionEvent};
use ui::prelude::*;
use util::maybe;
-pub struct LoadedSourceList {
+pub(crate) struct LoadedSourceList {
list: ListState,
invalidate: bool,
focus_handle: FocusHandle,
diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs
index 05352b3b1737b6451629a5587ceddeb7b0a1fa65..75a041e1b20f12e74369ff0fd31fd5b89a35104d 100644
--- a/crates/debugger_ui/src/session/running/variable_list.rs
+++ b/crates/debugger_ui/src/session/running/variable_list.rs
@@ -194,6 +194,10 @@ impl VariableList {
}
}
+ pub(super) fn has_open_context_menu(&self) -> bool {
+ self.open_context_menu.is_some()
+ }
+
fn build_entries(&mut self, cx: &mut Context) {
let Some(stack_frame_id) = self.selected_stack_frame_id else {
return;