feat(debugger): Sort values alphabetically in the Variables view

Settings › Debugger › Data Views gains Sort values alphabetically (off by
default). When on, variables and their children are listed by name, comparing
numbers numerically; array and list elements keep their index order.
This commit is contained in:
l0ng-ai
2026-10-29 04:13:53 +08:00
parent 4dcf0acc07
commit 004792192a
8 changed files with 42 additions and 3 deletions
+3
View File
@@ -218,6 +218,8 @@ pub struct DebuggerSettings {
/// While suspended, hovering the start of a line shows a Run to Cursor button.
pub run_to_cursor_inlay: bool,
pub inline_values: bool,
/// Data Views: list variables by name instead of the adapter's order.
pub sort_values: bool,
pub value_tooltip: bool,
pub value_tooltip_delay: u32,
/// Stepping: do not step into library code (debugpy `justMyCode`, js-debug `skipFiles`).
@@ -234,6 +236,7 @@ impl Default for DebuggerSettings {
breakpoints_any_file: false,
run_to_cursor_inlay: true,
inline_values: true,
sort_values: false,
value_tooltip: true,
value_tooltip_delay: 700,
skip_library_code: true,
+2
View File
@@ -2153,6 +2153,8 @@ export type DebuggerSettings = {
/** While suspended, hovering the start of a line shows a Run to Cursor button. */
runToCursorInlay?: boolean,
inlineValues?: boolean,
/** Data Views: list variables by name instead of the adapter's order. */
sortValues?: boolean,
valueTooltip?: boolean,
valueTooltipDelay?: number,
/** Stepping: do not step into library code (debugpy `justMyCode`, js-debug `skipFiles`). */
+17 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { classifyValue, identifiersIn, inlineValue } from "../values";
import { classifyValue, identifiersIn, inlineValue, sortVariables } from "../values";
import { inlineValuesFor } from "../editor/execution";
import { frameLabel, groupFrames, isLibraryFrame } from "../frames";
import type { StackFrame } from "@/platform/ipc";
@@ -93,3 +93,19 @@ describe("frames", () => {
expect(frameLabel(frame(1, null))).toBe("f1");
});
});
describe("sortVariables", () => {
const names = (vs: { name: string }[]) => vs.map((v) => v.name);
it("sorts by name, case-insensitively and with numbers compared numerically", () => {
expect(names(sortVariables([{ name: "b" }, { name: "item10" }, { name: "A" }, { name: "item2" }]))).toEqual([
"A",
"b",
"item2",
"item10",
]);
});
it("keeps array elements in index order", () => {
const items = [{ name: "[0]" }, { name: "[1]" }, { name: "[10]" }, { name: "[2]" }];
expect(names(sortVariables(items))).toEqual(["[0]", "[1]", "[10]", "[2]"]);
});
});
+4 -2
View File
@@ -9,7 +9,8 @@ import { addFieldWatchpoint } from "../breakpoints/special";
import { methodAt } from "../breakpoints/symbols";
import { activeSession, addWatch, removeWatch, revealFrame, sessionSupports, setVariableValue } from "../state";
import { blurDebugView, focusDebugView } from "./focusedView";
import { classifyValue } from "../values";
import { classifyValue, sortVariables } from "../values";
import { workflowSettings } from "@/platform/workflowSettings";
import styles from "./VariablesView.module.css";
export type VarRoot =
@@ -196,7 +197,8 @@ export function VariablesView({
loading.current.add(tag);
let list: VarNode[];
try {
const vars = await call(commands.debugVariables(session, node.reference));
const raw = await call(commands.debugVariables(session, node.reference));
const vars = workflowSettings().debugger.sortValues ? sortVariables(raw) : raw;
const seen = new Map<string, number>();
list = vars.map((v) => {
const n = (seen.get(v.name) ?? 0) + 1;
+11
View File
@@ -53,3 +53,14 @@ export function identifiersIn(text: string): string[] {
}
return out;
}
const INDEX = /^\[?\d+\]?$/;
/**
* Data Views › "Sort values alphabetically": children by name (numbers in
* names compared numerically). Array / list elements keep their index order.
*/
export function sortVariables<T extends { name: string }>(vars: readonly T[]): T[] {
if (vars.every((v) => INDEX.test(v.name))) return [...vars];
return [...vars].sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: "base" }));
}
@@ -173,6 +173,7 @@ export const EXECUTION_PAGES: PageSpec[] = [
label: "Show values inline",
hint: "Values of local variables are shown after the lines that use them while suspended.",
},
{ kind: "toggle", key: "debugger.sortValues", label: "Sort values alphabetically" },
{
kind: "toggle",
key: "debugger.valueTooltip",
+1
View File
@@ -2946,6 +2946,7 @@
"Close button position:": "关闭按钮位置:",
"Close button position": "关闭按钮位置",
"Tab Order": "标签页顺序",
"Sort values alphabetically": "按字母顺序对值排序",
"Sort tabs alphabetically": "按字母顺序对标签页排序",
"One row, and if tabs don't fit:": "单行,标签页放不下时:",
"If tabs don't fit": "标签页放不下时",
+3
View File
@@ -83,6 +83,7 @@ export interface DebuggerSettingsValid {
breakpointsAnyFile: boolean;
runToCursorInlay: boolean;
inlineValues: boolean;
sortValues: boolean;
valueTooltip: boolean;
valueTooltipDelay: number;
skipLibraryCode: boolean;
@@ -242,6 +243,7 @@ export const DEFAULT_WORKFLOW: WorkflowSettings = {
breakpointsAnyFile: false,
runToCursorInlay: true,
inlineValues: true,
sortValues: false,
valueTooltip: true,
valueTooltipDelay: 700,
skipLibraryCode: true,
@@ -376,6 +378,7 @@ export function validateDebugger(raw: unknown): DebuggerSettingsValid {
breakpointsAnyFile: bool(v.breakpointsAnyFile, d.breakpointsAnyFile),
runToCursorInlay: bool(v.runToCursorInlay, d.runToCursorInlay),
inlineValues: bool(v.inlineValues, d.inlineValues),
sortValues: bool(v.sortValues, d.sortValues),
valueTooltip: bool(v.valueTooltip, d.valueTooltip),
valueTooltipDelay: num(v.valueTooltipDelay, d.valueTooltipDelay, WORKFLOW_LIMITS.tooltipDelay, true),
skipLibraryCode: bool(v.skipLibraryCode, d.skipLibraryCode),