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:
Anthony Eid 2025-04-09 15:57:29 -04:00 committed by GitHub
parent 780143298a
commit 2752c08810
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 334 additions and 102 deletions

View file

@ -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();