Cargo.lock 🔗
@@ -4600,6 +4600,7 @@ dependencies = [
"client",
"clock",
"collections",
+ "command_palette_hooks",
"convert_case 0.8.0",
"ctor",
"db",
Anthony Eid , Max Brunsfeld , and Remco Smits created
## Summary
### Actions
This PR implements actions that allow a user to "run to cursor" and
"evaluate selected text" while there's an active debug session and
exposes the functionality to the UI as well.
- Run to cursor: Can be accessed by right clicking on the gutter
- Evaluate selected text: Can be accessed by selecting text then right
clicking in the editor
### Bug fixes
I also fixed these bugs as well
- Panic when using debugger: Stop action
- Debugger actions command palette filter not working properly in all
cases
- We stopped displaying the correct label in the session's context menu
when a session was terminated
Release Notes:
- N/A
---------
Co-authored-by: Max Brunsfeld <max@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
Cargo.lock | 1
crates/debugger_ui/src/debugger_panel.rs | 159 +++++--
crates/debugger_ui/src/debugger_ui.rs | 96 ++++
crates/debugger_ui/src/session.rs | 39 +
crates/debugger_ui/src/session/running.rs | 5
crates/debugger_ui/src/session/running/console.rs | 4
crates/debugger_ui/src/session/running/stack_frame_list.rs | 18
crates/debugger_ui/src/tests/stack_frame_list.rs | 6
crates/debugger_ui/src/tests/variable_list.rs | 12
crates/editor/Cargo.toml | 1
crates/editor/src/actions.rs | 2
crates/editor/src/editor.rs | 18
crates/editor/src/mouse_context_menu.rs | 22
crates/project/src/debugger/breakpoint_store.rs | 2
crates/project/src/debugger/session.rs | 51 ++
15 files changed, 334 insertions(+), 102 deletions(-)
@@ -4600,6 +4600,7 @@ dependencies = [
"client",
"clock",
"collections",
+ "command_palette_hooks",
"convert_case 0.8.0",
"ctor",
"db",
@@ -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::<Disconnect>(),
+ TypeId::of::<Stop>(),
+ TypeId::of::<ToggleIgnoreBreakpoints>(),
+ ];
+
+ let running_action_types = [TypeId::of::<Pause>()];
+
+ let stopped_action_type = [
+ TypeId::of::<Continue>(),
+ TypeId::of::<StepOver>(),
+ TypeId::of::<StepInto>(),
+ TypeId::of::<StepOut>(),
+ TypeId::of::<editor::actions::DebuggerRunToCursor>(),
+ TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
+ ];
+
+ let step_back_action_type = [TypeId::of::<StepBack>()];
+ let restart_action_type = [TypeId::of::<Restart>()];
+
+ 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<Workspace>,
cx: AsyncWindowContext,
@@ -111,63 +193,15 @@ impl DebugPanel {
)
});
+ cx.observe_new::<DebugPanel>(|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::<Continue>(),
- TypeId::of::<StepOver>(),
- TypeId::of::<StepInto>(),
- TypeId::of::<StepOut>(),
- TypeId::of::<Stop>(),
- TypeId::of::<Disconnect>(),
- TypeId::of::<Pause>(),
- TypeId::of::<ToggleIgnoreBreakpoints>(),
- ];
-
- let step_back_action_type = [TypeId::of::<StepBack>()];
- let restart_action_type = [TypeId::of::<Restart>()];
-
- 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();
}
@@ -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::<DebugPanel>(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::<DebugPanel>(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();
}
@@ -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<workspace::ViewId>,
mode: DebugSessionState,
+ label: OnceLock<String>,
dap_store: WeakEntity<DapStore>,
_debug_panel: WeakEntity<DebugPanel>,
_worktree_store: WeakEntity<WorktreeStore>,
@@ -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()
}
}
@@ -432,6 +432,10 @@ impl RunningState {
self.session_id
}
+ pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
+ self.stack_frame_list.read(cx).selected_stack_frame_id()
+ }
+
#[cfg(test)]
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
&self.stack_frame_list
@@ -492,7 +496,6 @@ impl RunningState {
}
}
- #[cfg(test)]
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
self.thread_id
}
@@ -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<Result<Option<Vec<Completion>>>> {
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),
@@ -31,7 +31,7 @@ pub struct StackFrameList {
invalidate: bool,
entries: Vec<StackFrameEntry>,
workspace: WeakEntity<Workspace>,
- current_stack_frame_id: Option<StackFrameId>,
+ selected_stack_frame_id: Option<StackFrameId>,
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<StackFrameId> {
- self.current_stack_frame_id
+ pub fn selected_stack_frame_id(&self) -> Option<StackFrameId> {
+ self.selected_stack_frame_id
}
pub(super) fn refresh(&mut self, cx: &mut Context<Self>) {
@@ -188,20 +188,20 @@ impl StackFrameList {
}
pub fn go_to_selected_stack_frame(&mut self, window: &Window, cx: &mut Context<Self>) {
- 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<Self>,
) -> Task<Result<()>> {
- 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<Self>,
) -> 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!(
"{}:{}",
@@ -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));
});
@@ -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);
@@ -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
@@ -408,6 +408,8 @@ actions!(
DisableBreakpoint,
EnableBreakpoint,
EditLogBreakpoint,
+ DebuggerRunToCursor,
+ DebuggerEvaluateSelectedText,
ToggleAutoSignatureHelp,
ToggleGitBlameInline,
OpenGitBlameCommit,
@@ -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();
@@ -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))
@@ -218,7 +218,7 @@ impl BreakpointStore {
}
}
- fn abs_path_from_buffer(buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> {
+ pub fn abs_path_from_buffer(buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> {
worktree::File::from_dyn(buffer.read(cx).file())
.and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok())
.map(Arc::<Path>::from)
@@ -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<dyn DebugAdapter>,
breakpoint_store: Entity<BreakpointStore>,
+ tmp_breakpoint: Option<SourceBreakpoint>,
}
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<Self>,
+ ) {
+ 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<Self>) {
+ 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();