Skip to content

Make the Debug Console and Watch work at a pause, and on a saved stack - #128

Merged
ericwinger merged 16 commits into
mainfrom
eric/debug-console
Oct 14, 2026
Merged

ericwinger merged 16 commits into
mainfrom
eric/debug-console

Conversation

@ericwinger

Copy link
Copy Markdown
Member

What

The Debug Console and Watch now work at a GemDB pause. Python typed there runs in the frame selected in Call Stack, and a saved stack opened again gets a working Debug Console too.

Why

Until now a GemDB pause showed Variables but answered nothing typed in the Debug Console or Watch, which is the first place a Python developer goes at a breakpoint. GemDB's objects live in the database, so the console is also where persistence can be put in reach: saving an object, committing, and working on yesterday's saved stack.

Changes

  1. Debug Console and Watch at a pause.
    • A line runs in the selected frame, with that frame's locals, closed-over names, module globals and the builtins.
    • A result opens like a Variables row, and print() shows in the console.
    • Names you bind last until the run resumes.
    • A console error reads as a Jupyter-style traceback. A failed watch shows its one error line.
    • A console line has no time limit and can be stopped from a notification. A watch keeps the 2-second limit.
    • Code the console calls runs past breakpoint() and red dots.
  2. From Variables. Right-click a row for Show in Debug Console (the object, with its attributes listed, and its name left at the prompt) or Evaluate with self….
  3. % commands. %persist p (or %persist expr as key), %commit, %names and %help, with autocomplete. The first pause in a window says once that they exist.
  4. Persisted Objects in the console.
    • gemdb is available without an import.
    • Each gemdb.root entry is there by its key, unless the paused code has a name of its own spelled the same, and it is labelled when typed alone.
    • persisted.o always reaches the entry, and autocomplete offers it.
  5. A working Debug Console on a reopened saved stack.
    • The stack opens in a database session of its own, so a change waits uncommitted as a notebook's does, until %commit or ✓.
    • A yellow SAVED STACK badge in the console says that a commit overwrites what was saved.
    • Closing the stack with changes pending asks whether to commit them.
  6. Smaller fixes. A classmethod's frame is named by its class (Account.open), and %names hides Grail's internal ___…___ names.

Under the hood:

  • A live pause and a saved stack share one console implementation.
  • Paused queries take options: no time budget, a cancel signal, and running past halts.
  • Sessions have a new kind, stack.

README and CHANGELOG are updated.

Testing

