diff --git a/src/features/run/index.ts b/src/features/run/index.ts
index 4f8670ecf..bca9c6987 100644
--- a/src/features/run/index.ts
+++ b/src/features/run/index.ts
@@ -71,6 +71,7 @@ import {
resetRuns,
runningSessions,
saveUserConfigs,
+ dropStaleContextConfigs,
selectConfig,
selectedConfig,
startRun,
@@ -654,6 +655,8 @@ export function activate(): () => void {
disposers.push(
onIdeEvent("fsChanged", (e) => {
const paths = [...e.created, ...e.modified, ...e.removed];
+ // A file that moved or was deleted leaves its context configuration behind.
+ dropStaleContextConfigs(e.removed);
if (paths.some((p) => /(?:^|\/)go\.(?:mod|work)$/.test(p))) invalidateGoModules();
if (paths.some((p) => p.endsWith("/__init__.py"))) invalidatePythonPackages();
if (paths.some((p) => CONFIG_FILES.test(p))) scheduleReload();
diff --git a/src/features/run/removal.test.tsx b/src/features/run/removal.test.tsx
index 29733e03a..567ae03ff 100644
--- a/src/features/run/removal.test.tsx
+++ b/src/features/run/removal.test.tsx
@@ -19,7 +19,15 @@ import type { RunConfig } from "@/platform/ipc";
import { useProject } from "@/platform/project";
import { EditConfigsOverlay } from "./EditConfigsDialog";
import { ConfigChooserOverlay } from "./ConfigChooser";
-import { adoptTemporary, canDeleteConfig, deleteConfig, loadConfigs, resetRuns, useRun } from "./store";
+import {
+ adoptTemporary,
+ canDeleteConfig,
+ deleteConfig,
+ dropStaleContextConfigs,
+ loadConfigs,
+ resetRuns,
+ useRun,
+} from "./store";
import { registerBuiltinTypes } from "./types";
const config = (kind = "npm"): RunConfig => ({
@@ -112,6 +120,27 @@ it("temporary deletion does not persist suppression", async () => {
expect(useRun.getState().configs).toEqual([]);
});
+it("a moved file drops its stale temporary context configuration", async () => {
+ const old: RunConfig = { ...config("cc"), id: "context:file:src/main.cxx", name: "main.cxx", detected: false };
+ adoptTemporary(old);
+ expect(useRun.getState().configs.map((c) => c.id)).toContain("context:file:src/main.cxx");
+ dropStaleContextConfigs(["/test-removal/src/main.cxx"]);
+ expect(useRun.getState().configs.map((c) => c.id)).not.toContain("context:file:src/main.cxx");
+ expect(useRun.getState().temporary).toEqual([]);
+ expect(ipc.runSaveConfigs).not.toHaveBeenCalled();
+});
+
+it("keeps other temporary and non-temporary configurations on removal", async () => {
+ const moved: RunConfig = { ...config("cc"), id: "context:file:src/main.cxx", name: "main.cxx", detected: false };
+ const other: RunConfig = { ...config("go"), id: "context:file:app/main.go", name: "main.go", detected: false };
+ adoptTemporary(moved);
+ adoptTemporary(other);
+ dropStaleContextConfigs(["/test-removal/src/main.cxx"]);
+ expect(useRun.getState().temporary).toEqual([other.id]);
+ // The saved/detected npm config and an unrelated file stay.
+ expect(useRun.getState().configs.map((c) => c.id)).toEqual(expect.arrayContaining(["npm:app", other.id]));
+});
+
it("chooser popup Delete removes the selected detection", async () => {
useRun.setState({ chooserOpen: true, editorOpen: false });
render();
diff --git a/src/features/run/store.ts b/src/features/run/store.ts
index 3e3b25314..eab344fff 100644
--- a/src/features/run/store.ts
+++ b/src/features/run/store.ts
@@ -411,6 +411,32 @@ export function adoptTemporary(config: RunConfig): RunConfig {
return config;
}
+/**
+ * A file removed (or moved away) takes the temporary context configurations its
+ * path produced (`context:file:` and the compiled `context:build:` partner), like
+ * IDEA deleting the context configuration with the file. Saved and detected
+ * configurations the user made stay.
+ */
+export function dropStaleContextConfigs(removed: readonly string[]) {
+ const { configs, temporary, selectedId } = useRun.getState();
+ const root = useProject.getState().project?.root;
+ if (!root || !removed.length || !temporary.length) return;
+ // Temporary ids carry the project-relative path (the full path outside the root).
+ const gone = new Set(removed.map((p) => (p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p)));
+ const stale = (id: string) => {
+ const m = /^context:(?:file|build):(.+?)(?:#.+)?$/.exec(id);
+ return !!m && gone.has(m[1]!);
+ };
+ const dropped = temporary.filter(stale);
+ if (!dropped.length) return;
+ useRun.setState({
+ configs: configs.filter((c) => !dropped.includes(c.id)),
+ temporary: temporary.filter((id) => !stale(id)),
+ });
+ persistTemporary();
+ if (selectedId && dropped.includes(selectedId)) selectConfig(null);
+}
+
/** "Save Configuration": a temporary configuration becomes a user configuration. */
export async function saveTemporary(id: string): Promise {
const { configs, temporary } = useRun.getState();