From 010441e23bf81cfaacf4b875e9e330d456c53558 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:45:22 -0400 Subject: [PATCH] debugger: Show run to cursor in editor's context menu (#35745) This also fixed a bug where evaluate selected text was an available option when the selected debug session was terminated. Release Notes: - debugger: add Run to Cursor back to Editor's context menu Co-authored-by: Remco Smits --- crates/debugger_ui/src/debugger_ui.rs | 103 ++++++++++++++---------- crates/editor/src/mouse_context_menu.rs | 16 ++-- crates/gpui/src/window.rs | 19 +++++ 3 files changed, 90 insertions(+), 48 deletions(-) diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 9eac59af83d6a9255fd11202fc5d30969919fd01..5f5dfd1a1e6a543cdb7a4d87e1b8e9984c4ecba9 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -299,59 +299,76 @@ pub fn init(cx: &mut App) { else { return; }; + + let session = active_session + .read(cx) + .running_state + .read(cx) + .session() + .read(cx); + + if session.is_terminated() { + return; + } + let editor = cx.entity().downgrade(); - window.on_action(TypeId::of::(), { - let editor = editor.clone(); - let active_session = active_session.clone(); - move |_, phase, _, cx| { - if phase != DispatchPhase::Bubble { - return; - } - maybe!({ - let (buffer, position, _) = editor - .update(cx, |editor, cx| { - let cursor_point: language::Point = - editor.selections.newest(cx).head(); - editor - .buffer() - .read(cx) - .point_to_buffer_point(cursor_point, cx) - }) - .ok()??; + window.on_action_when( + session.any_stopped_thread(), + TypeId::of::(), + { + let editor = editor.clone(); + let active_session = active_session.clone(); + move |_, phase, _, cx| { + if phase != DispatchPhase::Bubble { + return; + } + maybe!({ + let (buffer, position, _) = editor + .update(cx, |editor, cx| { + let cursor_point: language::Point = + editor.selections.newest(cx).head(); + + editor + .buffer() + .read(cx) + .point_to_buffer_point(cursor_point, cx) + }) + .ok()??; - let path = + let path = debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer( &buffer, cx, )?; - let source_breakpoint = SourceBreakpoint { - row: position.row, - path, - message: None, - condition: None, - hit_condition: None, - state: debugger::breakpoint_store::BreakpointState::Enabled, - }; + let source_breakpoint = SourceBreakpoint { + row: position.row, + path, + message: None, + condition: None, + hit_condition: None, + state: debugger::breakpoint_store::BreakpointState::Enabled, + }; - active_session.update(cx, |session, cx| { - session.running_state().update(cx, |state, cx| { - if let Some(thread_id) = state.selected_thread_id() { - state.session().update(cx, |session, cx| { - session.run_to_position( - source_breakpoint, - thread_id, - cx, - ); - }) - } + active_session.update(cx, |session, cx| { + session.running_state().update(cx, |state, cx| { + if let Some(thread_id) = state.selected_thread_id() { + state.session().update(cx, |session, cx| { + session.run_to_position( + source_breakpoint, + thread_id, + cx, + ); + }) + } + }); }); - }); - Some(()) - }); - } - }); + Some(()) + }); + } + }, + ); window.on_action( TypeId::of::(), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index cbb6791a2f0c7bba9fa0da9774d71eedd78f2c55..9d5145dec1f380013fbf76776efd077d7b466a37 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,8 +1,8 @@ use crate::{ Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, - GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionEffects, - SelectionExt, ToDisplayPoint, ToggleCodeActions, + GoToTypeDefinition, Paste, Rename, RevealInFileManager, RunToCursor, SelectMode, + SelectionEffects, SelectionExt, ToDisplayPoint, ToggleCodeActions, actions::{Format, FormatSelections}, selections_collection::SelectionsCollection, }; @@ -200,15 +200,21 @@ pub fn deploy_context_menu( }); let evaluate_selection = window.is_action_available(&EvaluateSelectedText, cx); + let run_to_cursor = window.is_action_available(&RunToCursor, cx); ui::ContextMenu::build(window, cx, |menu, _window, _cx| { let builder = menu .on_blur_subscription(Subscription::new(|| {})) + .when(run_to_cursor, |builder| { + builder.action("Run to Cursor", Box::new(RunToCursor)) + }) .when(evaluate_selection && has_selections, |builder| { - builder - .action("Evaluate Selection", Box::new(EvaluateSelectedText)) - .separator() + builder.action("Evaluate Selection", Box::new(EvaluateSelectedText)) }) + .when( + run_to_cursor || (evaluate_selection && has_selections), + |builder| builder.separator(), + ) .action("Go to Definition", Box::new(GoToDefinition)) .action("Go to Declaration", Box::new(GoToDeclaration)) .action("Go to Type Definition", Box::new(GoToTypeDefinition)) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8610993994b3eb9b2c68fc60007722a2ea2432cf..40d3845ff9600f9dcd16fe55b0b27d5e3eddce09 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4248,6 +4248,25 @@ impl Window { .on_action(action_type, Rc::new(listener)); } + /// Register an action listener on the window for the next frame if the condition is true. + /// The type of action is determined by the first parameter of the given listener. + /// When the next frame is rendered the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using action handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_action_when( + &mut self, + condition: bool, + action_type: TypeId, + listener: impl Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static, + ) { + if condition { + self.next_frame + .dispatch_tree + .on_action(action_type, Rc::new(listener)); + } + } + /// Read information about the GPU backing this window. /// Currently returns None on Mac and Windows. pub fn gpu_specs(&self) -> Option {