дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
fix(lightedit): merge the saved session with launched tabs instead of replacing them (#2229) (#2290)
Цей коміт міститься в:
1 джерело
e77ee39047
коміт
b12fabd472
3 files changed
+90
-1
No files matched your search
@@ -56,6 +56,7 @@ import {
|
||||
enforceTabLimit,
|
||||
findTab,
|
||||
initialTabsState,
|
||||
mergeSessionTabs,
|
||||
moveTab,
|
||||
moveTabToNewSplit,
|
||||
neighbourPane,
|
||||
@@ -531,7 +532,9 @@ export class EditorController implements SlotHost {
|
||||
this.restoredTabs = new Set(Object.values(state.panes).flatMap((p) => p.tabs.map((t) => t.id)));
|
||||
this.restoring = true;
|
||||
try {
|
||||
useTabs.setState(state, true);
|
||||
// A restore landing after tabs were already opened (e.g. a launched file
|
||||
// admitted while the transition was still in flight) must not wipe them.
|
||||
useTabs.setState((current) => mergeSessionTabs(current, state), true);
|
||||
} finally {
|
||||
this.restoring = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { EditorController, LIGHT_EDIT_SESSION, openFilePaths, useTabs } from "./controller";
|
||||
import { createMemoryBackend } from "./backend";
|
||||
import { initialTabsState } from "./model/tabs";
|
||||
import { storeSession } from "./model/session";
|
||||
import { useProject } from "@/platform/project";
|
||||
|
||||
let ctrl: EditorController | null = null;
|
||||
const initialSettings = useProject.getState().settings;
|
||||
|
||||
afterEach(() => {
|
||||
ctrl?.stop();
|
||||
ctrl = null;
|
||||
useTabs.setState(initialTabsState(), true);
|
||||
useProject.setState({ project: null, lightEdit: false, settings: initialSettings } as never);
|
||||
try {
|
||||
localStorage.removeItem(`openidea.editorSession.v1:${LIGHT_EDIT_SESSION}`);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
|
||||
function activePath() {
|
||||
const state = useTabs.getState();
|
||||
const pane = state.panes[state.activePane]!;
|
||||
return pane.tabs.find((tab) => tab.id === pane.active)?.input.path;
|
||||
}
|
||||
|
||||
function seedLightEditSession() {
|
||||
storeSession(LIGHT_EDIT_SESSION, {
|
||||
version: 1,
|
||||
layout: { kind: "pane", tabs: [{ path: "/p/a.ts" }], active: 0 },
|
||||
activePane: 0,
|
||||
files: {},
|
||||
});
|
||||
}
|
||||
|
||||
describe("lightedit launch (#2229)", () => {
|
||||
it("admits the launched file after the saved session restores", async () => {
|
||||
seedLightEditSession();
|
||||
ctrl = new EditorController(createMemoryBackend({ "/p/a.ts": "a", "/p/b.ts": "b" }));
|
||||
ctrl.start();
|
||||
// openFilesHere: window has neither project nor LightEdit -> openLightEdit(b) then openInEditor([b]).
|
||||
useProject.setState({ project: null, lightEdit: true });
|
||||
await ctrl.openFile("/p/b.ts", { focus: true });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
expect(openFilePaths(useTabs.getState()).sort()).toEqual(["/p/a.ts", "/p/b.ts"]);
|
||||
expect(activePath()).toBe("/p/b.ts");
|
||||
});
|
||||
|
||||
it("merges a late saved-session restore with the launched tab instead of replacing it", async () => {
|
||||
seedLightEditSession();
|
||||
ctrl = new EditorController(createMemoryBackend({ "/p/a.ts": "a", "/p/b.ts": "b" }));
|
||||
ctrl.start();
|
||||
useProject.setState({ project: null, lightEdit: true });
|
||||
await ctrl.openFile("/p/b.ts", { focus: true });
|
||||
// A stale/second transition restore landing after the admit (#2229).
|
||||
ctrl.restoreSession(LIGHT_EDIT_SESSION);
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
expect(openFilePaths(useTabs.getState()).sort()).toEqual(["/p/a.ts", "/p/b.ts"]);
|
||||
expect(activePath()).toBe("/p/b.ts");
|
||||
});
|
||||
});
|
||||
@@ -93,6 +93,29 @@ export function openKeys(s: TabsState): Set<string> {
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Folds restored tabs into the current tabs without losing anything open: tabs
|
||||
* already open stay (with their active tab), restored tabs missing by key are
|
||||
* appended to the active pane. An empty window takes the restored state as-is,
|
||||
* keeping its saved layout and active tab.
|
||||
*/
|
||||
export function mergeSessionTabs(current: TabsState, restored: TabsState): TabsState {
|
||||
const target = current.panes[current.activePane];
|
||||
if (!target || !Object.values(current.panes).some((p) => p.tabs.length)) return restored;
|
||||
const seen = openKeys(current);
|
||||
const additions: Tab[] = [];
|
||||
for (const pane of Object.values(restored.panes))
|
||||
for (const tab of pane.tabs) {
|
||||
if (seen.has(tab.input.key)) continue;
|
||||
seen.add(tab.input.key);
|
||||
additions.push(tab);
|
||||
}
|
||||
if (!additions.length) return current;
|
||||
const tabs = [...target.tabs, ...additions];
|
||||
const mru = [...target.mru, ...additions.map((t) => t.id).filter((id) => !target.mru.includes(id))];
|
||||
return withPane(current, { ...target, tabs, mru });
|
||||
}
|
||||
|
||||
function withPane(s: TabsState, pane: Pane): TabsState {
|
||||
return { ...s, panes: { ...s.panes, [pane.id]: pane } };
|
||||
}
|
||||
|
||||
Reference in new issue
Заблокувати користувача