diff --git a/Cargo.lock b/Cargo.lock index 57fd310ce9f47ef79e6c3ca9bcae30e0593ebb70..cba4ac7df1545193a4ec1d245a30f5c6bc473074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4600,6 +4600,7 @@ dependencies = [ "client", "clock", "collections", + "command_palette_hooks", "convert_case 0.8.0", "ctor", "db", diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 6db230a6f1d15f8ca7de0760eebec1f15887c201..63496e41e6ae9fd7396405df2ca289db8c119b2d 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -15,6 +15,7 @@ use gpui::{ Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, }; + use project::{ Project, debugger::{ @@ -94,6 +95,87 @@ impl DebugPanel { }) } + fn filter_action_types(&self, cx: &mut App) { + let (has_active_session, supports_restart, support_step_back, status) = self + .active_session() + .map(|item| { + let running = item.read(cx).mode().as_running().cloned(); + + match running { + Some(running) => { + let caps = running.read(cx).capabilities(cx); + ( + !running.read(cx).session().read(cx).is_terminated(), + caps.supports_restart_request.unwrap_or_default(), + caps.supports_step_back.unwrap_or_default(), + running.read(cx).thread_status(cx), + ) + } + None => (false, false, false, None), + } + }) + .unwrap_or((false, false, false, None)); + + let filter = CommandPaletteFilter::global_mut(cx); + let debugger_action_types = [ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ]; + + let running_action_types = [TypeId::of::()]; + + let stopped_action_type = [ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ]; + + let step_back_action_type = [TypeId::of::()]; + let restart_action_type = [TypeId::of::()]; + + if has_active_session { + filter.show_action_types(debugger_action_types.iter()); + + if supports_restart { + filter.show_action_types(restart_action_type.iter()); + } else { + filter.hide_action_types(&restart_action_type); + } + + if support_step_back { + filter.show_action_types(step_back_action_type.iter()); + } else { + filter.hide_action_types(&step_back_action_type); + } + + match status { + Some(ThreadStatus::Running) => { + filter.show_action_types(running_action_types.iter()); + filter.hide_action_types(&stopped_action_type); + } + Some(ThreadStatus::Stopped) => { + filter.show_action_types(stopped_action_type.iter()); + filter.hide_action_types(&running_action_types); + } + _ => { + filter.hide_action_types(&running_action_types); + filter.hide_action_types(&stopped_action_type); + } + } + } else { + // show only the `debug: start` + filter.hide_action_types(&debugger_action_types); + filter.hide_action_types(&step_back_action_type); + filter.hide_action_types(&restart_action_type); + filter.hide_action_types(&running_action_types); + filter.hide_action_types(&stopped_action_type); + } + } + pub fn load( workspace: WeakEntity, cx: AsyncWindowContext, @@ -111,63 +193,15 @@ impl DebugPanel { ) }); + cx.observe_new::(|debug_panel, _, cx| { + Self::filter_action_types(debug_panel, cx); + }) + .detach(); + cx.observe(&debug_panel, |_, debug_panel, cx| { - let (has_active_session, supports_restart, support_step_back) = debug_panel - .update(cx, |this, cx| { - this.active_session() - .map(|item| { - let running = item.read(cx).mode().as_running().cloned(); - - match running { - Some(running) => { - let caps = running.read(cx).capabilities(cx); - ( - true, - caps.supports_restart_request.unwrap_or_default(), - caps.supports_step_back.unwrap_or_default(), - ) - } - None => (false, false, false), - } - }) - .unwrap_or((false, false, false)) - }); - - let filter = CommandPaletteFilter::global_mut(cx); - let debugger_action_types = [ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ]; - - let step_back_action_type = [TypeId::of::()]; - let restart_action_type = [TypeId::of::()]; - - if has_active_session { - filter.show_action_types(debugger_action_types.iter()); - - if supports_restart { - filter.show_action_types(restart_action_type.iter()); - } else { - filter.hide_action_types(&restart_action_type); - } - - 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); - filter.hide_action_types(&restart_action_type); - } + debug_panel.update(cx, |debug_panel, cx| { + Self::filter_action_types(debug_panel, cx); + }); }) .detach(); @@ -243,6 +277,12 @@ impl DebugPanel { 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(); + } + self.sessions.push(session_item.clone()); self.activate_session(session_item, window, cx); } @@ -360,6 +400,8 @@ impl DebugPanel { self.active_session = self.sessions.first().cloned(); } } + + cx.notify(); } fn sessions_drop_down_menu( @@ -378,7 +420,7 @@ impl DebugPanel { ContextMenu::build(window, cx, move |mut this, _, _| { for session in sessions.into_iter() { let weak_session = session.downgrade(); - let weak_id = weak_session.entity_id(); + let weak_session_id = weak_session.entity_id(); this = this.custom_entry( { @@ -400,7 +442,8 @@ impl DebugPanel { let weak = weak.clone(); move |_, _, cx| { weak.update(cx, |panel, cx| { - panel.close_session(weak_id, cx); + panel + .close_session(weak_session_id, cx); }) .ok(); } diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 1e1db067b76a896b4880083aedf931e1c1071139..560333a12f8f96da3978fbe4184388e415eda8ba 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -1,10 +1,13 @@ use dap::debugger_settings::DebuggerSettings; use debugger_panel::{DebugPanel, ToggleFocus}; +use editor::Editor; use feature_flags::{Debugger, FeatureFlagViewExt}; -use gpui::{App, actions}; +use gpui::{App, EntityInputHandler, actions}; use new_session_modal::NewSessionModal; +use project::debugger::{self, breakpoint_store::SourceBreakpoint}; use session::DebugSession; use settings::Settings; +use util::maybe; use workspace::{ShutdownDebugAdapters, Workspace}; pub mod attach_modal; @@ -110,7 +113,9 @@ pub fn init(cx: &mut App) { .active_session() .and_then(|session| session.read(cx).mode().as_running().cloned()) }) { - active_item.update(cx, |item, cx| item.stop_thread(cx)) + cx.defer(move |cx| { + active_item.update(cx, |item, cx| item.stop_thread(cx)) + }) } } }) @@ -155,4 +160,91 @@ pub fn init(cx: &mut App) { }) }) .detach(); + + cx.observe_new({ + move |editor: &mut Editor, _, cx| { + editor + .register_action(cx.listener( + move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| { + maybe!({ + let debug_panel = + editor.workspace()?.read(cx).panel::(cx)?; + let cursor_point: language::Point = editor.selections.newest(cx).head(); + let active_session = debug_panel.read(cx).active_session()?; + + let (buffer, position, _) = editor + .buffer() + .read(cx) + .point_to_buffer_point(cursor_point, cx)?; + + 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, + }; + + active_session + .update(cx, |session_item, _| { + session_item.mode().as_running().cloned() + })? + .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(()) + }); + }, + )) + .detach(); + + editor + .register_action(cx.listener( + move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| { + maybe!({ + let debug_panel = + editor.workspace()?.read(cx).panel::(cx)?; + let active_session = debug_panel.read(cx).active_session()?; + + let text = editor.text_for_range( + editor.selections.newest(cx).range(), + &mut None, + window, + cx, + )?; + + active_session + .update(cx, |session_item, _| { + session_item.mode().as_running().cloned() + })? + .update(cx, |state, cx| { + let stack_id = state.selected_stack_frame_id(cx); + + state.session().update(cx, |session, cx| { + session.evaluate(text, None, stack_id, None, cx); + }) + }); + Some(()) + }); + }, + )) + .detach(); + } + }) + .detach(); } diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 038478a9dbbe676eb36fd8437aaf29bca82bbc30..c69a2259b207a15a9a5374cea045e2bbf44dca77 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -1,5 +1,7 @@ pub mod running; +use std::sync::OnceLock; + use dap::client::SessionId; use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity}; use project::Project; @@ -30,6 +32,7 @@ impl DebugSessionState { pub struct DebugSession { remote_id: Option, mode: DebugSessionState, + label: OnceLock, dap_store: WeakEntity, _debug_panel: WeakEntity, _worktree_store: WeakEntity, @@ -68,6 +71,7 @@ impl DebugSession { })], remote_id: None, mode: DebugSessionState::Running(mode), + label: OnceLock::new(), dap_store: project.read(cx).dap_store().downgrade(), _debug_panel, _worktree_store: project.read(cx).worktree_store().downgrade(), @@ -92,36 +96,45 @@ impl DebugSession { } pub(crate) fn label(&self, cx: &App) -> String { + if let Some(label) = self.label.get() { + return label.to_owned(); + } + let session_id = match &self.mode { DebugSessionState::Running(running_state) => running_state.read(cx).session_id(), }; + let Ok(Some(session)) = self .dap_store .read_with(cx, |store, _| store.session_by_id(session_id)) else { return "".to_owned(); }; - session - .read(cx) - .as_local() - .expect("Remote Debug Sessions are not implemented yet") - .label() + + self.label + .get_or_init(|| { + session + .read(cx) + .as_local() + .expect("Remote Debug Sessions are not implemented yet") + .label() + }) + .to_owned() } pub(crate) fn label_element(&self, cx: &App) -> AnyElement { let label = self.label(cx); - let (icon, color) = match &self.mode { + let icon = match &self.mode { DebugSessionState::Running(state) => { if state.read(cx).session().read(cx).is_terminated() { - (Some(Indicator::dot().color(Color::Error)), Color::Error) + Some(Indicator::dot().color(Color::Error)) } else { match state.read(cx).thread_status(cx).unwrap_or_default() { - project::debugger::session::ThreadStatus::Stopped => ( - Some(Indicator::dot().color(Color::Conflict)), - Color::Conflict, - ), - _ => (Some(Indicator::dot().color(Color::Success)), Color::Success), + project::debugger::session::ThreadStatus::Stopped => { + Some(Indicator::dot().color(Color::Conflict)) + } + _ => Some(Indicator::dot().color(Color::Success)), } } } @@ -131,7 +144,7 @@ impl DebugSession { .gap_2() .when_some(icon, |this, indicator| this.child(indicator)) .justify_between() - .child(Label::new(label).color(color)) + .child(Label::new(label)) .into_any_element() } } diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 9fa7ae28944b1bd8a5a3b6fb24ab14eb9afc9931..35009b08a20cdb3ad729b918bdd7e59d261daea3 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -432,6 +432,10 @@ impl RunningState { self.session_id } + pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option { + self.stack_frame_list.read(cx).selected_stack_frame_id() + } + #[cfg(test)] pub fn stack_frame_list(&self) -> &Entity { &self.stack_frame_list @@ -492,7 +496,6 @@ impl RunningState { } } - #[cfg(test)] pub(crate) fn selected_thread_id(&self) -> Option { self.thread_id } diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 4973b854c3a129acffffb41b07c1d7e1af06f0ea..e8fad5b4437069dea71202d97554b9b6004f3491 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -141,7 +141,7 @@ impl Console { state.evaluate( expression, Some(dap::EvaluateArgumentsContext::Variables), - self.stack_frame_list.read(cx).current_stack_frame_id(), + self.stack_frame_list.read(cx).selected_stack_frame_id(), None, cx, ); @@ -384,7 +384,7 @@ impl ConsoleQueryBarCompletionProvider { ) -> Task>>> { let completion_task = console.update(cx, |console, cx| { console.session.update(cx, |state, cx| { - let frame_id = console.stack_frame_list.read(cx).current_stack_frame_id(); + let frame_id = console.stack_frame_list.read(cx).selected_stack_frame_id(); state.completions( CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id), diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index 745798170da3ff2b4eb9b5ef673c99f9d82fdd1c..12dbef172228f8a2d224871b292b3ee71c776581 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -31,7 +31,7 @@ pub struct StackFrameList { invalidate: bool, entries: Vec, workspace: WeakEntity, - current_stack_frame_id: Option, + selected_stack_frame_id: Option, scrollbar_state: ScrollbarState, } @@ -85,7 +85,7 @@ impl StackFrameList { _subscription, invalidate: true, entries: Default::default(), - current_stack_frame_id: None, + selected_stack_frame_id: None, } } @@ -132,8 +132,8 @@ impl StackFrameList { .unwrap_or(0) } - pub fn current_stack_frame_id(&self) -> Option { - self.current_stack_frame_id + pub fn selected_stack_frame_id(&self) -> Option { + self.selected_stack_frame_id } pub(super) fn refresh(&mut self, cx: &mut Context) { @@ -188,20 +188,20 @@ impl StackFrameList { } pub fn go_to_selected_stack_frame(&mut self, window: &Window, cx: &mut Context) { - if let Some(current_stack_frame_id) = self.current_stack_frame_id { + if let Some(selected_stack_frame_id) = self.selected_stack_frame_id { let frame = self .entries .iter() .find_map(|entry| match entry { StackFrameEntry::Normal(dap) => { - if dap.id == current_stack_frame_id { + if dap.id == selected_stack_frame_id { Some(dap) } else { None } } StackFrameEntry::Collapsed(daps) => { - daps.iter().find(|dap| dap.id == current_stack_frame_id) + daps.iter().find(|dap| dap.id == selected_stack_frame_id) } }) .cloned(); @@ -220,7 +220,7 @@ impl StackFrameList { window: &Window, cx: &mut Context, ) -> Task> { - self.current_stack_frame_id = Some(stack_frame.id); + self.selected_stack_frame_id = Some(stack_frame.id); cx.emit(StackFrameListEvent::SelectedStackFrameChanged( stack_frame.id, @@ -319,7 +319,7 @@ impl StackFrameList { cx: &mut Context, ) -> AnyElement { let source = stack_frame.source.clone(); - let is_selected_frame = Some(stack_frame.id) == self.current_stack_frame_id; + let is_selected_frame = Some(stack_frame.id) == self.selected_stack_frame_id; let formatted_path = format!( "{}:{}", diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 0aa806e3e959e467df3f7eda93a6a854ec864b76..0cabd3df8b71456ad41d922b4fbfe97277041922 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -191,7 +191,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( .update(cx, |state, _| state.stack_frame_list().clone()); stack_frame_list.update(cx, |stack_frame_list, cx| { - assert_eq!(Some(1), stack_frame_list.current_stack_frame_id()); + assert_eq!(Some(1), stack_frame_list.selected_stack_frame_id()); assert_eq!(stack_frames, stack_frame_list.dap_stack_frames(cx)); }); }); @@ -425,7 +425,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC .unwrap(); stack_frame_list.update(cx, |stack_frame_list, cx| { - assert_eq!(Some(1), stack_frame_list.current_stack_frame_id()); + assert_eq!(Some(1), stack_frame_list.selected_stack_frame_id()); assert_eq!(stack_frames, stack_frame_list.dap_stack_frames(cx)); }); @@ -440,7 +440,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC cx.run_until_parked(); stack_frame_list.update(cx, |stack_frame_list, cx| { - assert_eq!(Some(2), stack_frame_list.current_stack_frame_id()); + assert_eq!(Some(2), stack_frame_list.selected_stack_frame_id()); assert_eq!(stack_frames, stack_frame_list.dap_stack_frames(cx)); }); diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 13ffd97bc43f30a4dd7bcd0d63559bfb2cad2613..8c352731fbedc88357c4da9a3bab59395fb932d7 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -212,7 +212,7 @@ async fn test_basic_fetch_initial_scope_and_variables( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.current_stack_frame_id()) + (list.flatten_entries(), list.selected_stack_frame_id()) }); assert_eq!(stack_frames, stack_frame_list); @@ -483,7 +483,7 @@ async fn test_fetch_variables_for_multiple_scopes( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.current_stack_frame_id()) + (list.flatten_entries(), list.selected_stack_frame_id()) }); assert_eq!(Some(1), stack_frame_id); @@ -1565,7 +1565,7 @@ async fn test_variable_list_only_sends_requests_when_rendering( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.current_stack_frame_id()) + (list.flatten_entries(), list.selected_stack_frame_id()) }); assert_eq!(Some(1), stack_frame_id); @@ -1877,7 +1877,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.current_stack_frame_id()) + (list.flatten_entries(), list.selected_stack_frame_id()) }); let variable_list = running_state.variable_list().read(cx); @@ -1888,7 +1888,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state .stack_frame_list() .read(cx) - .current_stack_frame_id(), + .selected_stack_frame_id(), Some(1) ); @@ -1934,7 +1934,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.current_stack_frame_id()) + (list.flatten_entries(), list.selected_stack_frame_id()) }); let variable_list = running_state.variable_list().read(cx); diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index a7d58dccf239b03f9ffbf0cd046cd11b38da0068..716fc602bd0bdfa098d580afa71fdc0e7db4f58a 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -35,6 +35,7 @@ assets.workspace = true client.workspace = true clock.workspace = true collections.workspace = true +command_palette_hooks.workspace = true convert_case.workspace = true db.workspace = true buffer_diff.workspace = true diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index f0fbc168df64207e0a400c1b5562776b83f8ac36..5a3583a898ce3876888f7a2bd1a2fe519233d1dc 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -408,6 +408,8 @@ actions!( DisableBreakpoint, EnableBreakpoint, EditLogBreakpoint, + DebuggerRunToCursor, + DebuggerEvaluateSelectedText, ToggleAutoSignatureHelp, ToggleGitBlameInline, OpenGitBlameCommit, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0fc53debe9418cb1c43d173f98e1bacd6bd65241..de674fc25b0dde16e1a794f213bbf9149d780691 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6415,6 +6415,9 @@ impl Editor { "Set Breakpoint" }; + let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx) + .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor)); + let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state { BreakpointState::Enabled => Some("Disable"), BreakpointState::Disabled => Some("Enable"), @@ -6426,6 +6429,21 @@ impl Editor { ui::ContextMenu::build(window, cx, |menu, _, _cx| { menu.on_blur_subscription(Subscription::new(|| {})) .context(focus_handle) + .when(run_to_cursor, |this| { + let weak_editor = weak_editor.clone(); + this.entry("Run to cursor", None, move |window, cx| { + weak_editor + .update(cx, |editor, cx| { + editor.change_selections(None, window, cx, |s| { + s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]) + }); + }) + .ok(); + + window.dispatch_action(Box::new(DebuggerRunToCursor), cx); + }) + .separator() + }) .when_some(toggle_state_msg, |this, msg| { this.entry(msg, None, { let weak_editor = weak_editor.clone(); diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 3c282d8253c4f220bfbbb130e52dd88175beccb5..028ed315b8a062aee60d479ed1bf98a6af7af772 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,10 +1,10 @@ -use crate::CopyAndTrim; -use crate::actions::FormatSelections; use crate::{ - Copy, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EditorMode, - FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, GoToTypeDefinition, - Paste, Rename, RevealInFileManager, SelectMode, ToDisplayPoint, ToggleCodeActions, - actions::Format, selections_collection::SelectionsCollection, + Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DebuggerEvaluateSelectedText, DisplayPoint, + DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDeclaration, GoToDefinition, + GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, + ToDisplayPoint, ToggleCodeActions, + actions::{Format, FormatSelections}, + selections_collection::SelectionsCollection, }; use gpui::prelude::FluentBuilder; use gpui::{Context, DismissEvent, Entity, Focusable as _, Pixels, Point, Subscription, Window}; @@ -169,9 +169,19 @@ pub fn deploy_context_menu( .is_some() }); + let evaluate_selection = command_palette_hooks::CommandPaletteFilter::try_global(cx) + .map_or(false, |filter| { + !filter.is_hidden(&DebuggerEvaluateSelectedText) + }); + ui::ContextMenu::build(window, cx, |menu, _window, _cx| { let builder = menu .on_blur_subscription(Subscription::new(|| {})) + .when(evaluate_selection && has_selections, |builder| { + builder + .action("Evaluate Selection", Box::new(DebuggerEvaluateSelectedText)) + .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/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index 524eed44a90215dc303915d782b06d3a16da5374..a5e5a134b17b7d4474bb00b7032c9d0bad962047 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -218,7 +218,7 @@ impl BreakpointStore { } } - fn abs_path_from_buffer(buffer: &Entity, cx: &App) -> Option> { + pub fn abs_path_from_buffer(buffer: &Entity, cx: &App) -> Option> { worktree::File::from_dyn(buffer.read(cx).file()) .and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok()) .map(Arc::::from) diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 43cbe52946cb6909a0e49d3c4a32945aed5e06eb..47b082fe918970f6959a8aad96dce8a2a95da36d 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -1,6 +1,8 @@ use crate::project_settings::ProjectSettings; -use super::breakpoint_store::{BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason}; +use super::breakpoint_store::{ + BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint, +}; use super::dap_command::{ self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand, @@ -163,6 +165,7 @@ pub struct LocalMode { config: DebugAdapterConfig, adapter: Arc, breakpoint_store: Entity, + tmp_breakpoint: Option, } fn client_source(abs_path: &Path) -> dap::Source { @@ -383,6 +386,7 @@ impl LocalMode { client, adapter, breakpoint_store, + tmp_breakpoint: None, config: config.clone(), }; @@ -431,6 +435,7 @@ impl LocalMode { .read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx)) .into_iter() .filter(|bp| bp.state.is_enabled()) + .chain(self.tmp_breakpoint.clone()) .map(Into::into) .collect(); @@ -1040,6 +1045,40 @@ impl Session { } } + pub fn run_to_position( + &mut self, + breakpoint: SourceBreakpoint, + active_thread_id: ThreadId, + cx: &mut Context, + ) { + match &mut self.mode { + Mode::Local(local_mode) => { + if !matches!( + self.thread_states.thread_state(active_thread_id), + Some(ThreadStatus::Stopped) + ) { + return; + }; + let path = breakpoint.path.clone(); + local_mode.tmp_breakpoint = Some(breakpoint); + let task = local_mode.send_breakpoints_from_path( + path, + BreakpointUpdatedReason::Toggled, + cx, + ); + + cx.spawn(async move |this, cx| { + task.await; + this.update(cx, |this, cx| { + this.continue_thread(active_thread_id, cx); + }) + }) + .detach(); + } + Mode::Remote(_) => {} + } + } + pub fn output( &self, since: OutputToken, @@ -1086,6 +1125,16 @@ impl Session { } fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context) { + if let Some((local, path)) = self.as_local_mut().and_then(|local| { + let breakpoint = local.tmp_breakpoint.take()?; + let path = breakpoint.path.clone(); + Some((local, path)) + }) { + local + .send_breakpoints_from_path(path, BreakpointUpdatedReason::Toggled, cx) + .detach(); + }; + if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() { self.thread_states.stop_all_threads();