Local runs against GemStone 4.0.0.a4, on Linux:

  • npm test: 875 passed.
  • Integration: debugConsole, breakpoint and redDots, 54 passed, 1 expected fail (cls in a classmethod's frame, not supported yet) and 1 todo.
  • Tested by hand in an Extension Development Host:
    • the console, % commands and persisted names at a live pause;
    • a saved stack reopened after a window reload: evaluate, change, %commit, the close prompt;
    • Watch, across frames and with a failing expression.

🤖 Generated with Claude Code

ericwinger and others added 16 commits October 12, 2026 15:58
Type Python into the Debug Console at a breakpoint() or red dot and it
runs in the frame selected in Call Stack, with that frame's locals over
its own module's globals and the builtins. Names bound there last until
the run resumes. A result opens like a Variables row, print() goes to
the console, and an error reads as a Jupyter traceback, built from
Grail's pythonTracebackFrames. A console line has no time limit and can
be stopped through DAP cancel or a notification; a watch keeps the
two-second budget. Code the console calls runs past breakpoint() and
red dots.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two items on a Variables row's menu. Show in Debug Console opens the
console with the row's object in it, expandable like the row. Evaluate
with self... asks for an expression and shows what it gives with self
bound to the row's object, in the frame selected in Call Stack. Both
find the object by its registry handle, as Add to Persisted Objects
does, so a row deep in the tree needs no expression to name it, and
self is bound in a copy of the frame's console scope, leaving the
frame's own self alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VS Code reads an output event's variablesReference as a list of values
and shows each child with its name hidden, so Show in Debug Console on
a dict printed its values unlabelled and no dict. Hand the console a
one-row container instead, answered by the adapter: the row, labelled
"entry: {...}", opening onto the object's own children.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Show in Debug Console, and Evaluate with self... on a result with
children, now write a line naming the object and then its attributes
beneath, each by name, so the one thing looked for needs no click: all
of them for an object or dict with up to 20, else the first five, and a
list, tuple or set's first five, then a "... N more" row that opens
onto the whole object.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A list, set or dict shown in the console printed its first five items
above a "... N more" row, which opened onto VS Code's own [0..99]
pages: one list read three ways, in four colours. Show a container, or
an object with more than 20 attributes, as one closed row that opens
like the Variables row, and keep listing a smaller object's attributes.
Everything shown now goes out through the adapter's own container, so a
plain value, a name line and an attribute all read alike. Each row now
also says how its children are read, so a dict is told from an object.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After Show in Debug Console, the console's input holds what reaches the
object: the row's own name when it already means that object in the
frame (a Locals or Globals row), else _, which the object is bound to
(a list item, a dict entry). VS Code has no API to set the console's
input, so the name is typed into it once focusRepl has focused it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A @classmethod or @staticmethod is compiled on the class's metaclass, so
the Call Stack read "Account class.open". Name the frame by the class
itself, "Account.open", as CPython does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
#	README.md
#	src/debugger.ts
#	src/pauseVariables.ts
A Debug Console line that starts with % is a GemDB command rather than
Python. %persist p adds p to Persisted Objects (or %persist expr as
key), shown there as added until the notebook commits; %commit commits
the notebook as the check mark in Persisted Objects does; %names lists
the names bound in the console at this pause; %help lists the commands.

To make them findable without a hover that flashes while typing, the
adapter answers completions for % lines only: typing % suggests the
commands with a line each on what they do, and %persist suggests the
console's names. The first pause in a window also says, once, that the
commands exist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A line copied from Persisted Objects reads gemdb.root["key"], and in a
program that never imported gemdb it failed with NameError. Each frame's
console scope now starts with gemdb bound, unless the frame has a name
of that spelling of its own, which wins. It is not listed by %names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At a pause, each gemdb.root entry whose key is a Python name is there
under that name: o for gemdb.root["o"]. The frame's own names win, a
builtin (list, print) is never hidden, and %names leaves them out, since
the user did not bind them at this pause.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a console line is just a name and its value is the Persisted
Objects entry of that key, the console says so above the value:
"o is gemdb.root[\"o\"], from Persisted Objects (also persisted.o)". A name
of the paused code's own, or one bound in the console, gets no label.

persisted is always in the console, below the frame's own names:
persisted.o is gemdb.root["o"] whatever o means at the pause, and
persisted alone lists what Persisted Objects holds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While a name is typed, autocomplete offers persisted.key for each
Persisted Objects entry the name starts, described as the gemdb.root
entry it is, so a persisted object can be found without knowing that
persisted exists; after persisted. it offers the keys. The keys are
read once a pause and again only after a console line, so a keystroke
costs the database nothing, and no user code runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each function this branch adds now has a test of its own, beside the
real-database suite that runs them end to end:

- the Jupyter-style traceback: its header, the lines around the one
  that raised, the gutter at two-digit lines, the fallback to Grail's
  recorded text, a SyntaxError's caret, a file's label and an error
  with no message;
- the readers of the console queries' answers, for every shape: a
  value with and without a persisted name's origin, None, interrupted,
  an error with its frames, a SyntaxError's position, a stack overflow,
  a failed query, the console's names and Persisted Objects' keys;
- the label for a persisted name; the Variables menu commands'
  registration against package.json; %persist answering even when
  Persisted Objects cannot be told; and the view showing a %persist as
  waiting under its notebook and revealing it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A stack opened with Restore a Saved Stack now has a Debug Console in each
frame: evaluate, call methods on the saved objects, bind names, change
objects, %names, %persist, %commit, Show in Debug Console and Evaluate
with self.

The stack opens in a database session of its own, so a change waits
uncommitted as a notebook's does. When it opens, the console shows a
SAVED STACK badge saying that %commit overwrites what was saved; closing
the stack with changes pending asks whether to commit them.

The console code now runs through a ConsoleHost, so a live pause and a
saved stack share it. Each frame's console is made and undone when the
stack opens, since making one writes, so the close prompt asks only about
the user's own changes. %names hides Grail's ___…___ names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Watch row shows only the first line of an error, and a Jupyter
traceback's first line is a rule of dashes. A watch now answers with
the line that says what went wrong (NameError: name 'x' is not
defined); the Debug Console keeps the whole traceback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ericwinger
ericwinger requested a review from jgfoster October 14, 2026 20:49
@ericwinger
ericwinger added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 778919c Oct 14, 2026
5 checks passed
@ericwinger
ericwinger deleted the eric/debug-console branch October 14, 2026 21:39
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.

2 participants