fix(debugger): keep watches per project (#3293)

Watches were one list shared by every project, so a watch added while debugging one project showed up, and usually failed to evaluate, in all the others. Each project now keeps its own list; a project without one starts from the old shared list once, since that list doesn't record which project each watch came from.

Co-authored-by: ntbang0901 <121003585+ntbang0901@users.noreply.github.com>
This commit is contained in:
ntbang0901
2026-11-14 13:40:10 +08:00
committed by GitHub
parent 632e564d58
commit 49bb6b2d76
3 changed files with 72 additions and 7 deletions
@@ -0,0 +1,49 @@
import { beforeEach, describe, expect, it } from "vitest";
import { addWatch, loadWatches, useDebug } from "../state";
const SHARED = "openidea.debugger.watches";
const of = (root: string) => `${SHARED}:${root}`;
const watches = () => useDebug.getState().watches;
beforeEach(() => {
localStorage.clear();
loadWatches(null);
});
describe("watch storage", () => {
it("loads a project's own watches", () => {
localStorage.setItem(of("/a"), '["x"]');
loadWatches("/a");
expect(watches()).toEqual(["x"]);
});
it("starts a project from the shared list once", () => {
localStorage.setItem(SHARED, '["legacy"]');
loadWatches("/b");
expect(watches()).toEqual(["legacy"]);
expect(localStorage.getItem(of("/b"))).toBe('["legacy"]');
expect(localStorage.getItem(SHARED)).toBe('["legacy"]');
});
it("keeps projects apart", () => {
loadWatches("/a");
addWatch("y");
loadWatches("/b");
expect(watches()).not.toContain("y");
loadWatches("/a");
expect(watches()).toContain("y");
});
it("an emptied list stays empty", () => {
localStorage.setItem(SHARED, '["legacy"]');
localStorage.setItem(of("/a"), "[]");
loadWatches("/a");
expect(watches()).toEqual([]);
});
it("without a project nothing is stored", () => {
loadWatches(null);
addWatch("z");
expect(localStorage.length).toBe(0);
});
});
+2 -1
View File
@@ -57,7 +57,7 @@ export function activate(): () => void {
useDebugRunSessions.setState({
Console: lazyComponent(() => import("./ui/DebugConsole").then((m) => m.DebugServiceConsole)),
});
loadWatches();
loadWatches(useProject.getState().project?.root ?? null);
publishContext();
loadBreakpoints(useProject.getState().project?.root ?? null);
loadExceptionBreakpoints(useProject.getState().project?.root ?? null);
@@ -136,6 +136,7 @@ export function activate(): () => void {
const root = s.project?.root ?? null;
if (root !== (prev.project?.root ?? null) || s.project?.trusted !== prev.project?.trusted) {
resetSessions();
loadWatches(root);
loadBreakpoints(root);
loadExceptionBreakpoints(root);
loadSpecialBreakpoints(root);
+21 -6
View File
@@ -1025,25 +1025,40 @@ export function removeWatch(expression: string) {
persistWatches();
}
const WATCH_KEY = "openidea.debugger.watches";
/** Watches used to be one list for every project; it is only read now, as a project's first list. */
const SHARED_WATCH_KEY = "openidea.debugger.watches";
const watchKey = (root: string) => `${SHARED_WATCH_KEY}:${root}`;
/** The project whose watches are loaded; without one they aren't stored. */
let watchRoot: string | null = null;
function persistWatches() {
if (watchRoot == null) return;
try {
localStorage.setItem(WATCH_KEY, JSON.stringify(useDebug.getState().watches));
localStorage.setItem(watchKey(watchRoot), JSON.stringify(useDebug.getState().watches));
} catch {
// ignore
}
}
export function loadWatches() {
function storedWatches(key: string): string[] | null {
try {
const raw = JSON.parse(localStorage.getItem(WATCH_KEY) ?? "[]") as unknown;
if (Array.isArray(raw)) useDebug.setState({ watches: raw.filter((w): w is string => typeof w === "string") });
const stored = localStorage.getItem(key);
const raw = stored == null ? null : (JSON.parse(stored) as unknown);
return Array.isArray(raw) ? raw.filter((w): w is string => typeof w === "string") : null;
} catch {
// ignore
return null;
}
}
/** The watches of the project at `root`. */
export function loadWatches(root: string | null) {
watchRoot = root;
const own = root == null ? [] : storedWatches(watchKey(root));
useDebug.setState({ watches: own ?? storedWatches(SHARED_WATCH_KEY) ?? [] });
if (own == null) persistWatches();
}
/**
* "Set Value" (F2 in the variables tree): `setVariable` in `container`, or
* `setExpression` on the evaluate name. Refreshes variables, watches and inline values.