Skip to content

debugger: Add keyboard shortcuts for pause/resume, stepping, and frame navigation (#3867)#9835

Open
NadeemIqbal wants to merge 2 commits into
flutter:masterfrom
NadeemIqbal:nadeem/3867-debugger-keyboard-shortcuts
Open

debugger: Add keyboard shortcuts for pause/resume, stepping, and frame navigation (#3867)#9835
NadeemIqbal wants to merge 2 commits into
flutter:masterfrom
NadeemIqbal:nadeem/3867-debugger-keyboard-shortcuts

Conversation

@NadeemIqbal
Copy link
Copy Markdown

Fixes #3867.

Description

Wires up the keyboard shortcuts requested in #3867 for the debugger panel, using the bindings Elliette suggested on the issue (which match Chrome DevTools and the stepping triplet used by VS Code):

Shortcut Action
F8 Pause when running, Resume when paused
F9 Cycle the selected frame in the Call Stack (wraps to top)
F10 Step Over
F11 Step In
Shift + F11 Step Out

The new shortcuts plug into the existing ShortcutsConfiguration returned by DebuggerScreen.buildKeyboardShortcuts, alongside the existing Go-To-Line / Search-In-File / Open-File / Escape shortcuts. Each action delegates to the same DebuggerController methods used by the on-screen buttons (pause(), resume(), stepOver(), stepIn(), stepOut(), selectStackFrame()).

Each action's isEnabled mirrors the gating already used by the buttons in controls.dart:

  • Pause/Resume is disabled on a system isolate, and disabled while a resume is already in flight when the isolate is paused.
  • Step Over / In / Out require a paused, non-system isolate with at least one stack frame and no in-flight resume.
  • Next frame requires more than one stack frame to be available.

When a shortcut's isEnabled returns false it falls through to the framework's default key handling, so the shortcut does not steal events when the debugger is not in a stepable state.

Note on previous-frame navigation

The issue lists "Call stack navigation (next / previous frame)" but the comment specifies only F9. This PR implements next-frame on F9 (cycling through the call stack, which gives previous-frame behaviour after a wrap). Happy to add an explicit Shift+F9 for previous-frame in a follow-up if that's preferred.

Tests

Added packages/devtools_app/test/screens/debugger/debugger_keyboard_shortcuts_test.dart, which exercises each Action's isEnabled and invoke directly against a mocked DebuggerController. This covers:

  • Pause vs. Resume routing based on isMainIsolatePaused.
  • System-isolate and in-flight-resume disable cases.
  • Step actions firing on the paused happy path and being disabled in each gating case.
  • Frame cycling: next, wrap-around, and selecting the first frame from an unselected starting state.

Pre-launch Checklist

…e navigation

Wire up the F8 / F9 / F10 / F11 / Shift+F11 shortcuts requested in flutter#3867 to
the existing DebuggerController actions, mirroring the bindings used by
Chrome DevTools and VS Code:

- F8         — Pause when running, Resume when paused.
- F9         — Cycle the selected frame in the Call Stack (wraps to top).
- F10        — Step Over.
- F11        — Step In.
- Shift+F11  — Step Out.

Each shortcut is gated by the same conditions that disable the corresponding
debugger control buttons in `controls.dart`:

- Pause/Resume is disabled on a system isolate, and disabled while a resume
  is already in flight when the isolate is paused.
- Step Over/In/Out require a paused, non-system isolate with at least one
  stack frame and no in-flight resume.
- Next frame requires more than one frame to be available.

Adds a unit test that exercises each Action's `isEnabled` and `invoke`
directly so the gating logic is covered without depending on a pumped
widget tree.

Fixes flutter#3867.
@NadeemIqbal NadeemIqbal requested a review from srawlins as a code owner May 17, 2026 18:26
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements keyboard shortcuts for debugger stepping and pause/resume operations (F8 through F11), matching the behavior of Chrome DevTools and VS Code. The changes include new intents and actions in debugger_screen.dart, key set definitions in key_sets.dart, and comprehensive regression tests. Feedback identifies an opportunity to refactor duplicated stepping logic into DebuggerController and suggests removing a redundant frame count check in NextStackFrameAction.invoke.

Comment on lines +435 to +440
bool _canStep(DebuggerController controller) {
return serviceConnection.serviceManager.isMainIsolatePaused &&
!controller.resuming.value &&
controller.stackFramesWithLocation.value.isNotEmpty &&
!controller.isSystemIsolate;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[CONCERN] The logic for determining if stepping is allowed is duplicated here and in controls.dart. To improve maintainability and ensure consistent behavior between buttons and shortcuts, this logic should be moved to a shared property or method in DebuggerController.

References
  1. Code Reuse: Use shared primitives and components rather than recreating them from scratch. (link)

Comment on lines +489 to +492
if (frames.length < 2) {
return;
}
final currentlySelected = controller.selectedStackFrame.value;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[NIT] This check is redundant as isEnabled already ensures that frames.length > 1. Removing it simplifies the method.

    final currentlySelected = controller.selectedStackFrame.value;
References
  1. Adhere to DRY (Don't Repeat Yourself) principles by removing redundant logic checks. (link)

… guard

Address review feedback on flutter#9835:

- The condition for "step operations are allowed" was duplicated between
  the debugger control buttons in `controls.dart` and the keyboard-shortcut
  Actions in `debugger_screen.dart`. Expose it once as
  `DebuggerController.canStep` and have both surfaces read it, so they
  cannot drift out of sync.
- `PauseResumeAction.isEnabled` now reads `intent._controller.isSystemIsolate`
  directly; the `_isSystemIsolate` helper was only there to mirror the
  duplicated style and is no longer needed.
- `NextStackFrameAction.invoke` had a `frames.length < 2` early return that
  was redundant with the matching guard in `isEnabled`; drop it.

The Step Action tests now stub `canStep` directly instead of driving it
through its inputs — that is what the Actions actually read, and the
component conditions are tested where they belong (on `DebuggerController`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DevTools debugger should support keyboard shortcuts for stepping

1 participant