debugger: Enable setting debug panel dock position to the side (#29914)
### Preview <img width="301" alt="Screenshot 2025-05-05 at 11 08 43 PM" src="https://github.com/user-attachments/assets/aa445117-1c1c-4d90-a3bb-049f8417eca4" /> Setups the ground work to write debug panel persistence tests and allows users to change the dock position of the debug panel. Release Notes: - N/A
This commit is contained in:
parent
6e28400e17
commit
1aa92d9928
8 changed files with 590 additions and 302 deletions
|
@ -4,6 +4,14 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DebugPanelDockPosition {
|
||||
Left,
|
||||
Bottom,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)]
|
||||
#[serde(default)]
|
||||
pub struct DebuggerSettings {
|
||||
|
@ -31,6 +39,10 @@ pub struct DebuggerSettings {
|
|||
///
|
||||
/// Default: true
|
||||
pub format_dap_log_messages: bool,
|
||||
/// The dock position of the debug panel
|
||||
///
|
||||
/// Default: Bottom
|
||||
pub dock: DebugPanelDockPosition,
|
||||
}
|
||||
|
||||
impl Default for DebuggerSettings {
|
||||
|
@ -42,6 +54,7 @@ impl Default for DebuggerSettings {
|
|||
timeout: 2000,
|
||||
log_dap_communications: true,
|
||||
format_dap_log_messages: true,
|
||||
dock: DebugPanelDockPosition::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
|||
use anyhow::Result;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::adapters::DebugAdapterName;
|
||||
use dap::debugger_settings::DebugPanelDockPosition;
|
||||
use dap::{
|
||||
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||
client::SessionId, debugger_settings::DebuggerSettings,
|
||||
|
@ -21,11 +22,13 @@ use gpui::{
|
|||
};
|
||||
|
||||
use language::Buffer;
|
||||
use project::Fs;
|
||||
use project::debugger::session::{Session, SessionStateEvent};
|
||||
use project::{Project, debugger::session::ThreadStatus};
|
||||
use rpc::proto::{self};
|
||||
use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
use task::{DebugScenario, TaskContext};
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use workspace::SplitDirection;
|
||||
|
@ -62,6 +65,7 @@ pub struct DebugPanel {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
impl DebugPanel {
|
||||
|
@ -82,6 +86,7 @@ impl DebugPanel {
|
|||
project,
|
||||
workspace: workspace.weak_handle(),
|
||||
context_menu: None,
|
||||
fs: workspace.app_state().fs.clone(),
|
||||
};
|
||||
|
||||
debug_panel
|
||||
|
@ -284,7 +289,7 @@ impl DebugPanel {
|
|||
})
|
||||
.ok();
|
||||
|
||||
let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;
|
||||
let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
|
||||
|
||||
let (debug_session, workspace) = this.update_in(cx, |this, window, cx| {
|
||||
this.sessions.retain(|session| {
|
||||
|
@ -303,6 +308,7 @@ impl DebugPanel {
|
|||
session,
|
||||
cx.weak_entity(),
|
||||
serialized_layout,
|
||||
this.position(window, cx).axis(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
@ -599,66 +605,143 @@ impl DebugPanel {
|
|||
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let active_session = self.active_session.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
|
||||
let div = if is_side { v_flex() } else { h_flex() };
|
||||
let weak_panel = cx.weak_entity();
|
||||
|
||||
let new_session_button = || {
|
||||
IconButton::new("debug-new-session", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let workspace = self.workspace.clone();
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = self.past_debug_definition.clone();
|
||||
move |_, window, cx| {
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = past_debug_definition.clone();
|
||||
|
||||
let _ = workspace.update(cx, |this, cx| {
|
||||
let workspace = cx.weak_entity();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
past_debug_definition,
|
||||
weak_panel,
|
||||
workspace,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Debug Session",
|
||||
&CreateDebuggingSession,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Some(
|
||||
h_flex()
|
||||
.border_b_1()
|
||||
div.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.when(is_side, |this| this.gap_1())
|
||||
.child(
|
||||
h_flex().gap_2().w_full().when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state()),
|
||||
|this, running_session| {
|
||||
let thread_status = running_session
|
||||
.read(cx)
|
||||
.thread_status(cx)
|
||||
.unwrap_or(project::debugger::session::ThreadStatus::Exited);
|
||||
let capabilities = running_session.read(cx).capabilities(cx);
|
||||
this.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
IconButton::new("debug-pause", IconName::DebugPause)
|
||||
h_flex()
|
||||
.child(
|
||||
h_flex().gap_2().w_full().when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state()),
|
||||
|this, running_session| {
|
||||
let thread_status =
|
||||
running_session.read(cx).thread_status(cx).unwrap_or(
|
||||
project::debugger::session::ThreadStatus::Exited,
|
||||
);
|
||||
let capabilities = running_session.read(cx).capabilities(cx);
|
||||
this.map(|this| {
|
||||
if thread_status == ThreadStatus::Running {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"debug-pause",
|
||||
IconName::DebugPause,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.pause_thread(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Pause program",
|
||||
&Pause,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"debug-continue",
|
||||
IconName::DebugContinue,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| this.continue_thread(cx),
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Continue program",
|
||||
&Continue,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-step-over", IconName::ArrowRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.pause_thread(cx);
|
||||
this.step_over(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Pause program",
|
||||
&Pause,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
IconButton::new("debug-continue", IconName::DebugContinue)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| this.continue_thread(cx),
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Continue program",
|
||||
&Continue,
|
||||
"Step over",
|
||||
&StepOver,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
|
@ -666,240 +749,197 @@ impl DebugPanel {
|
|||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-step-over", IconName::ArrowRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_over(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step over",
|
||||
&StepOver,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-out", IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_out(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step out",
|
||||
&StepOut,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-step-into", IconName::ArrowDownRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step in",
|
||||
&StepInto,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-enable-breakpoint",
|
||||
IconName::DebugDisabledBreakpoint,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(
|
||||
thread_status == ThreadStatus::Exited
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
.child(
|
||||
IconButton::new("debug-step-out", IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_out(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step out",
|
||||
&StepOut,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.toggle_ignore_breakpoints(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Disable all breakpoints",
|
||||
&ToggleIgnoreBreakpoints,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Restart",
|
||||
&Restart,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-stop", IconName::Power)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.stop_thread(cx);
|
||||
},
|
||||
))
|
||||
.disabled(
|
||||
thread_status != ThreadStatus::Stopped
|
||||
&& thread_status != ThreadStatus::Running,
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-step-into",
|
||||
IconName::ArrowDownRight,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.step_in(cx);
|
||||
},
|
||||
))
|
||||
.disabled(thread_status != ThreadStatus::Stopped)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Step in",
|
||||
&StepInto,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
let label = if capabilities
|
||||
.supports_terminate_threads_request
|
||||
.unwrap_or_default()
|
||||
{
|
||||
"Terminate Thread"
|
||||
} else {
|
||||
"Terminate All Threads"
|
||||
};
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
label,
|
||||
&Stop,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-enable-breakpoint",
|
||||
IconName::DebugDisabledBreakpoint,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-disable-breakpoint",
|
||||
IconName::CircleOff,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(thread_status != ThreadStatus::Stopped),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"debug-disable-all-breakpoints",
|
||||
IconName::BugOff,
|
||||
)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.disabled(
|
||||
thread_status == ThreadStatus::Exited
|
||||
|| thread_status == ThreadStatus::Ended,
|
||||
)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.toggle_ignore_breakpoints(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Disable all breakpoints",
|
||||
&ToggleIgnoreBreakpoints,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
IconButton::new("debug-restart", IconName::DebugRestart)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.restart_session(cx);
|
||||
},
|
||||
))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Restart",
|
||||
&Restart,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("debug-stop", IconName::Power)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click(window.listener_for(
|
||||
&running_session,
|
||||
|this, _, _window, cx| {
|
||||
this.stop_thread(cx);
|
||||
},
|
||||
))
|
||||
.disabled(
|
||||
thread_status != ThreadStatus::Stopped
|
||||
&& thread_status != ThreadStatus::Running,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
let label = if capabilities
|
||||
.supports_terminate_threads_request
|
||||
.unwrap_or_default()
|
||||
{
|
||||
"Terminate Thread"
|
||||
} else {
|
||||
"Terminate All Threads"
|
||||
};
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
label,
|
||||
&Stop,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
.justify_around()
|
||||
.when(is_side, |this| this.child(new_session_button())),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state())
|
||||
.cloned(),
|
||||
|this, session| {
|
||||
this.child(
|
||||
session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
|
||||
)
|
||||
.child(Divider::vertical())
|
||||
},
|
||||
)
|
||||
.when_some(active_session.as_ref(), |this, session| {
|
||||
let context_menu = self.sessions_drop_down_menu(session, window, cx);
|
||||
this.child(context_menu).child(Divider::vertical())
|
||||
})
|
||||
.when(is_side, |this| this.justify_between())
|
||||
.child(
|
||||
IconButton::new("debug-new-session", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let workspace = self.workspace.clone();
|
||||
let weak_panel = cx.weak_entity();
|
||||
let past_debug_definition = self.past_debug_definition.clone();
|
||||
move |_, window, cx| {
|
||||
let weak_panel = weak_panel.clone();
|
||||
let past_debug_definition = past_debug_definition.clone();
|
||||
|
||||
let _ = workspace.update(cx, |this, cx| {
|
||||
let workspace = cx.weak_entity();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
NewSessionModal::new(
|
||||
past_debug_definition,
|
||||
weak_panel,
|
||||
workspace,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
h_flex().when_some(
|
||||
active_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).running_state())
|
||||
.cloned(),
|
||||
|this, session| {
|
||||
this.child(
|
||||
session.update(cx, |this, cx| {
|
||||
this.thread_dropdown(window, cx)
|
||||
}),
|
||||
)
|
||||
.when(!is_side, |this| this.gap_2().child(Divider::vertical()))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.when_some(active_session.as_ref(), |this, session| {
|
||||
let context_menu =
|
||||
self.sessions_drop_down_menu(session, window, cx);
|
||||
this.child(context_menu).gap_2().child(Divider::vertical())
|
||||
})
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Debug Session",
|
||||
&CreateDebuggingSession,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
.when(!is_side, |this| this.child(new_session_button())),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -967,20 +1007,45 @@ impl Panel for DebugPanel {
|
|||
"DebugPanel"
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
|
||||
DockPosition::Bottom
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
match DebuggerSettings::get_global(cx).dock {
|
||||
DebugPanelDockPosition::Left => DockPosition::Left,
|
||||
DebugPanelDockPosition::Bottom => DockPosition::Bottom,
|
||||
DebugPanelDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, position: DockPosition) -> bool {
|
||||
position == DockPosition::Bottom
|
||||
fn position_is_valid(&self, _: DockPosition) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_position(
|
||||
&mut self,
|
||||
_position: DockPosition,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
position: DockPosition,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if position.axis() != self.position(window, cx).axis() {
|
||||
self.sessions.iter().for_each(|session_item| {
|
||||
session_item.update(cx, |item, cx| {
|
||||
item.running_state()
|
||||
.update(cx, |state, _| state.invert_axies())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
settings::update_settings_file::<DebuggerSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => DebugPanelDockPosition::Left,
|
||||
DockPosition::Bottom => DebugPanelDockPosition::Bottom,
|
||||
DockPosition::Right => DebugPanelDockPosition::Right,
|
||||
};
|
||||
settings.dock = dock;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, _window: &Window, _: &App) -> Pixels {
|
||||
|
|
|
@ -69,19 +69,22 @@ impl From<DebuggerPaneItem> for SharedString {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct SerializedAxis(pub Axis);
|
||||
pub(crate) struct SerializedLayout {
|
||||
pub(crate) panes: SerializedPaneLayout,
|
||||
pub(crate) dock_axis: Axis,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub(crate) enum SerializedPaneLayout {
|
||||
Pane(SerializedPane),
|
||||
Group {
|
||||
axis: SerializedAxis,
|
||||
axis: Axis,
|
||||
flexes: Option<Vec<f32>>,
|
||||
children: Vec<SerializedPaneLayout>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub(crate) struct SerializedPane {
|
||||
pub children: Vec<DebuggerPaneItem>,
|
||||
pub active_item: Option<DebuggerPaneItem>,
|
||||
|
@ -91,7 +94,7 @@ const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_";
|
|||
|
||||
pub(crate) async fn serialize_pane_layout(
|
||||
adapter_name: DebugAdapterName,
|
||||
pane_group: SerializedPaneLayout,
|
||||
pane_group: SerializedLayout,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Ok(serialized_pane_group) = serde_json::to_string(&pane_group) {
|
||||
KEY_VALUE_STORE
|
||||
|
@ -107,10 +110,18 @@ pub(crate) async fn serialize_pane_layout(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_serialized_pane_layout(
|
||||
pub(crate) fn build_serialized_layout(
|
||||
pane_group: &Member,
|
||||
cx: &mut App,
|
||||
) -> SerializedPaneLayout {
|
||||
dock_axis: Axis,
|
||||
cx: &App,
|
||||
) -> SerializedLayout {
|
||||
SerializedLayout {
|
||||
dock_axis,
|
||||
panes: build_serialized_pane_layout(pane_group, cx),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_serialized_pane_layout(pane_group: &Member, cx: &App) -> SerializedPaneLayout {
|
||||
match pane_group {
|
||||
Member::Axis(PaneAxis {
|
||||
axis,
|
||||
|
@ -118,7 +129,7 @@ pub(crate) fn build_serialized_pane_layout(
|
|||
flexes,
|
||||
bounding_boxes: _,
|
||||
}) => SerializedPaneLayout::Group {
|
||||
axis: SerializedAxis(*axis),
|
||||
axis: *axis,
|
||||
children: members
|
||||
.iter()
|
||||
.map(|member| build_serialized_pane_layout(member, cx))
|
||||
|
@ -129,7 +140,7 @@ pub(crate) fn build_serialized_pane_layout(
|
|||
}
|
||||
}
|
||||
|
||||
fn serialize_pane(pane: &Entity<Pane>, cx: &mut App) -> SerializedPane {
|
||||
fn serialize_pane(pane: &Entity<Pane>, cx: &App) -> SerializedPane {
|
||||
let pane = pane.read(cx);
|
||||
let children = pane
|
||||
.items()
|
||||
|
@ -150,20 +161,21 @@ fn serialize_pane(pane: &Entity<Pane>, cx: &mut App) -> SerializedPane {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_serialized_pane_layout(
|
||||
pub(crate) async fn get_serialized_layout(
|
||||
adapter_name: impl AsRef<str>,
|
||||
) -> Option<SerializedPaneLayout> {
|
||||
) -> Option<SerializedLayout> {
|
||||
let key = format!("{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())
|
||||
.and_then(|value| serde_json::from_str::<SerializedLayout>(&value).ok())
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_pane_layout(
|
||||
serialized: SerializedPaneLayout,
|
||||
should_invert: bool,
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
project: &Entity<Project>,
|
||||
stack_frame_list: &Entity<StackFrameList>,
|
||||
|
@ -187,6 +199,7 @@ pub(crate) fn deserialize_pane_layout(
|
|||
for child in children {
|
||||
if let Some(new_member) = deserialize_pane_layout(
|
||||
child,
|
||||
should_invert,
|
||||
workspace,
|
||||
project,
|
||||
stack_frame_list,
|
||||
|
@ -213,7 +226,7 @@ pub(crate) fn deserialize_pane_layout(
|
|||
}
|
||||
|
||||
Some(Member::Axis(PaneAxis::load(
|
||||
axis.0,
|
||||
if should_invert { axis.invert() } else { axis },
|
||||
members,
|
||||
flexes.clone(),
|
||||
)))
|
||||
|
@ -307,3 +320,28 @@ pub(crate) fn deserialize_pane_layout(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl SerializedPaneLayout {
|
||||
pub(crate) fn in_order(&self) -> Vec<SerializedPaneLayout> {
|
||||
let mut panes = vec![];
|
||||
|
||||
Self::inner_in_order(&self, &mut panes);
|
||||
panes
|
||||
}
|
||||
|
||||
fn inner_in_order(&self, panes: &mut Vec<SerializedPaneLayout>) {
|
||||
match self {
|
||||
SerializedPaneLayout::Pane(_) => panes.push((*self).clone()),
|
||||
SerializedPaneLayout::Group {
|
||||
axis: _,
|
||||
flexes: _,
|
||||
children,
|
||||
} => {
|
||||
for child in children {
|
||||
child.inner_in_order(panes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ pub mod running;
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use dap::client::SessionId;
|
||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||
use gpui::{
|
||||
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use project::Project;
|
||||
use project::debugger::session::Session;
|
||||
use project::worktree_store::WorktreeStore;
|
||||
|
@ -15,8 +17,7 @@ use workspace::{
|
|||
item::{self, Item},
|
||||
};
|
||||
|
||||
use crate::debugger_panel::DebugPanel;
|
||||
use crate::persistence::SerializedPaneLayout;
|
||||
use crate::{debugger_panel::DebugPanel, persistence::SerializedLayout};
|
||||
|
||||
pub struct DebugSession {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
|
@ -40,7 +41,8 @@ impl DebugSession {
|
|||
workspace: WeakEntity<Workspace>,
|
||||
session: Entity<Session>,
|
||||
_debug_panel: WeakEntity<DebugPanel>,
|
||||
serialized_pane_layout: Option<SerializedPaneLayout>,
|
||||
serialized_layout: Option<SerializedLayout>,
|
||||
dock_axis: Axis,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
|
@ -49,7 +51,8 @@ impl DebugSession {
|
|||
session.clone(),
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
serialized_pane_layout,
|
||||
serialized_layout,
|
||||
dock_axis,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ pub mod variable_list;
|
|||
|
||||
use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
|
||||
use crate::persistence::{self, DebuggerPaneItem, SerializedLayout};
|
||||
|
||||
use super::DebugPanelItemEvent;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
@ -22,7 +22,7 @@ use dap::{
|
|||
};
|
||||
use futures::{SinkExt, channel::mpsc};
|
||||
use gpui::{
|
||||
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
NoAction, Pixels, Point, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use language::Buffer;
|
||||
|
@ -73,6 +73,7 @@ pub struct RunningState {
|
|||
panes: PaneGroup,
|
||||
active_pane: Option<Entity<Pane>>,
|
||||
pane_close_subscriptions: HashMap<EntityId, Subscription>,
|
||||
dock_axis: Axis,
|
||||
_schedule_serialize: Option<Task<()>>,
|
||||
}
|
||||
|
||||
|
@ -510,7 +511,8 @@ impl RunningState {
|
|||
session: Entity<Session>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
serialized_pane_layout: Option<SerializedPaneLayout>,
|
||||
serialized_pane_layout: Option<SerializedLayout>,
|
||||
dock_axis: Axis,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
|
@ -589,7 +591,8 @@ impl RunningState {
|
|||
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,
|
||||
serialized_layout.panes,
|
||||
dock_axis != serialized_layout.dock_axis,
|
||||
&workspace,
|
||||
&project,
|
||||
&stack_frame_list,
|
||||
|
@ -617,6 +620,7 @@ impl RunningState {
|
|||
&loaded_source_list,
|
||||
&console,
|
||||
&breakpoint_list,
|
||||
dock_axis,
|
||||
&mut pane_close_subscriptions,
|
||||
window,
|
||||
cx,
|
||||
|
@ -643,6 +647,7 @@ impl RunningState {
|
|||
loaded_sources_list: loaded_source_list,
|
||||
pane_close_subscriptions,
|
||||
debug_terminal,
|
||||
dock_axis,
|
||||
_schedule_serialize: None,
|
||||
}
|
||||
}
|
||||
|
@ -1056,12 +1061,16 @@ impl RunningState {
|
|||
.timer(Duration::from_millis(100))
|
||||
.await;
|
||||
|
||||
let Some((adapter_name, pane_group)) = this
|
||||
.update(cx, |this, cx| {
|
||||
let Some((adapter_name, pane_layout)) = this
|
||||
.read_with(cx, |this, cx| {
|
||||
let adapter_name = this.session.read(cx).adapter();
|
||||
(
|
||||
adapter_name,
|
||||
persistence::build_serialized_pane_layout(&this.panes.root, cx),
|
||||
persistence::build_serialized_layout(
|
||||
&this.panes.root,
|
||||
this.dock_axis,
|
||||
cx,
|
||||
),
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
|
@ -1069,7 +1078,7 @@ impl RunningState {
|
|||
return;
|
||||
};
|
||||
|
||||
persistence::serialize_pane_layout(adapter_name, pane_group)
|
||||
persistence::serialize_pane_layout(adapter_name, pane_layout)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
|
@ -1195,6 +1204,11 @@ impl RunningState {
|
|||
&self.variable_list
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
|
||||
persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
|
||||
}
|
||||
|
||||
pub fn capabilities(&self, cx: &App) -> Capabilities {
|
||||
self.session().read(cx).capabilities().clone()
|
||||
}
|
||||
|
@ -1408,6 +1422,7 @@ impl RunningState {
|
|||
loaded_source_list: &Entity<LoadedSourceList>,
|
||||
console: &Entity<Console>,
|
||||
breakpoints: &Entity<BreakpointList>,
|
||||
dock_axis: Axis,
|
||||
subscriptions: &mut HashMap<EntityId, Subscription>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, RunningState>,
|
||||
|
@ -1528,7 +1543,7 @@ impl RunningState {
|
|||
);
|
||||
|
||||
let group_root = workspace::PaneAxis::new(
|
||||
gpui::Axis::Horizontal,
|
||||
dock_axis.invert(),
|
||||
[leftmost_pane, center_pane, rightmost_pane]
|
||||
.into_iter()
|
||||
.map(workspace::Member::Pane)
|
||||
|
@ -1537,6 +1552,11 @@ impl RunningState {
|
|||
|
||||
Member::Axis(group_root)
|
||||
}
|
||||
|
||||
pub(crate) fn invert_axies(&mut self) {
|
||||
self.dock_axis = self.dock_axis.invert();
|
||||
self.panes.invert_axies();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for RunningState {}
|
||||
|
|
|
@ -23,6 +23,8 @@ mod debugger_panel;
|
|||
#[cfg(test)]
|
||||
mod module_list;
|
||||
#[cfg(test)]
|
||||
mod persistence;
|
||||
#[cfg(test)]
|
||||
mod stack_frame_list;
|
||||
#[cfg(test)]
|
||||
mod variable_list;
|
||||
|
|
131
crates/debugger_ui/src/tests/persistence.rs
Normal file
131
crates/debugger_ui/src/tests/persistence.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use std::iter::zip;
|
||||
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
persistence::SerializedPaneLayout,
|
||||
tests::{init_test, init_test_workspace, start_debug_session},
|
||||
};
|
||||
use dap::{StoppedEvent, StoppedEventReason, messages::Events};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use util::path;
|
||||
use workspace::{Panel, dock::DockPosition};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_invert_axis_on_panel_position_change(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
"main.rs": "fn main() {\n println!(\"Hello, world!\");\n}",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
// Start a debug session
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
// Setup thread response
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
Ok(dap::ThreadsResponse { threads: vec![] })
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
client
|
||||
.fake_event(Events::Stopped(StoppedEvent {
|
||||
reason: StoppedEventReason::Pause,
|
||||
description: None,
|
||||
thread_id: Some(1),
|
||||
preserve_focus_hint: None,
|
||||
text: None,
|
||||
all_threads_stopped: None,
|
||||
hit_breakpoint_ids: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let (debug_panel, dock_position) = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let dock_position = debug_panel.read(cx).position(window, cx);
|
||||
(debug_panel, dock_position)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
dock_position,
|
||||
DockPosition::Bottom,
|
||||
"Default dock position should be bottom for debug panel"
|
||||
);
|
||||
|
||||
let pre_serialized_layout = debug_panel
|
||||
.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_session()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.running_state()
|
||||
.read(cx)
|
||||
.serialized_layout(cx)
|
||||
})
|
||||
.panes;
|
||||
|
||||
let post_serialized_layout = debug_panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.set_position(DockPosition::Right, window, cx);
|
||||
|
||||
panel
|
||||
.active_session()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.running_state()
|
||||
.read(cx)
|
||||
.serialized_layout(cx)
|
||||
})
|
||||
.panes;
|
||||
|
||||
let pre_panes = pre_serialized_layout.in_order();
|
||||
let post_panes = post_serialized_layout.in_order();
|
||||
|
||||
assert_eq!(pre_panes.len(), post_panes.len());
|
||||
|
||||
for (pre, post) in zip(pre_panes, post_panes) {
|
||||
match (pre, post) {
|
||||
(
|
||||
SerializedPaneLayout::Group {
|
||||
axis: pre_axis,
|
||||
flexes: pre_flexes,
|
||||
children: _,
|
||||
},
|
||||
SerializedPaneLayout::Group {
|
||||
axis: post_axis,
|
||||
flexes: post_flexes,
|
||||
children: _,
|
||||
},
|
||||
) => {
|
||||
assert_ne!(pre_axis, post_axis);
|
||||
assert_eq!(pre_flexes, post_flexes);
|
||||
}
|
||||
(SerializedPaneLayout::Pane(pre_pane), SerializedPaneLayout::Pane(post_pane)) => {
|
||||
assert_eq!(pre_pane.children, post_pane.children);
|
||||
assert_eq!(pre_pane.active_item, post_pane.active_item);
|
||||
}
|
||||
_ => {
|
||||
panic!("Variants don't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -176,6 +176,10 @@ impl PaneGroup {
|
|||
};
|
||||
self.pane_at_pixel_position(target)
|
||||
}
|
||||
|
||||
pub fn invert_axies(&mut self) {
|
||||
self.root.invert_pane_axies();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -441,6 +445,18 @@ impl Member {
|
|||
Member::Pane(pane) => panes.push(pane),
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_pane_axies(&mut self) {
|
||||
match self {
|
||||
Self::Axis(axis) => {
|
||||
axis.axis = axis.axis.invert();
|
||||
for member in axis.members.iter_mut() {
|
||||
member.invert_pane_axies();
|
||||
}
|
||||
}
|
||||
Self::Pane(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue