дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
Цей коміт міститься в:
5 files changed
+119
-3
No files matched your search
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Variable } from "@/platform/ipc";
|
||||
import { valueText } from "../valueText";
|
||||
|
||||
const v = (name: string, value: string, variablesReference = 0): Variable =>
|
||||
({ name, value, variablesReference, typeName: null, evaluateName: null }) as Variable;
|
||||
|
||||
const fetcher = (tree: Record<number, Variable[]>) => async (ref: number) => tree[ref] ?? [];
|
||||
|
||||
describe("valueText", () => {
|
||||
it("returns a scalar as is", async () => {
|
||||
expect(await valueText({ value: "42", reference: 0 }, fetcher({}))).toBe("42");
|
||||
});
|
||||
|
||||
it("copies a PHP array with its nested elements", async () => {
|
||||
const tree = {
|
||||
1: [v("name", '"Ann"'), v("tags", "array(2)", 2)],
|
||||
2: [v("0", '"a"'), v("1", '"b"')],
|
||||
};
|
||||
expect(await valueText({ value: "array(2)", reference: 1 }, fetcher(tree))).toBe(
|
||||
["array(2)", ' name: "Ann"', " tags: array(2)", ' 0: "a"', ' 1: "b"'].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("stops at a cycle", async () => {
|
||||
const tree = { 1: [v("self", "Node", 1)] };
|
||||
expect(await valueText({ value: "Node", reference: 1 }, fetcher(tree))).toBe("Node\n self: Node");
|
||||
});
|
||||
|
||||
it("truncates past the entry limit", async () => {
|
||||
const tree = { 1: [v("0", "a"), v("1", "b"), v("2", "c")] };
|
||||
const text = await valueText({ value: "arr", reference: 1 }, fetcher(tree), { depth: 8, entries: 2 });
|
||||
expect(text).toBe("arr\n 0: a\n 1: b\n …");
|
||||
});
|
||||
|
||||
it("truncates past the depth limit", async () => {
|
||||
const tree = { 1: [v("a", "x", 2)], 2: [v("b", "y", 3)], 3: [v("c", "z")] };
|
||||
const text = await valueText({ value: "r", reference: 1 }, fetcher(tree), { depth: 2, entries: 100 });
|
||||
expect(text).toBe("r\n a: x\n b: y\n …");
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import { methodAt } from "../breakpoints/symbols";
|
||||
import { activeSession, addWatch, removeWatch, revealFrame, sessionSupports, setVariableValue } from "../state";
|
||||
import { blurDebugView, focusDebugView } from "./focusedView";
|
||||
import { classifyValue, oneLine, sortVariables } from "../values";
|
||||
import { valueText } from "../valueText";
|
||||
import { workflowSettings } from "@/platform/workflowSettings";
|
||||
import styles from "./VariablesView.module.css";
|
||||
|
||||
@@ -270,6 +271,25 @@ export function VariablesView({
|
||||
const canWatch = (n: VarNode) =>
|
||||
session != null && n.kind === "var" && n.container > 0 && sessionSupports("dataBreakpoints");
|
||||
|
||||
const copyValue = async (node: VarNode) => {
|
||||
let text = node.value;
|
||||
if (session != null && node.reference > 0) {
|
||||
try {
|
||||
text = await valueText(node, (ref) => call(commands.debugVariables(session, ref)));
|
||||
} catch (e) {
|
||||
notifications().notify({
|
||||
group: "run",
|
||||
level: "error",
|
||||
key: "debug.copyValue",
|
||||
title: "Cannot copy the value",
|
||||
body: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
await navigator.clipboard?.writeText(text);
|
||||
};
|
||||
|
||||
const onContextMenu = (node: VarNode | null, _sel: Set<string>, e: React.MouseEvent) => {
|
||||
if (!node || node.kind === "message") return;
|
||||
e.preventDefault();
|
||||
@@ -281,7 +301,7 @@ export function VariablesView({
|
||||
);
|
||||
if (node.kind === "watch") items.push({ label: "Remove Watch", icon: "minus", run: () => removeWatch(node.name) });
|
||||
if (node.kind !== "scope") {
|
||||
items.push({ label: "Copy Value", icon: "copy", run: () => void navigator.clipboard?.writeText(node.value) });
|
||||
items.push({ label: "Copy Value", icon: "copy", run: () => void copyValue(node) });
|
||||
items.push({ label: "Copy Name", run: () => void navigator.clipboard?.writeText(node.name) });
|
||||
if (node.kind === "var" && node.evaluateName)
|
||||
items.push({ label: "Add to Watches", icon: "eye", run: () => addWatch(node.evaluateName!) });
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/** Text for "Copy Value": a structured value (array, object, map) is copied with its contents, not its summary. */
|
||||
import type { Variable } from "@/platform/ipc";
|
||||
|
||||
export interface ValueTextRoot {
|
||||
value: string;
|
||||
reference: number;
|
||||
}
|
||||
|
||||
export interface ValueTextLimits {
|
||||
depth: number;
|
||||
entries: number;
|
||||
}
|
||||
|
||||
export const VALUE_TEXT_LIMITS: ValueTextLimits = { depth: 8, entries: 2000 };
|
||||
|
||||
/**
|
||||
* `value` alone for a scalar; for an expandable value the summary line followed by its children,
|
||||
* one per line and indented by depth. Cycles and anything past the limits end in an ellipsis line.
|
||||
*/
|
||||
export async function valueText(
|
||||
root: ValueTextRoot,
|
||||
fetchChildren: (reference: number) => Promise<Variable[]>,
|
||||
limits: ValueTextLimits = VALUE_TEXT_LIMITS,
|
||||
): Promise<string> {
|
||||
if (root.reference <= 0) return root.value;
|
||||
const lines = [root.value];
|
||||
let budget = limits.entries;
|
||||
const path = new Set<number>([root.reference]);
|
||||
|
||||
const walk = async (reference: number, depth: number): Promise<void> => {
|
||||
const pad = " ".repeat(depth);
|
||||
if (depth > limits.depth) {
|
||||
lines.push(`${pad}…`);
|
||||
return;
|
||||
}
|
||||
for (const child of await fetchChildren(reference)) {
|
||||
if (budget-- <= 0) {
|
||||
lines.push(`${pad}…`);
|
||||
return;
|
||||
}
|
||||
lines.push(`${pad}${child.name}: ${child.value}`);
|
||||
const ref = child.variablesReference;
|
||||
if (ref > 0 && !path.has(ref)) {
|
||||
path.add(ref);
|
||||
await walk(ref, depth + 1);
|
||||
path.delete(ref);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walk(root.reference, 1);
|
||||
return lines.join("\n");
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача