debugger: Remember pane layout from previous debugger session (#28692)
This PR makes a debugger's pane layout persistent across session's that use the same debug adapter. Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
parent
b794919842
commit
d4761cea47
13 changed files with 550 additions and 150 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4177,6 +4177,7 @@ dependencies = [
|
|||
"collections",
|
||||
"command_palette_hooks",
|
||||
"dap",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"feature_flags",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use sqlez_macros::sql;
|
||||
|
||||
use crate::{define_connection, query};
|
||||
pub static DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
|
||||
|
||||
define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
|
||||
&[sql!(
|
||||
|
|
|
@ -28,6 +28,7 @@ client.workspace = true
|
|||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
dap.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,
|
||||
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||
StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence,
|
||||
};
|
||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||
use anyhow::{Result, anyhow};
|
||||
|
@ -293,35 +293,49 @@ impl DebugPanel {
|
|||
);
|
||||
};
|
||||
|
||||
let Some(project) = self.project.upgrade() else {
|
||||
return log::error!("Debug Panel out lived it's weak reference to Project");
|
||||
};
|
||||
let adapter_name = session.read(cx).adapter_name();
|
||||
|
||||
if self
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|item| item.read(cx).session_id(cx) == *session_id)
|
||||
{
|
||||
// We already have an item for this session.
|
||||
return;
|
||||
}
|
||||
let session_item = DebugSession::running(
|
||||
project,
|
||||
self.workspace.clone(),
|
||||
session,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let session_id = *session_id;
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let serialized_layout =
|
||||
persistence::get_serialized_pane_layout(adapter_name).await;
|
||||
|
||||
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
||||
// We might want to make this an event subscription and only notify when a new thread is selected
|
||||
// This is used to filter the command menu correctly
|
||||
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let Some(project) = this.project.upgrade() else {
|
||||
return log::error!(
|
||||
"Debug Panel out lived it's weak reference to Project"
|
||||
);
|
||||
};
|
||||
|
||||
self.sessions.push(session_item.clone());
|
||||
self.activate_session(session_item, window, cx);
|
||||
if this
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|item| item.read(cx).session_id(cx) == session_id)
|
||||
{
|
||||
// We already have an item for this session.
|
||||
return;
|
||||
}
|
||||
let session_item = DebugSession::running(
|
||||
project,
|
||||
this.workspace.clone(),
|
||||
session,
|
||||
cx.weak_entity(),
|
||||
serialized_layout,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
||||
// We might want to make this an event subscription and only notify when a new thread is selected
|
||||
// This is used to filter the command menu correctly
|
||||
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
|
||||
this.sessions.push(session_item.clone());
|
||||
this.activate_session(session_item, window, cx);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
dap_store::DapStoreEvent::RunInTerminal {
|
||||
title,
|
||||
|
|
|
@ -13,6 +13,7 @@ use workspace::{ShutdownDebugAdapters, Workspace};
|
|||
pub mod attach_modal;
|
||||
pub mod debugger_panel;
|
||||
mod new_session_modal;
|
||||
mod persistence;
|
||||
pub(crate) mod session;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
259
crates/debugger_ui/src/persistence.rs
Normal file
259
crates/debugger_ui/src/persistence.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window};
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::{App, SharedString};
|
||||
use util::ResultExt;
|
||||
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,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub(crate) enum DebuggerPaneItem {
|
||||
Console,
|
||||
Variables,
|
||||
BreakpointList,
|
||||
Frames,
|
||||
Modules,
|
||||
}
|
||||
|
||||
impl DebuggerPaneItem {
|
||||
pub(crate) fn to_shared_string(self) -> SharedString {
|
||||
match self {
|
||||
DebuggerPaneItem::Console => SharedString::new_static("Console"),
|
||||
DebuggerPaneItem::Variables => SharedString::new_static("Variables"),
|
||||
DebuggerPaneItem::BreakpointList => SharedString::new_static("Breakpoints"),
|
||||
DebuggerPaneItem::Frames => SharedString::new_static("Frames"),
|
||||
DebuggerPaneItem::Modules => SharedString::new_static("Modules"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct SerializedAxis(pub Axis);
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) enum SerializedPaneLayout {
|
||||
Pane(SerializedPane),
|
||||
Group {
|
||||
axis: SerializedAxis,
|
||||
flexes: Option<Vec<f32>>,
|
||||
children: Vec<SerializedPaneLayout>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct SerializedPane {
|
||||
pub children: Vec<DebuggerPaneItem>,
|
||||
pub active_item: Option<DebuggerPaneItem>,
|
||||
}
|
||||
|
||||
pub(crate) async fn serialize_pane_layout(
|
||||
adapter_name: SharedString,
|
||||
pane_group: SerializedPaneLayout,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
|
||||
KEY_VALUE_STORE
|
||||
.write_kvp(
|
||||
format!("{}-{adapter_name}", db::kvp::DEBUGGER_PANEL_PREFIX),
|
||||
serialized_pane_group,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Failed to serialize pane group with serde_json as a string"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_serialized_pane_layout(
|
||||
pane_group: &Member,
|
||||
cx: &mut App,
|
||||
) -> SerializedPaneLayout {
|
||||
match pane_group {
|
||||
Member::Axis(PaneAxis {
|
||||
axis,
|
||||
members,
|
||||
flexes,
|
||||
bounding_boxes: _,
|
||||
}) => SerializedPaneLayout::Group {
|
||||
axis: SerializedAxis(*axis),
|
||||
children: members
|
||||
.iter()
|
||||
.map(|member| build_serialized_pane_layout(member, cx))
|
||||
.collect::<Vec<_>>(),
|
||||
flexes: Some(flexes.lock().clone()),
|
||||
},
|
||||
Member::Pane(pane_handle) => SerializedPaneLayout::Pane(serialize_pane(pane_handle, cx)),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_pane(pane: &Entity<Pane>, cx: &mut App) -> SerializedPane {
|
||||
let pane = pane.read(cx);
|
||||
let children = pane
|
||||
.items()
|
||||
.filter_map(|item| {
|
||||
item.act_as::<SubView>(cx)
|
||||
.map(|view| view.read(cx).view_kind())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let active_item = pane
|
||||
.active_item()
|
||||
.and_then(|item| item.act_as::<SubView>(cx))
|
||||
.map(|view| view.read(cx).view_kind());
|
||||
|
||||
SerializedPane {
|
||||
children,
|
||||
active_item,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_serialized_pane_layout(
|
||||
adapter_name: impl AsRef<str>,
|
||||
) -> Option<SerializedPaneLayout> {
|
||||
let key = format!(
|
||||
"{}-{}",
|
||||
db::kvp::DEBUGGER_PANEL_PREFIX,
|
||||
adapter_name.as_ref()
|
||||
);
|
||||
|
||||
KEY_VALUE_STORE
|
||||
.read_kvp(&key)
|
||||
.log_err()
|
||||
.flatten()
|
||||
.and_then(|value| serde_json::from_str::<SerializedPaneLayout>(&value).ok())
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_pane_layout(
|
||||
serialized: SerializedPaneLayout,
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
project: &Entity<Project>,
|
||||
stack_frame_list: &Entity<StackFrameList>,
|
||||
variable_list: &Entity<VariableList>,
|
||||
module_list: &Entity<ModuleList>,
|
||||
console: &Entity<Console>,
|
||||
breakpoint_list: &Entity<BreakpointList>,
|
||||
subscriptions: &mut HashMap<EntityId, Subscription>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<RunningState>,
|
||||
) -> Option<Member> {
|
||||
match serialized {
|
||||
SerializedPaneLayout::Group {
|
||||
axis,
|
||||
flexes,
|
||||
children,
|
||||
} => {
|
||||
let mut members = Vec::new();
|
||||
for child in children {
|
||||
if let Some(new_member) = deserialize_pane_layout(
|
||||
child,
|
||||
workspace,
|
||||
project,
|
||||
stack_frame_list,
|
||||
variable_list,
|
||||
module_list,
|
||||
console,
|
||||
breakpoint_list,
|
||||
subscriptions,
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
members.push(new_member);
|
||||
}
|
||||
}
|
||||
|
||||
if members.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if members.len() == 1 {
|
||||
return Some(members.remove(0));
|
||||
}
|
||||
|
||||
Some(Member::Axis(PaneAxis::load(
|
||||
axis.0,
|
||||
members,
|
||||
flexes.clone(),
|
||||
)))
|
||||
}
|
||||
SerializedPaneLayout::Pane(serialized_pane) => {
|
||||
let pane = running::new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
subscriptions.insert(
|
||||
pane.entity_id(),
|
||||
cx.subscribe_in(&pane, window, RunningState::handle_pane_event),
|
||||
);
|
||||
|
||||
let sub_views: Vec<_> = serialized_pane
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| match child {
|
||||
DebuggerPaneItem::Frames => Box::new(SubView::new(
|
||||
pane.focus_handle(cx),
|
||||
stack_frame_list.clone().into(),
|
||||
DebuggerPaneItem::Frames,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
DebuggerPaneItem::Variables => Box::new(SubView::new(
|
||||
variable_list.focus_handle(cx),
|
||||
variable_list.clone().into(),
|
||||
DebuggerPaneItem::Variables,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
|
||||
breakpoint_list.focus_handle(cx),
|
||||
breakpoint_list.clone().into(),
|
||||
DebuggerPaneItem::BreakpointList,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
DebuggerPaneItem::Modules => Box::new(SubView::new(
|
||||
pane.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
DebuggerPaneItem::Modules,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
|
||||
DebuggerPaneItem::Console => Box::new(SubView::new(
|
||||
pane.focus_handle(cx),
|
||||
console.clone().into(),
|
||||
DebuggerPaneItem::Console,
|
||||
Some(Box::new({
|
||||
let console = console.clone().downgrade();
|
||||
move |cx| {
|
||||
console
|
||||
.read_with(cx, |console, cx| console.show_indicator(cx))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
})),
|
||||
cx,
|
||||
)),
|
||||
})
|
||||
.collect();
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
let mut active_idx = 0;
|
||||
for (idx, sub_view) in sub_views.into_iter().enumerate() {
|
||||
if serialized_pane
|
||||
.active_item
|
||||
.is_some_and(|active| active == sub_view.read(cx).view_kind())
|
||||
{
|
||||
active_idx = idx;
|
||||
}
|
||||
pane.add_item(sub_view, false, false, None, window, cx);
|
||||
}
|
||||
|
||||
pane.activate_item(active_idx, false, false, window, cx);
|
||||
});
|
||||
|
||||
Some(Member::Pane(pane.clone()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ use workspace::{
|
|||
};
|
||||
|
||||
use crate::debugger_panel::DebugPanel;
|
||||
use crate::persistence::SerializedPaneLayout;
|
||||
|
||||
pub(crate) enum DebugSessionState {
|
||||
Running(Entity<running::RunningState>),
|
||||
|
@ -52,6 +53,7 @@ impl DebugSession {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
session: Entity<Session>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
serialized_pane_layout: Option<SerializedPaneLayout>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
|
@ -60,6 +62,7 @@ impl DebugSession {
|
|||
session.clone(),
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
serialized_pane_layout,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
mod breakpoint_list;
|
||||
mod console;
|
||||
mod loaded_source_list;
|
||||
mod module_list;
|
||||
pub(crate) mod breakpoint_list;
|
||||
pub(crate) mod console;
|
||||
pub(crate) mod loaded_source_list;
|
||||
pub(crate) mod module_list;
|
||||
pub mod stack_frame_list;
|
||||
pub mod variable_list;
|
||||
|
||||
use std::{any::Any, ops::ControlFlow, sync::Arc};
|
||||
use std::{any::Any, ops::ControlFlow, sync::Arc, time::Duration};
|
||||
|
||||
use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
|
||||
|
||||
use super::DebugPanelItemEvent;
|
||||
use breakpoint_list::BreakpointList;
|
||||
|
@ -14,7 +16,7 @@ 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, WeakEntity,
|
||||
NoAction, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use loaded_source_list::LoadedSourceList;
|
||||
use module_list::ModuleList;
|
||||
|
@ -33,8 +35,8 @@ use ui::{
|
|||
use util::ResultExt;
|
||||
use variable_list::VariableList;
|
||||
use workspace::{
|
||||
ActivePaneDecorator, DraggedTab, Item, Pane, PaneGroup, Workspace, item::TabContentParams,
|
||||
move_item, pane::Event,
|
||||
ActivePaneDecorator, DraggedTab, Item, Member, Pane, PaneGroup, Workspace,
|
||||
item::TabContentParams, move_item, pane::Event,
|
||||
};
|
||||
|
||||
pub struct RunningState {
|
||||
|
@ -51,6 +53,7 @@ pub struct RunningState {
|
|||
_console: Entity<Console>,
|
||||
panes: PaneGroup,
|
||||
pane_close_subscriptions: HashMap<EntityId, Subscription>,
|
||||
_schedule_serialize: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl Render for RunningState {
|
||||
|
@ -84,28 +87,32 @@ impl Render for RunningState {
|
|||
}
|
||||
}
|
||||
|
||||
struct SubView {
|
||||
pub(crate) struct SubView {
|
||||
inner: AnyView,
|
||||
pane_focus_handle: FocusHandle,
|
||||
tab_name: SharedString,
|
||||
kind: DebuggerPaneItem,
|
||||
show_indicator: Box<dyn Fn(&App) -> bool>,
|
||||
}
|
||||
|
||||
impl SubView {
|
||||
fn new(
|
||||
pub(crate) fn new(
|
||||
pane_focus_handle: FocusHandle,
|
||||
view: AnyView,
|
||||
tab_name: SharedString,
|
||||
kind: DebuggerPaneItem,
|
||||
show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|_| Self {
|
||||
tab_name,
|
||||
kind,
|
||||
inner: view,
|
||||
pane_focus_handle,
|
||||
show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
impl Focusable for SubView {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
|
@ -116,13 +123,19 @@ impl EventEmitter<()> for SubView {}
|
|||
impl Item for SubView {
|
||||
type Event = ();
|
||||
|
||||
/// This is used to serialize debugger pane layouts
|
||||
/// A SharedString gets converted to a enum and back during serialization/deserialization.
|
||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
||||
Some(self.kind.to_shared_string())
|
||||
}
|
||||
|
||||
fn tab_content(
|
||||
&self,
|
||||
params: workspace::item::TabContentParams,
|
||||
_: &Window,
|
||||
cx: &App,
|
||||
) -> AnyElement {
|
||||
let label = Label::new(self.tab_name.clone())
|
||||
let label = Label::new(self.kind.to_shared_string())
|
||||
.size(ui::LabelSize::Small)
|
||||
.color(params.text_color())
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel);
|
||||
|
@ -146,7 +159,7 @@ impl Render for SubView {
|
|||
}
|
||||
}
|
||||
|
||||
fn new_debugger_pane(
|
||||
pub(crate) fn new_debugger_pane(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
|
@ -185,7 +198,7 @@ fn new_debugger_pane(
|
|||
new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
let _previous_subscription = running.pane_close_subscriptions.insert(
|
||||
new_pane.entity_id(),
|
||||
cx.subscribe(&new_pane, RunningState::handle_pane_event),
|
||||
cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
|
||||
);
|
||||
debug_assert!(_previous_subscription.is_none());
|
||||
running
|
||||
|
@ -354,6 +367,7 @@ impl RunningState {
|
|||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
serialized_pane_layout: Option<SerializedPaneLayout>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
@ -382,6 +396,8 @@ impl RunningState {
|
|||
)
|
||||
});
|
||||
|
||||
let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.observe(&module_list, |_, _, cx| cx.notify()),
|
||||
cx.subscribe_in(&session, window, |this, _, event, window, cx| {
|
||||
|
@ -407,112 +423,40 @@ impl RunningState {
|
|||
}),
|
||||
];
|
||||
|
||||
let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
leftmost_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
stack_frame_list.clone().into(),
|
||||
SharedString::new_static("Frames"),
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
let mut pane_close_subscriptions = HashMap::default();
|
||||
let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
|
||||
persistence::deserialize_pane_layout(
|
||||
serialized_layout,
|
||||
&workspace,
|
||||
&project,
|
||||
&stack_frame_list,
|
||||
&variable_list,
|
||||
&module_list,
|
||||
&console,
|
||||
&breakpoints,
|
||||
&mut pane_close_subscriptions,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}) {
|
||||
workspace::PaneGroup::with_root(root)
|
||||
} else {
|
||||
pane_close_subscriptions.clear();
|
||||
let root = Self::default_pane_layout(
|
||||
project,
|
||||
&workspace,
|
||||
&stack_frame_list,
|
||||
&variable_list,
|
||||
&module_list,
|
||||
&console,
|
||||
breakpoints,
|
||||
&mut pane_close_subscriptions,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let breakpoints = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
breakpoints.focus_handle(cx),
|
||||
breakpoints.into(),
|
||||
SharedString::new_static("Breakpoints"),
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
});
|
||||
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
center_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
variable_list.focus_handle(cx),
|
||||
variable_list.clone().into(),
|
||||
SharedString::new_static("Variables"),
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
SharedString::new_static("Modules"),
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
});
|
||||
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
rightmost_pane.update(cx, |this, cx| {
|
||||
let weak_console = console.downgrade();
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
console.clone().into(),
|
||||
SharedString::new_static("Console"),
|
||||
Some(Box::new(move |cx| {
|
||||
weak_console
|
||||
.read_with(cx, |console, cx| console.show_indicator(cx))
|
||||
.unwrap_or_default()
|
||||
})),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let pane_close_subscriptions = HashMap::from_iter(
|
||||
[&leftmost_pane, ¢er_pane, &rightmost_pane]
|
||||
.into_iter()
|
||||
.map(|entity| {
|
||||
(
|
||||
entity.entity_id(),
|
||||
cx.subscribe(entity, Self::handle_pane_event),
|
||||
)
|
||||
}),
|
||||
);
|
||||
let group_root = workspace::PaneAxis::new(
|
||||
gpui::Axis::Horizontal,
|
||||
[leftmost_pane, center_pane, rightmost_pane]
|
||||
.into_iter()
|
||||
.map(workspace::Member::Pane)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let panes = PaneGroup::with_root(workspace::Member::Axis(group_root));
|
||||
workspace::PaneGroup::with_root(root)
|
||||
};
|
||||
|
||||
Self {
|
||||
session,
|
||||
|
@ -528,21 +472,57 @@ impl RunningState {
|
|||
_module_list: module_list,
|
||||
_console: console,
|
||||
pane_close_subscriptions,
|
||||
_schedule_serialize: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pane_event(
|
||||
fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self._schedule_serialize.is_none() {
|
||||
self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(100))
|
||||
.await;
|
||||
|
||||
let Some((adapter_name, pane_group)) = this
|
||||
.update(cx, |this, cx| {
|
||||
let adapter_name = this.session.read(cx).adapter_name();
|
||||
(
|
||||
adapter_name,
|
||||
persistence::build_serialized_pane_layout(&this.panes.root, cx),
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
persistence::serialize_pane_layout(adapter_name, pane_group)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this._schedule_serialize.take();
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_pane_event(
|
||||
this: &mut RunningState,
|
||||
source_pane: Entity<Pane>,
|
||||
source_pane: &Entity<Pane>,
|
||||
event: &Event,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<RunningState>,
|
||||
) {
|
||||
this.serialize_layout(window, cx);
|
||||
if let Event::Remove { .. } = event {
|
||||
let _did_find_pane = this.panes.remove(&source_pane).is_ok();
|
||||
debug_assert!(_did_find_pane);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
|
||||
if self.thread_id.is_some() {
|
||||
self.stack_frame_list
|
||||
|
@ -586,7 +566,7 @@ impl RunningState {
|
|||
.find_map(|pane| {
|
||||
pane.read(cx)
|
||||
.items_of_type::<SubView>()
|
||||
.position(|view| view.read(cx).tab_name == *"Modules")
|
||||
.position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
|
||||
.map(|view| (view, pane))
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -802,6 +782,127 @@ impl RunningState {
|
|||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn default_pane_layout(
|
||||
project: Entity<Project>,
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
stack_frame_list: &Entity<StackFrameList>,
|
||||
variable_list: &Entity<VariableList>,
|
||||
module_list: &Entity<ModuleList>,
|
||||
console: &Entity<Console>,
|
||||
breakpoints: Entity<BreakpointList>,
|
||||
subscriptions: &mut HashMap<EntityId, Subscription>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, RunningState>,
|
||||
) -> Member {
|
||||
let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
leftmost_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
stack_frame_list.clone().into(),
|
||||
DebuggerPaneItem::Frames,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
breakpoints.focus_handle(cx),
|
||||
breakpoints.into(),
|
||||
DebuggerPaneItem::BreakpointList,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
});
|
||||
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
center_pane.update(cx, |this, cx| {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
variable_list.focus_handle(cx),
|
||||
variable_list.clone().into(),
|
||||
DebuggerPaneItem::Variables,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
DebuggerPaneItem::Modules,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
});
|
||||
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
rightmost_pane.update(cx, |this, cx| {
|
||||
let weak_console = console.downgrade();
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
this.focus_handle(cx),
|
||||
console.clone().into(),
|
||||
DebuggerPaneItem::Console,
|
||||
Some(Box::new(move |cx| {
|
||||
weak_console
|
||||
.read_with(cx, |console, cx| console.show_indicator(cx))
|
||||
.unwrap_or_default()
|
||||
})),
|
||||
cx,
|
||||
)),
|
||||
true,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
subscriptions.extend(
|
||||
[&leftmost_pane, ¢er_pane, &rightmost_pane]
|
||||
.into_iter()
|
||||
.map(|entity| {
|
||||
(
|
||||
entity.entity_id(),
|
||||
cx.subscribe_in(entity, window, Self::handle_pane_event),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let group_root = workspace::PaneAxis::new(
|
||||
gpui::Axis::Horizontal,
|
||||
[leftmost_pane, center_pane, rightmost_pane]
|
||||
.into_iter()
|
||||
.map(workspace::Member::Pane)
|
||||
.collect(),
|
||||
);
|
||||
|
||||
Member::Axis(group_root)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
|
||||
|
|
|
@ -27,7 +27,7 @@ use ui::{
|
|||
use util::{ResultExt, maybe};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(super) struct BreakpointList {
|
||||
pub(crate) struct BreakpointList {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
|
|
|
@ -68,6 +68,7 @@ pub async fn init_test_workspace(
|
|||
workspace_handle
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn active_debug_session_panel(
|
||||
workspace: WindowHandle<Workspace>,
|
||||
cx: &mut TestAppContext,
|
||||
|
|
|
@ -81,6 +81,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
|||
})
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// assert we have a debug panel item before the session has stopped
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
|
@ -229,6 +231,8 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
|||
})
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// assert we have a debug panel item before the session has stopped
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
|
@ -1052,6 +1056,8 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
|||
}))
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
|
||||
item.mode()
|
||||
.as_running()
|
||||
|
|
|
@ -1538,6 +1538,8 @@ async fn test_variable_list_only_sends_requests_when_rendering(
|
|||
})
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
|
||||
let state = item
|
||||
.mode()
|
||||
|
|
|
@ -30,7 +30,8 @@ use dap::{
|
|||
use futures::channel::oneshot;
|
||||
use futures::{FutureExt, future::Shared};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,
|
||||
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
|
||||
Task, WeakEntity,
|
||||
};
|
||||
use rpc::AnyProtoClient;
|
||||
use serde_json::{Value, json};
|
||||
|
@ -125,6 +126,7 @@ type UpstreamProjectId = u64;
|
|||
struct RemoteConnection {
|
||||
_client: AnyProtoClient,
|
||||
_upstream_project_id: UpstreamProjectId,
|
||||
_adapter_name: SharedString,
|
||||
}
|
||||
|
||||
impl RemoteConnection {
|
||||
|
@ -996,6 +998,7 @@ impl Session {
|
|||
) -> Self {
|
||||
Self {
|
||||
mode: Mode::Remote(RemoteConnection {
|
||||
_adapter_name: SharedString::new(""), // todo(debugger) we need to pipe in the right values to deserialize the debugger pane layout
|
||||
_client: client,
|
||||
_upstream_project_id: upstream_project_id,
|
||||
}),
|
||||
|
@ -1044,6 +1047,13 @@ impl Session {
|
|||
&self.capabilities
|
||||
}
|
||||
|
||||
pub fn adapter_name(&self) -> SharedString {
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => local_mode.adapter.name().into(),
|
||||
Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configuration(&self) -> Option<DebugAdapterConfig> {
|
||||
if let Mode::Local(local_mode) = &self.mode {
|
||||
Some(local_mode.config.clone())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue