Repository navigation
Make the Debug Console and Watch work at a pause, and on a saved stack - #128
Merged
Merged
Conversation
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>
jgfoster
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
print()shows in the console.breakpoint()and red dots.%commands.%persist p(or%persist expr as key),%commit,%namesand%help, with autocomplete. The first pause in a window says once that they exist.gemdbis available without an import.gemdb.rootentry 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.oalways reaches the entry, and autocomplete offers it.%commitor ✓.Account.open), and%nameshides Grail's internal___…___names.Under the hood:
stack.README and CHANGELOG are updated.
Testing
Local runs against GemStone 4.0.0.a4, on Linux:
npm test: 875 passed.debugConsole,breakpointandredDots, 54 passed, 1 expected fail (clsin a classmethod's frame, not supported yet) and 1 todo.%commands and persisted names at a live pause;%commit, the close prompt;🤖 Generated with Claude Code