debugger: Extend `f5` binding to contextually rerun the last session (#31753)

Cole Miller created

Release Notes:

- Debugger Beta: if there is no stopped or running session, `f5` now
reruns the last session, or opens the new session modal if there is no
previously-run session.

Change summary

assets/keymaps/default-linux.json        | 17 ++++++++++++++---
assets/keymaps/default-macos.json        | 19 ++++++++++++++++---
crates/debugger_ui/src/debugger_panel.rs | 22 ++++++++++++++++++----
crates/project/src/debugger/session.rs   |  4 ++++
crates/workspace/src/workspace.rs        | 19 ++++++++++++++++++-
5 files changed, 70 insertions(+), 11 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -31,8 +31,6 @@
       "ctrl-,": "zed::OpenSettings",
       "ctrl-q": "zed::Quit",
       "f4": "debugger::Start",
-      "alt-f4": "debugger::RerunLastSession",
-      "f5": "debugger::Continue",
       "shift-f5": "debugger::Stop",
       "ctrl-shift-f5": "debugger::Restart",
       "f6": "debugger::Pause",
@@ -583,11 +581,24 @@
       "ctrl-alt-r": "task::Rerun",
       "alt-t": "task::Rerun",
       "alt-shift-t": "task::Spawn",
-      "alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
+      "alt-shift-r": ["task::Spawn", { "reveal_target": "center" }],
       // also possible to spawn tasks by name:
       // "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
       // or by tag:
       // "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
+      "f5": "debugger::RerunLastSession"
+    }
+  },
+  {
+    "context": "Workspace && debugger_running",
+    "bindings": {
+      "f5": "zed::NoAction"
+    }
+  },
+  {
+    "context": "Workspace && debugger_stopped",
+    "bindings": {
+      "f5": "debugger::Continue"
     }
   },
   {

assets/keymaps/default-macos.json 🔗

@@ -4,8 +4,6 @@
     "use_key_equivalents": true,
     "bindings": {
       "f4": "debugger::Start",
-      "alt-f4": "debugger::RerunLastSession",
-      "f5": "debugger::Continue",
       "shift-f5": "debugger::Stop",
       "shift-cmd-f5": "debugger::Restart",
       "f6": "debugger::Pause",
@@ -635,7 +633,8 @@
       "cmd-k shift-right": "workspace::SwapPaneRight",
       "cmd-k shift-up": "workspace::SwapPaneUp",
       "cmd-k shift-down": "workspace::SwapPaneDown",
-      "cmd-shift-x": "zed::Extensions"
+      "cmd-shift-x": "zed::Extensions",
+      "f5": "debugger::RerunLastSession"
     }
   },
   {
@@ -652,6 +651,20 @@
       // "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }],
     }
   },
+  {
+    "context": "Workspace && debugger_running",
+    "use_key_equivalents": true,
+    "bindings": {
+      "f5": "zed::NoAction"
+    }
+  },
+  {
+    "context": "Workspace && debugger_stopped",
+    "use_key_equivalents": true,
+    "bindings": {
+      "f5": "debugger::Continue"
+    }
+  },
   // Bindings from Sublime Text
   {
     "context": "Editor",

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -3,9 +3,10 @@ use crate::session::DebugSession;
 use crate::session::running::RunningState;
 use crate::{
     ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
-    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
-    ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
-    ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
+    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
+    NewProcessMode, Pause, Restart, ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop,
+    ToggleIgnoreBreakpoints, ToggleSessionPicker, ToggleThreadPicker, persistence,
+    spawn_task_or_modal,
 };
 use anyhow::Result;
 use command_palette_hooks::CommandPaletteFilter;
@@ -334,10 +335,17 @@ impl DebugPanel {
         let Some(task_inventory) = task_store.read(cx).task_inventory() else {
             return;
         };
+        let workspace = self.workspace.clone();
         let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else {
+            window.defer(cx, move |window, cx| {
+                workspace
+                    .update(cx, |workspace, cx| {
+                        NewProcessModal::show(workspace, window, NewProcessMode::Launch, None, cx);
+                    })
+                    .ok();
+            });
             return;
         };
-        let workspace = self.workspace.clone();
 
         cx.spawn_in(window, async move |this, cx| {
             let task_contexts = workspace
@@ -1411,4 +1419,10 @@ impl workspace::DebuggerProvider for DebuggerProvider {
     fn debug_scenario_scheduled_last(&self, cx: &App) -> bool {
         self.0.read(cx).debug_scenario_scheduled_last
     }
+
+    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus> {
+        let session = self.0.read(cx).active_session()?;
+        let thread = session.read(cx).running_state().read(cx).thread_id()?;
+        session.read(cx).session(cx).read(cx).thread_state(thread)
+    }
 }

crates/project/src/debugger/session.rs 🔗

@@ -2194,4 +2194,8 @@ impl Session {
             self.shutdown(cx).detach();
         }
     }
+
+    pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
+        self.thread_states.thread_state(thread_id)
+    }
 }

crates/workspace/src/workspace.rs 🔗

@@ -68,7 +68,7 @@ pub use persistence::{
 use postage::stream::Stream;
 use project::{
     DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
-    debugger::breakpoint_store::BreakpointStoreEvent,
+    debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
 };
 use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
 use schemars::JsonSchema;
@@ -161,6 +161,8 @@ pub trait DebuggerProvider {
     fn task_scheduled(&self, cx: &mut App);
     fn debug_scenario_scheduled(&self, cx: &mut App);
     fn debug_scenario_scheduled_last(&self, cx: &App) -> bool;
+
+    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus>;
 }
 
 actions!(
@@ -202,6 +204,7 @@ actions!(
         Unfollow,
         Welcome,
         RestoreBanner,
+        ToggleExpandItem,
     ]
 );
 
@@ -5802,6 +5805,20 @@ impl Render for Workspace {
         let mut context = KeyContext::new_with_defaults();
         context.add("Workspace");
         context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
+        if let Some(status) = self
+            .debugger_provider
+            .as_ref()
+            .and_then(|provider| provider.active_thread_state(cx))
+        {
+            match status {
+                ThreadStatus::Running | ThreadStatus::Stepping => {
+                    context.add("debugger_running");
+                }
+                ThreadStatus::Stopped => context.add("debugger_stopped"),
+                ThreadStatus::Exited | ThreadStatus::Ended => {}
+            }
+        }
+
         let centered_layout = self.centered_layout
             && self.center.panes().len() == 1
             && self.active_item(cx).is_some();