From 784f2eca88c5671847d2a34e5e8ddace1ed970de Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 19 Dec 2024 12:32:44 -0500 Subject: [PATCH 1/3] Add step back support for DAP The step back button is hidden because most dap implementations don't support it. --- assets/icons/debug_step_back.svg | 1 + crates/debugger_ui/src/debugger_panel_item.rs | 23 ++++++++++ crates/project/src/dap_store.rs | 42 ++++++++++++++++--- crates/ui/src/components/icon.rs | 1 + 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 assets/icons/debug_step_back.svg diff --git a/assets/icons/debug_step_back.svg b/assets/icons/debug_step_back.svg new file mode 100644 index 00000000000000..bc7c9b8444cda2 --- /dev/null +++ b/assets/icons/debug_step_back.svg @@ -0,0 +1 @@ + diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs index 310421a54a492a..03789d152c6054 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/debugger_panel_item.rs @@ -588,6 +588,18 @@ impl DebugPanelItem { }); } + pub fn step_back(&mut self, cx: &mut ViewContext) { + self.update_thread_state_status(ThreadStatus::Running, cx); + + let granularity = DebuggerSettings::get_global(cx).stepping_granularity; + + self.dap_store.update(cx, |store, cx| { + store + .step_back(&self.client_id, self.thread_id, granularity, cx) + .detach_and_log_err(cx); + }); + } + pub fn restart_client(&self, cx: &mut ViewContext) { self.dap_store.update(cx, |store, cx| { store @@ -807,6 +819,17 @@ impl Render for DebugPanelItem { .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |cx| Tooltip::text("Step out", cx)), ) + .when(capabilities.supports_step_back.unwrap_or(false), |this| { + this.child( + IconButton::new("debug-step-back", IconName::DebugStepBack) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, cx| { + this.step_back(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |cx| Tooltip::text("Step back", cx)), + ) + }) .child( IconButton::new("debug-restart", IconName::DebugRestart) .icon_size(IconSize::Small) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index e7b701101523ac..6454ebc7dd08b5 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -3,15 +3,15 @@ use crate::{ProjectEnvironment, ProjectItem as _, ProjectPath}; use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; use collections::HashMap; -use dap::adapters::{DapDelegate, DapStatus, DebugAdapter, DebugAdapterBinary, DebugAdapterName}; use dap::{ + adapters::{DapDelegate, DapStatus, DebugAdapter, DebugAdapterBinary, DebugAdapterName}, client::{DebugAdapterClient, DebugAdapterClientId}, messages::{Message, Response}, requests::{ Attach, Completions, ConfigurationDone, Continue, Disconnect, Evaluate, Initialize, Launch, LoadedSources, Modules, Next, Pause, Request as _, Restart, RunInTerminal, Scopes, - SetBreakpoints, SetExpression, SetVariable, StackTrace, StartDebugging, StepIn, StepOut, - Terminate, TerminateThreads, Variables, + SetBreakpoints, SetExpression, SetVariable, StackTrace, StartDebugging, StepBack, StepIn, + StepOut, Terminate, TerminateThreads, Variables, }, AttachRequestArguments, Capabilities, CompletionItem, CompletionsArguments, ConfigurationDoneArguments, ContinueArguments, DisconnectArguments, ErrorResponse, @@ -20,8 +20,9 @@ use dap::{ ModulesArguments, NextArguments, PauseArguments, RestartArguments, RunInTerminalResponse, Scope, ScopesArguments, SetBreakpointsArguments, SetExpressionArguments, SetVariableArguments, Source, SourceBreakpoint, StackFrame, StackTraceArguments, StartDebuggingRequestArguments, - StartDebuggingRequestArgumentsRequest, StepInArguments, StepOutArguments, SteppingGranularity, - TerminateArguments, TerminateThreadsArguments, Variable, VariablesArguments, + StartDebuggingRequestArgumentsRequest, StepBackArguments, StepInArguments, StepOutArguments, + SteppingGranularity, TerminateArguments, TerminateThreadsArguments, Variable, + VariablesArguments, }; use dap_adapters::build_adapter; use fs::Fs; @@ -1077,6 +1078,37 @@ impl DapStore { }) } + pub fn step_back( + &self, + client_id: &DebugAdapterClientId, + thread_id: u64, + granularity: SteppingGranularity, + cx: &mut ModelContext, + ) -> Task> { + let Some(client) = self.client_by_id(client_id) else { + return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + }; + + let capabilities = self.capabilities_by_id(client_id); + + let supports_single_thread_execution_requests = capabilities + .supports_single_thread_execution_requests + .unwrap_or_default(); + let supports_stepping_granularity = capabilities + .supports_stepping_granularity + .unwrap_or_default(); + + cx.background_executor().spawn(async move { + client + .request::(StepBackArguments { + thread_id, + granularity: supports_stepping_granularity.then(|| granularity), + single_thread: supports_single_thread_execution_requests.then(|| true), + }) + .await + }) + } + pub fn variables( &self, client_id: &DebugAdapterClientId, diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 71bb647f5da3d8..08bee40a019973 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -168,6 +168,7 @@ pub enum IconName { DebugStepOver, DebugStepInto, DebugStepOut, + DebugStepBack, DebugRestart, Debug, DebugStop, From d214317c8609795e33651834dbeeabe8feedad57 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 19 Dec 2024 13:04:52 -0500 Subject: [PATCH 2/3] Add step back as global action --- crates/debugger_ui/src/debugger_panel_item.rs | 22 +++++++++---------- crates/debugger_ui/src/lib.rs | 15 +++++++++++-- crates/workspace/src/workspace.rs | 1 + 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs index 03789d152c6054..e4c53ea59ce22c 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/debugger_panel_item.rs @@ -792,6 +792,17 @@ impl Render for DebugPanelItem { ) } }) + .when(capabilities.supports_step_back.unwrap_or(false), |this| { + this.child( + IconButton::new("debug-step-back", IconName::DebugStepBack) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, cx| { + this.step_back(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |cx| Tooltip::text("Step back", cx)), + ) + }) .child( IconButton::new("debug-step-over", IconName::DebugStepOver) .icon_size(IconSize::Small) @@ -819,17 +830,6 @@ impl Render for DebugPanelItem { .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |cx| Tooltip::text("Step out", cx)), ) - .when(capabilities.supports_step_back.unwrap_or(false), |this| { - this.child( - IconButton::new("debug-step-back", IconName::DebugStepBack) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, cx| { - this.step_back(cx); - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |cx| Tooltip::text("Step back", cx)), - ) - }) .child( IconButton::new("debug-restart", IconName::DebugRestart) .icon_size(IconSize::Small) diff --git a/crates/debugger_ui/src/lib.rs b/crates/debugger_ui/src/lib.rs index 51009adf59dcf0..8176f36d43edd7 100644 --- a/crates/debugger_ui/src/lib.rs +++ b/crates/debugger_ui/src/lib.rs @@ -5,8 +5,8 @@ use gpui::AppContext; use settings::Settings; use ui::ViewContext; use workspace::{ - Continue, Pause, Restart, ShutdownDebugAdapters, Start, StepInto, StepOut, StepOver, Stop, - ToggleIgnoreBreakpoints, Workspace, + Continue, Pause, Restart, ShutdownDebugAdapters, Start, StepBack, StepInto, StepOut, StepOver, + Stop, ToggleIgnoreBreakpoints, Workspace, }; mod attach_modal; @@ -78,6 +78,17 @@ pub fn init(cx: &mut AppContext) { active_item.update(cx, |item, cx| item.step_in(cx)) }); }) + .register_action(|workspace: &mut Workspace, _: &StepBack, cx| { + let debug_panel = workspace.panel::(cx).unwrap(); + + debug_panel.update(cx, |panel, cx| { + let Some(active_item) = panel.active_debug_panel_item(cx) else { + return; + }; + + active_item.update(cx, |item, cx| item.step_back(cx)) + }); + }) .register_action(|workspace: &mut Workspace, _: &StepOut, cx| { let debug_panel = workspace.panel::(cx).unwrap(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4e77e09ab4a142..5d22b99e0276ac 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -135,6 +135,7 @@ actions!( StepInto, StepOver, StepOut, + StepBack, Stop, ToggleIgnoreBreakpoints ] From c792135cd4e44c2ed19d0ce3ce294e0929567840 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 19 Dec 2024 13:25:32 -0500 Subject: [PATCH 3/3] Filter step back action when not avaliable --- crates/debugger_ui/src/debugger_panel.rs | 28 ++++++++++++++++++++---- crates/project/src/dap_store.rs | 7 ++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 070e2edffa0f95..5b7acd5e48de44 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -26,8 +26,8 @@ use terminal_view::terminal_panel::TerminalPanel; use ui::prelude::*; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - pane, Continue, Disconnect, Pane, Pause, Restart, Start, StepInto, StepOut, StepOver, Stop, - ToggleIgnoreBreakpoints, Workspace, + pane, Continue, Disconnect, Pane, Pause, Restart, Start, StepBack, StepInto, StepOut, StepOver, + Stop, ToggleIgnoreBreakpoints, Workspace, }; pub enum DebugPanelEvent { @@ -214,8 +214,19 @@ impl DebugPanel { let debug_panel = DebugPanel::new(workspace, cx); cx.observe(&debug_panel, |_, debug_panel, cx| { - let has_active_session = debug_panel - .update(cx, |this, cx| this.active_debug_panel_item(cx).is_some()); + let (has_active_session, support_step_back) = + debug_panel.update(cx, |this, cx| { + this.active_debug_panel_item(cx) + .map(|item| { + ( + true, + item.update(cx, |this, cx| this.capabilities(cx)) + .supports_step_back + .unwrap_or(false), + ) + }) + .unwrap_or((false, false)) + }); let filter = CommandPaletteFilter::global_mut(cx); let debugger_action_types = [ @@ -230,11 +241,20 @@ impl DebugPanel { TypeId::of::(), ]; + let step_back_action_type = [TypeId::of::()]; + if has_active_session { filter.show_action_types(debugger_action_types.iter()); + + if support_step_back { + filter.show_action_types(step_back_action_type.iter()); + } else { + filter.hide_action_types(&step_back_action_type); + } } else { // show only the `debug: start` filter.hide_action_types(&debugger_action_types); + filter.hide_action_types(&step_back_action_type); } }) .detach(); diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 6454ebc7dd08b5..27d284427da25a 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -1091,6 +1091,13 @@ impl DapStore { let capabilities = self.capabilities_by_id(client_id); + if capabilities.supports_step_back.unwrap_or(false) { + return Task::ready(Err(anyhow!( + "Step back request isn't support for client_id: {:?}", + client_id + ))); + } + let supports_single_thread_execution_requests = capabilities .supports_single_thread_execution_requests .unwrap_or_default();