From 004792192aea540a7eae6c79a2d11aea4c1b7339 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:13:53 +0800 Subject: [PATCH] feat(debugger): Sort values alphabetically in the Variables view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/ide-core/src/workflow_settings.rs | 3 +++ src/bindings.ts | 2 ++ src/features/debugger/__tests__/values.test.ts | 18 +++++++++++++++++- src/features/debugger/ui/VariablesView.tsx | 6 ++++-- src/features/debugger/values.ts | 11 +++++++++++ src/features/settings/pages/execution.tsx | 1 + src/platform/locales/text/zh.json | 1 + src/platform/workflowSettings.ts | 3 +++ 8 files changed, 42 insertions(+), 3 deletions(-) diff --git a/crates/ide-core/src/workflow_settings.rs b/crates/ide-core/src/workflow_settings.rs index 59a6d7e3..56b6a80a 100644 --- a/crates/ide-core/src/workflow_settings.rs +++ b/crates/ide-core/src/workflow_settings.rs @@ -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, diff --git a/src/bindings.ts b/src/bindings.ts index e1a8691c..e9701963 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -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`). */ diff --git a/src/features/debugger/__tests__/values.test.ts b/src/features/debugger/__tests__/values.test.ts index a4880cc8..a22584c5 100644 --- a/src/features/debugger/__tests__/values.test.ts +++ b/src/features/debugger/__tests__/values.test.ts @@ -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]"]); + }); +}); diff --git a/src/features/debugger/ui/VariablesView.tsx b/src/features/debugger/ui/VariablesView.tsx index 48da7f4b..b073a2b4 100644 --- a/src/features/debugger/ui/VariablesView.tsx +++ b/src/features/debugger/ui/VariablesView.tsx @@ -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(); list = vars.map((v) => { const n = (seen.get(v.name) ?? 0) + 1; diff --git a/src/features/debugger/values.ts b/src/features/debugger/values.ts index ad27e800..86d02c8f 100644 --- a/src/features/debugger/values.ts +++ b/src/features/debugger/values.ts @@ -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(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" })); +} diff --git a/src/features/settings/pages/execution.tsx b/src/features/settings/pages/execution.tsx index c8efadbb..fdf638a2 100644 --- a/src/features/settings/pages/execution.tsx +++ b/src/features/settings/pages/execution.tsx @@ -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", diff --git a/src/platform/locales/text/zh.json b/src/platform/locales/text/zh.json index f7455c86..b606c975 100644 --- a/src/platform/locales/text/zh.json +++ b/src/platform/locales/text/zh.json @@ -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": "标签页放不下时", diff --git a/src/platform/workflowSettings.ts b/src/platform/workflowSettings.ts index b68e6e99..83fe6ee0 100644 --- a/src/platform/workflowSettings.ts +++ b/src/platform/workflowSettings.ts @@ -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),