feat(debugger): Show the value of the selected code while paused (#173)

Settings › Debugger › Data Views gains Show value tooltip on code selection
(on by default). While the debugger is paused, selecting an expression on one
line shows its value once the selection settles, with objects expandable like
the hover tooltip.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-29 09:12:40 +08:00
1 джерело 1c46a1cd04
коміт 7cfbf6c47b
8 files changed
+92 -3

No files matched your search

+21
Переглянути файл
@@ -1,4 +1,6 @@
import { describe, expect, it } from "vitest";
import { EditorSelection, EditorState } from "@codemirror/state";
import { selectionExpression } from "../editor/valueTooltip";
import { classifyValue, identifiersIn, inlineValue, sortVariables } from "../values";
import { inlineValuesFor } from "../editor/execution";
import { frameLabel, groupFrames, isLibraryFrame } from "../frames";
@@ -109,3 +111,22 @@ describe("sortVariables", () => {
expect(names(sortVariables(items))).toEqual(["[0]", "[1]", "[10]", "[2]"]);
});
});
describe("selectionExpression", () => {
const at = (doc: string, from: number, to: number) =>
selectionExpression(EditorState.create({ doc, selection: EditorSelection.single(from, to) }));
it("takes a one-line selection", () => {
expect(at("total += item.price", 9, 19)).toEqual({ from: 9, text: "item.price" });
});
it("ignores empty, blank, multi-line and multiple selections", () => {
expect(at("a + b", 2, 2)).toBeNull();
expect(at("a b", 1, 4)).toBeNull();
expect(at("a\nb", 0, 3)).toBeNull();
const multi = EditorState.create({
doc: "a + b",
selection: EditorSelection.create([EditorSelection.range(0, 1), EditorSelection.range(4, 5)]),
extensions: EditorState.allowMultipleSelections.of(true),
});
expect(selectionExpression(multi)).toBeNull();
});
});
+59 -2
Переглянути файл
@@ -3,9 +3,10 @@
* while suspended, hovering an expression (or the selection under the mouse)
* evaluates it in the selected frame.
*/
import type { Extension } from "@codemirror/state";
import type { EditorState, Extension } from "@codemirror/state";
import type { DebugSessionId, Variable } from "@/bindings";
import { hoverTooltip, type EditorView } from "@codemirror/view";
import { hoverTooltip, ViewPlugin, type EditorView, type ViewUpdate } from "@codemirror/view";
import { showHint } from "@/editor/lsp/hint";
import { workflowSettings } from "@/platform/workflowSettings";
import { navigationModifierHeld } from "@/platform/navigationModifier";
import { activeSession, evaluate } from "../state";
@@ -109,3 +110,59 @@ export function valueTooltip(): Extension {
{ hoverTime: BASE_HOVER },
);
}
const SELECTION_DELAY = 400;
const MAX_SELECTION = 200;
/** The selected text when it can be evaluated: one non-blank line, not too long. */
export function selectionExpression(state: EditorState): { from: number; text: string } | null {
const sel = state.selection;
if (sel.ranges.length !== 1 || sel.main.empty) return null;
const text = state.sliceDoc(sel.main.from, sel.main.to).trim();
if (!text || text.length > MAX_SELECTION || text.includes("\n")) return null;
return { from: sel.main.from, text };
}
/**
* Settings › Debugger › Data Views › "Show value tooltip on code selection": while
* suspended, selecting an expression shows its value once the selection settles.
*/
export function selectionValueTooltip(): Extension {
return ViewPlugin.fromClass(
class {
private timer: ReturnType<typeof setTimeout> | undefined;
private seq = 0;
constructor(readonly view: EditorView) {}
update(u: ViewUpdate) {
if (!u.selectionSet) return;
clearTimeout(this.timer);
this.seq++;
if (!u.transactions.some((tr) => tr.isUserEvent("select"))) return;
const seq = this.seq;
this.timer = setTimeout(() => void this.show(seq), SELECTION_DELAY);
}
private async show(seq: number) {
if (!workflowSettings().debugger.valueTooltipOnSelection) return;
const session = activeSession();
if (!session || session.status !== "paused") return;
const expr = selectionExpression(this.view.state);
if (!expr) return;
let value;
try {
value = await evaluate(expr.text, "hover", session);
} catch {
return;
}
// The selection moved on while evaluating.
if (seq !== this.seq) return;
showHint(this.view, () => valueHint(expr.text, value, session.id), { pos: expr.from, timeout: 60_000 });
}
destroy() {
clearTimeout(this.timer);
}
},
);
}
+2 -1
Переглянути файл
@@ -6,7 +6,7 @@
import { lazyComponent } from "@/ui";
import { onIdeEvent } from "@/platform/ipc";
import { useProject } from "@/platform/project";
import { valueTooltip } from "./editor/valueTooltip";
import { selectionValueTooltip, valueTooltip } from "./editor/valueTooltip";
import { registerEditorContribution } from "@/editor/api";
import {
registerMenuGroup,
@@ -65,6 +65,7 @@ export function activate(): () => void {
executionPoint(ctx),
runToCursorInlay(ctx),
valueTooltip(),
selectionValueTooltip(),
smartStepTargets(ctx),
],
}),