feat(editor): Close read-only tabs and keep a preview tab open from the tab menu (#165)

The editor tab menu gains Close All Read-Only Tabs (shown when the group has
unpinned read-only tabs) and Keep Tab Open (on a preview tab), which turns the
preview into a regular tab.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-29 03:06:12 +08:00
1 джерело e0af8ccce1
коміт c7f75cb693
3 files changed
+29

No files matched your search

+19
Переглянути файл
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it } from "vitest";
import { EditorController, openFilePaths, useEditorStatus, useTabs } from "./controller";
import { createMemoryBackend } from "./backend";
import { initialTabsState } from "./model/tabs";
import { useDocMeta } from "./model/document";
import { resetDistractionFree, setDistractionFree } from "@/workbench/distractionFree";
import { registerActions } from "@/platform/actions";
import { useProject } from "@/platform/project";
@@ -82,6 +83,24 @@ describe("EditorController", () => {
expect(paths(first)).toEqual(["/p/a.ts", "/p/b.ts"]);
});
it("closes read-only tabs and keeps a preview tab open from the tab menu", async () => {
ctrl = new EditorController(createMemoryBackend({ "/p/a.ts": "", "/p/b.ts": "", "/p/c.ts": "" }));
ctrl.start();
await ctrl.openFile("/p/a.ts", { focus: false });
await ctrl.openFile("/p/b.ts", { focus: false });
await ctrl.openFile("/p/c.ts", { focus: false, preview: true });
const pane = () => useTabs.getState().panes[useTabs.getState().activePane]!;
const labels = () => resolveMenuItems(ctrl!.tabMenu(pane().id, pane().tabs[2]!)).map((i) => i.label);
expect(labels()).toContain("Keep Tab Open");
expect(labels()).not.toContain("Close All Read-Only Tabs");
ctrl.keepTab(pane().id, pane().tabs[2]!.id);
expect(pane().tabs[2]!.preview).toBe(false);
useDocMeta.setState({ "/p/b.ts": { ...useDocMeta.getState()["/p/b.ts"]!, readOnly: true } });
expect(labels()).toContain("Close All Read-Only Tabs");
ctrl.closeReadOnly(pane().id);
expect(pane().tabs.map((t) => t.input.path)).toEqual(["/p/a.ts", "/p/c.ts"]);
});
it("reopens the LightEdit files the next time a window switches to LightEdit", async () => {
ctrl = new EditorController(createMemoryBackend({ "/x/a.txt": "a", "/y/b.md": "b" }));
ctrl.start();
+8
Переглянути файл
@@ -915,6 +915,10 @@ export class EditorController implements SlotHost {
};
this.apply((s) => closeUnmodified(s, paneId, modified));
}
closeReadOnly(paneId: string) {
const writable = (t: Tab) => !t.input.path || !useDocMeta.getState()[t.input.path]?.readOnly;
this.apply((s) => closeUnmodified(s, paneId, writable));
}
/** Reload from Disk: re-reads the open, unmodified documents of `paths`. */
async reloadFromDisk(paths: string[]) {
for (const path of paths) {
@@ -1189,8 +1193,12 @@ export class EditorController implements SlotHost {
...(tabs.some((t) => t.pinned)
? ([{ label: "Close All but Pinned", run: () => this.closeAll(paneId, true) }] as MenuItem[])
: []),
...(tabs.some((t) => !t.pinned && !!t.input.path && useDocMeta.getState()[t.input.path]?.readOnly)
? ([{ label: "Close All Read-Only Tabs", run: () => this.closeReadOnly(paneId) }] as MenuItem[])
: []),
{ kind: "separator" },
{ label: tab.pinned ? "Unpin Tab" : "Pin Tab", icon: "pin", run: () => this.togglePin(paneId, tab.id) },
...(tab.preview ? ([{ label: "Keep Tab Open", run: () => this.keepTab(paneId, tab.id) }] as MenuItem[]) : []),
{ kind: "separator" },
{ label: "Split and Move Right", run: () => this.moveTabToSplit(paneId, tab.id, paneId, "right") },
{ label: "Split and Move Down", run: () => this.moveTabToSplit(paneId, tab.id, paneId, "bottom") },
+2
Переглянути файл
@@ -280,6 +280,7 @@
"Cloned, but the project could not be opened": "已克隆,但无法打开项目",
"Cloning requires the desktop app": "克隆需要桌面应用",
"Close": "关闭",
"Close All Read-Only Tabs": "关闭所有只读标签页",
"Close All Tabs": "关闭所有标签页",
"Close All but Pinned": "关闭除已固定外的所有标签页",
"Close Annotations": "关闭注解",
@@ -954,6 +955,7 @@
"Jump to Source": "跳转到源代码",
"Just Restart": "仅重启",
"Keep": "保留",
"Keep Tab Open": "保持标签页打开",
"Keep Column": "保留列",
"Keep index": "保留索引",
"Keep local history for": "本地历史记录保留时长",