debugger: Add run to cursor and evaluate selected text actions (#28405)
## 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>
This commit is contained in:
parent
780143298a
commit
2752c08810
15 changed files with 334 additions and 102 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4600,6 +4600,7 @@ dependencies = [
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"command_palette_hooks",
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"ctor",
|
"ctor",
|
||||||
"db",
|
"db",
|
||||||
|
|
|
@ -15,6 +15,7 @@ use gpui::{
|
||||||
Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
Focusable, Subscription, Task, WeakEntity, actions,
|
Focusable, Subscription, Task, WeakEntity, actions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use project::{
|
use project::{
|
||||||
Project,
|
Project,
|
||||||
debugger::{
|
debugger::{
|
||||||
|
@ -94,27 +95,9 @@ impl DebugPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(
|
fn filter_action_types(&self, cx: &mut App) {
|
||||||
workspace: WeakEntity<Workspace>,
|
let (has_active_session, supports_restart, support_step_back, status) = self
|
||||||
cx: AsyncWindowContext,
|
.active_session()
|
||||||
) -> Task<Result<Entity<Self>>> {
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
workspace.update_in(cx, |workspace, window, cx| {
|
|
||||||
let debug_panel = DebugPanel::new(workspace, window, cx);
|
|
||||||
|
|
||||||
workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
|
|
||||||
workspace.project().read(cx).breakpoint_store().update(
|
|
||||||
cx,
|
|
||||||
|breakpoint_store, cx| {
|
|
||||||
breakpoint_store.clear_breakpoints(cx);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
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| {
|
.map(|item| {
|
||||||
let running = item.read(cx).mode().as_running().cloned();
|
let running = item.read(cx).mode().as_running().cloned();
|
||||||
|
|
||||||
|
@ -122,27 +105,33 @@ impl DebugPanel {
|
||||||
Some(running) => {
|
Some(running) => {
|
||||||
let caps = running.read(cx).capabilities(cx);
|
let caps = running.read(cx).capabilities(cx);
|
||||||
(
|
(
|
||||||
true,
|
!running.read(cx).session().read(cx).is_terminated(),
|
||||||
caps.supports_restart_request.unwrap_or_default(),
|
caps.supports_restart_request.unwrap_or_default(),
|
||||||
caps.supports_step_back.unwrap_or_default(),
|
caps.supports_step_back.unwrap_or_default(),
|
||||||
|
running.read(cx).thread_status(cx),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => (false, false, false),
|
None => (false, false, false, None),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or((false, false, false))
|
.unwrap_or((false, false, false, None));
|
||||||
});
|
|
||||||
|
|
||||||
let filter = CommandPaletteFilter::global_mut(cx);
|
let filter = CommandPaletteFilter::global_mut(cx);
|
||||||
let debugger_action_types = [
|
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::<Continue>(),
|
||||||
TypeId::of::<StepOver>(),
|
TypeId::of::<StepOver>(),
|
||||||
TypeId::of::<StepInto>(),
|
TypeId::of::<StepInto>(),
|
||||||
TypeId::of::<StepOut>(),
|
TypeId::of::<StepOut>(),
|
||||||
TypeId::of::<Stop>(),
|
TypeId::of::<editor::actions::DebuggerRunToCursor>(),
|
||||||
TypeId::of::<Disconnect>(),
|
TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
|
||||||
TypeId::of::<Pause>(),
|
|
||||||
TypeId::of::<ToggleIgnoreBreakpoints>(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let step_back_action_type = [TypeId::of::<StepBack>()];
|
let step_back_action_type = [TypeId::of::<StepBack>()];
|
||||||
|
@ -162,12 +151,57 @@ impl DebugPanel {
|
||||||
} else {
|
} else {
|
||||||
filter.hide_action_types(&step_back_action_type);
|
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 {
|
} else {
|
||||||
// show only the `debug: start`
|
// show only the `debug: start`
|
||||||
filter.hide_action_types(&debugger_action_types);
|
filter.hide_action_types(&debugger_action_types);
|
||||||
filter.hide_action_types(&step_back_action_type);
|
filter.hide_action_types(&step_back_action_type);
|
||||||
filter.hide_action_types(&restart_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,
|
||||||
|
) -> Task<Result<Entity<Self>>> {
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
|
let debug_panel = DebugPanel::new(workspace, window, cx);
|
||||||
|
|
||||||
|
workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
|
||||||
|
workspace.project().read(cx).breakpoint_store().update(
|
||||||
|
cx,
|
||||||
|
|breakpoint_store, cx| {
|
||||||
|
breakpoint_store.clear_breakpoints(cx);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
|
||||||
|
Self::filter_action_types(debug_panel, cx);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
cx.observe(&debug_panel, |_, debug_panel, cx| {
|
||||||
|
debug_panel.update(cx, |debug_panel, cx| {
|
||||||
|
Self::filter_action_types(debug_panel, cx);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -243,6 +277,12 @@ impl DebugPanel {
|
||||||
cx,
|
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.sessions.push(session_item.clone());
|
||||||
self.activate_session(session_item, window, cx);
|
self.activate_session(session_item, window, cx);
|
||||||
}
|
}
|
||||||
|
@ -360,6 +400,8 @@ impl DebugPanel {
|
||||||
self.active_session = self.sessions.first().cloned();
|
self.active_session = self.sessions.first().cloned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sessions_drop_down_menu(
|
fn sessions_drop_down_menu(
|
||||||
|
@ -378,7 +420,7 @@ impl DebugPanel {
|
||||||
ContextMenu::build(window, cx, move |mut this, _, _| {
|
ContextMenu::build(window, cx, move |mut this, _, _| {
|
||||||
for session in sessions.into_iter() {
|
for session in sessions.into_iter() {
|
||||||
let weak_session = session.downgrade();
|
let weak_session = session.downgrade();
|
||||||
let weak_id = weak_session.entity_id();
|
let weak_session_id = weak_session.entity_id();
|
||||||
|
|
||||||
this = this.custom_entry(
|
this = this.custom_entry(
|
||||||
{
|
{
|
||||||
|
@ -400,7 +442,8 @@ impl DebugPanel {
|
||||||
let weak = weak.clone();
|
let weak = weak.clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
weak.update(cx, |panel, cx| {
|
weak.update(cx, |panel, cx| {
|
||||||
panel.close_session(weak_id, cx);
|
panel
|
||||||
|
.close_session(weak_session_id, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use dap::debugger_settings::DebuggerSettings;
|
use dap::debugger_settings::DebuggerSettings;
|
||||||
use debugger_panel::{DebugPanel, ToggleFocus};
|
use debugger_panel::{DebugPanel, ToggleFocus};
|
||||||
|
use editor::Editor;
|
||||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||||
use gpui::{App, actions};
|
use gpui::{App, EntityInputHandler, actions};
|
||||||
use new_session_modal::NewSessionModal;
|
use new_session_modal::NewSessionModal;
|
||||||
|
use project::debugger::{self, breakpoint_store::SourceBreakpoint};
|
||||||
use session::DebugSession;
|
use session::DebugSession;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
use util::maybe;
|
||||||
use workspace::{ShutdownDebugAdapters, Workspace};
|
use workspace::{ShutdownDebugAdapters, Workspace};
|
||||||
|
|
||||||
pub mod attach_modal;
|
pub mod attach_modal;
|
||||||
|
@ -110,7 +113,9 @@ pub fn init(cx: &mut App) {
|
||||||
.active_session()
|
.active_session()
|
||||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||||
}) {
|
}) {
|
||||||
|
cx.defer(move |cx| {
|
||||||
active_item.update(cx, |item, cx| item.stop_thread(cx))
|
active_item.update(cx, |item, cx| item.stop_thread(cx))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -155,4 +160,91 @@ pub fn init(cx: &mut App) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.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;
|
pub mod running;
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use dap::client::SessionId;
|
use dap::client::SessionId;
|
||||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -30,6 +32,7 @@ impl DebugSessionState {
|
||||||
pub struct DebugSession {
|
pub struct DebugSession {
|
||||||
remote_id: Option<workspace::ViewId>,
|
remote_id: Option<workspace::ViewId>,
|
||||||
mode: DebugSessionState,
|
mode: DebugSessionState,
|
||||||
|
label: OnceLock<String>,
|
||||||
dap_store: WeakEntity<DapStore>,
|
dap_store: WeakEntity<DapStore>,
|
||||||
_debug_panel: WeakEntity<DebugPanel>,
|
_debug_panel: WeakEntity<DebugPanel>,
|
||||||
_worktree_store: WeakEntity<WorktreeStore>,
|
_worktree_store: WeakEntity<WorktreeStore>,
|
||||||
|
@ -68,6 +71,7 @@ impl DebugSession {
|
||||||
})],
|
})],
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
mode: DebugSessionState::Running(mode),
|
mode: DebugSessionState::Running(mode),
|
||||||
|
label: OnceLock::new(),
|
||||||
dap_store: project.read(cx).dap_store().downgrade(),
|
dap_store: project.read(cx).dap_store().downgrade(),
|
||||||
_debug_panel,
|
_debug_panel,
|
||||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||||
|
@ -92,36 +96,45 @@ impl DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn label(&self, cx: &App) -> String {
|
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 {
|
let session_id = match &self.mode {
|
||||||
DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
|
DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(Some(session)) = self
|
let Ok(Some(session)) = self
|
||||||
.dap_store
|
.dap_store
|
||||||
.read_with(cx, |store, _| store.session_by_id(session_id))
|
.read_with(cx, |store, _| store.session_by_id(session_id))
|
||||||
else {
|
else {
|
||||||
return "".to_owned();
|
return "".to_owned();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.label
|
||||||
|
.get_or_init(|| {
|
||||||
session
|
session
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.as_local()
|
.as_local()
|
||||||
.expect("Remote Debug Sessions are not implemented yet")
|
.expect("Remote Debug Sessions are not implemented yet")
|
||||||
.label()
|
.label()
|
||||||
|
})
|
||||||
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
|
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
|
||||||
let label = self.label(cx);
|
let label = self.label(cx);
|
||||||
|
|
||||||
let (icon, color) = match &self.mode {
|
let icon = match &self.mode {
|
||||||
DebugSessionState::Running(state) => {
|
DebugSessionState::Running(state) => {
|
||||||
if state.read(cx).session().read(cx).is_terminated() {
|
if state.read(cx).session().read(cx).is_terminated() {
|
||||||
(Some(Indicator::dot().color(Color::Error)), Color::Error)
|
Some(Indicator::dot().color(Color::Error))
|
||||||
} else {
|
} else {
|
||||||
match state.read(cx).thread_status(cx).unwrap_or_default() {
|
match state.read(cx).thread_status(cx).unwrap_or_default() {
|
||||||
project::debugger::session::ThreadStatus::Stopped => (
|
project::debugger::session::ThreadStatus::Stopped => {
|
||||||
Some(Indicator::dot().color(Color::Conflict)),
|
Some(Indicator::dot().color(Color::Conflict))
|
||||||
Color::Conflict,
|
}
|
||||||
),
|
_ => Some(Indicator::dot().color(Color::Success)),
|
||||||
_ => (Some(Indicator::dot().color(Color::Success)), Color::Success),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +144,7 @@ impl DebugSession {
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when_some(icon, |this, indicator| this.child(indicator))
|
.when_some(icon, |this, indicator| this.child(indicator))
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(Label::new(label).color(color))
|
.child(Label::new(label))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,6 +432,10 @@ impl RunningState {
|
||||||
self.session_id
|
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)]
|
#[cfg(test)]
|
||||||
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
|
||||||
&self.stack_frame_list
|
&self.stack_frame_list
|
||||||
|
@ -492,7 +496,6 @@ impl RunningState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
|
pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
|
||||||
self.thread_id
|
self.thread_id
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl Console {
|
||||||
state.evaluate(
|
state.evaluate(
|
||||||
expression,
|
expression,
|
||||||
Some(dap::EvaluateArgumentsContext::Variables),
|
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,
|
None,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -384,7 +384,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||||
) -> Task<Result<Option<Vec<Completion>>>> {
|
) -> Task<Result<Option<Vec<Completion>>>> {
|
||||||
let completion_task = console.update(cx, |console, cx| {
|
let completion_task = console.update(cx, |console, cx| {
|
||||||
console.session.update(cx, |state, 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(
|
state.completions(
|
||||||
CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id),
|
CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id),
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct StackFrameList {
|
||||||
invalidate: bool,
|
invalidate: bool,
|
||||||
entries: Vec<StackFrameEntry>,
|
entries: Vec<StackFrameEntry>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
current_stack_frame_id: Option<StackFrameId>,
|
selected_stack_frame_id: Option<StackFrameId>,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ impl StackFrameList {
|
||||||
_subscription,
|
_subscription,
|
||||||
invalidate: true,
|
invalidate: true,
|
||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
current_stack_frame_id: None,
|
selected_stack_frame_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,8 +132,8 @@ impl StackFrameList {
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_stack_frame_id(&self) -> Option<StackFrameId> {
|
pub fn selected_stack_frame_id(&self) -> Option<StackFrameId> {
|
||||||
self.current_stack_frame_id
|
self.selected_stack_frame_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn refresh(&mut self, cx: &mut Context<Self>) {
|
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>) {
|
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
|
let frame = self
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|entry| match entry {
|
.find_map(|entry| match entry {
|
||||||
StackFrameEntry::Normal(dap) => {
|
StackFrameEntry::Normal(dap) => {
|
||||||
if dap.id == current_stack_frame_id {
|
if dap.id == selected_stack_frame_id {
|
||||||
Some(dap)
|
Some(dap)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StackFrameEntry::Collapsed(daps) => {
|
StackFrameEntry::Collapsed(daps) => {
|
||||||
daps.iter().find(|dap| dap.id == current_stack_frame_id)
|
daps.iter().find(|dap| dap.id == selected_stack_frame_id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.cloned();
|
.cloned();
|
||||||
|
@ -220,7 +220,7 @@ impl StackFrameList {
|
||||||
window: &Window,
|
window: &Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
self.current_stack_frame_id = Some(stack_frame.id);
|
self.selected_stack_frame_id = Some(stack_frame.id);
|
||||||
|
|
||||||
cx.emit(StackFrameListEvent::SelectedStackFrameChanged(
|
cx.emit(StackFrameListEvent::SelectedStackFrameChanged(
|
||||||
stack_frame.id,
|
stack_frame.id,
|
||||||
|
@ -319,7 +319,7 @@ impl StackFrameList {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let source = stack_frame.source.clone();
|
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!(
|
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());
|
.update(cx, |state, _| state.stack_frame_list().clone());
|
||||||
|
|
||||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
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));
|
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();
|
.unwrap();
|
||||||
|
|
||||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
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));
|
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();
|
cx.run_until_parked();
|
||||||
|
|
||||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
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));
|
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| {
|
running_state.update(cx, |running_state, cx| {
|
||||||
let (stack_frame_list, stack_frame_id) =
|
let (stack_frame_list, stack_frame_id) =
|
||||||
running_state.stack_frame_list().update(cx, |list, _| {
|
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);
|
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| {
|
running_state.update(cx, |running_state, cx| {
|
||||||
let (stack_frame_list, stack_frame_id) =
|
let (stack_frame_list, stack_frame_id) =
|
||||||
running_state.stack_frame_list().update(cx, |list, _| {
|
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);
|
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| {
|
running_state.update(cx, |running_state, cx| {
|
||||||
let (stack_frame_list, stack_frame_id) =
|
let (stack_frame_list, stack_frame_id) =
|
||||||
running_state.stack_frame_list().update(cx, |list, _| {
|
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);
|
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| {
|
running_state.update(cx, |running_state, cx| {
|
||||||
let (stack_frame_list, stack_frame_id) =
|
let (stack_frame_list, stack_frame_id) =
|
||||||
running_state.stack_frame_list().update(cx, |list, _| {
|
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);
|
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
|
running_state
|
||||||
.stack_frame_list()
|
.stack_frame_list()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.current_stack_frame_id(),
|
.selected_stack_frame_id(),
|
||||||
Some(1)
|
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| {
|
running_state.update(cx, |running_state, cx| {
|
||||||
let (stack_frame_list, stack_frame_id) =
|
let (stack_frame_list, stack_frame_id) =
|
||||||
running_state.stack_frame_list().update(cx, |list, _| {
|
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);
|
let variable_list = running_state.variable_list().read(cx);
|
||||||
|
|
|
@ -35,6 +35,7 @@ assets.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
command_palette_hooks.workspace = true
|
||||||
convert_case.workspace = true
|
convert_case.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
buffer_diff.workspace = true
|
buffer_diff.workspace = true
|
||||||
|
|
|
@ -408,6 +408,8 @@ actions!(
|
||||||
DisableBreakpoint,
|
DisableBreakpoint,
|
||||||
EnableBreakpoint,
|
EnableBreakpoint,
|
||||||
EditLogBreakpoint,
|
EditLogBreakpoint,
|
||||||
|
DebuggerRunToCursor,
|
||||||
|
DebuggerEvaluateSelectedText,
|
||||||
ToggleAutoSignatureHelp,
|
ToggleAutoSignatureHelp,
|
||||||
ToggleGitBlameInline,
|
ToggleGitBlameInline,
|
||||||
OpenGitBlameCommit,
|
OpenGitBlameCommit,
|
||||||
|
|
|
@ -6415,6 +6415,9 @@ impl Editor {
|
||||||
"Set Breakpoint"
|
"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 {
|
let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
|
||||||
BreakpointState::Enabled => Some("Disable"),
|
BreakpointState::Enabled => Some("Disable"),
|
||||||
BreakpointState::Disabled => Some("Enable"),
|
BreakpointState::Disabled => Some("Enable"),
|
||||||
|
@ -6426,6 +6429,21 @@ impl Editor {
|
||||||
ui::ContextMenu::build(window, cx, |menu, _, _cx| {
|
ui::ContextMenu::build(window, cx, |menu, _, _cx| {
|
||||||
menu.on_blur_subscription(Subscription::new(|| {}))
|
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||||
.context(focus_handle)
|
.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| {
|
.when_some(toggle_state_msg, |this, msg| {
|
||||||
this.entry(msg, None, {
|
this.entry(msg, None, {
|
||||||
let weak_editor = weak_editor.clone();
|
let weak_editor = weak_editor.clone();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::CopyAndTrim;
|
|
||||||
use crate::actions::FormatSelections;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Copy, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EditorMode,
|
Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DebuggerEvaluateSelectedText, DisplayPoint,
|
||||||
FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, GoToTypeDefinition,
|
DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDeclaration, GoToDefinition,
|
||||||
Paste, Rename, RevealInFileManager, SelectMode, ToDisplayPoint, ToggleCodeActions,
|
GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode,
|
||||||
actions::Format, selections_collection::SelectionsCollection,
|
ToDisplayPoint, ToggleCodeActions,
|
||||||
|
actions::{Format, FormatSelections},
|
||||||
|
selections_collection::SelectionsCollection,
|
||||||
};
|
};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{Context, DismissEvent, Entity, Focusable as _, Pixels, Point, Subscription, Window};
|
use gpui::{Context, DismissEvent, Entity, Focusable as _, Pixels, Point, Subscription, Window};
|
||||||
|
@ -169,9 +169,19 @@ pub fn deploy_context_menu(
|
||||||
.is_some()
|
.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| {
|
ui::ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||||
let builder = menu
|
let builder = menu
|
||||||
.on_blur_subscription(Subscription::new(|| {}))
|
.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 Definition", Box::new(GoToDefinition))
|
||||||
.action("Go to Declaration", Box::new(GoToDeclaration))
|
.action("Go to Declaration", Box::new(GoToDeclaration))
|
||||||
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
|
.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())
|
worktree::File::from_dyn(buffer.read(cx).file())
|
||||||
.and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok())
|
.and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok())
|
||||||
.map(Arc::<Path>::from)
|
.map(Arc::<Path>::from)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::project_settings::ProjectSettings;
|
use crate::project_settings::ProjectSettings;
|
||||||
|
|
||||||
use super::breakpoint_store::{BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason};
|
use super::breakpoint_store::{
|
||||||
|
BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
|
||||||
|
};
|
||||||
use super::dap_command::{
|
use super::dap_command::{
|
||||||
self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
|
self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
|
||||||
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
||||||
|
@ -163,6 +165,7 @@ pub struct LocalMode {
|
||||||
config: DebugAdapterConfig,
|
config: DebugAdapterConfig,
|
||||||
adapter: Arc<dyn DebugAdapter>,
|
adapter: Arc<dyn DebugAdapter>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
tmp_breakpoint: Option<SourceBreakpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_source(abs_path: &Path) -> dap::Source {
|
fn client_source(abs_path: &Path) -> dap::Source {
|
||||||
|
@ -383,6 +386,7 @@ impl LocalMode {
|
||||||
client,
|
client,
|
||||||
adapter,
|
adapter,
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
|
tmp_breakpoint: None,
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -431,6 +435,7 @@ impl LocalMode {
|
||||||
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
|
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|bp| bp.state.is_enabled())
|
.filter(|bp| bp.state.is_enabled())
|
||||||
|
.chain(self.tmp_breakpoint.clone())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.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(
|
pub fn output(
|
||||||
&self,
|
&self,
|
||||||
since: OutputToken,
|
since: OutputToken,
|
||||||
|
@ -1086,6 +1125,16 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
|
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() {
|
if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() {
|
||||||
self.thread_states.stop_all_threads();
|
self.thread_states.stop_all_threads();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue