diff --git a/src/editor/core/controller.test.ts b/src/editor/core/controller.test.ts index 21ead41ac..28ce1cfe4 100644 --- a/src/editor/core/controller.test.ts +++ b/src/editor/core/controller.test.ts @@ -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(); diff --git a/src/editor/core/controller.ts b/src/editor/core/controller.ts index deca08024..0ace3873c 100644 --- a/src/editor/core/controller.ts +++ b/src/editor/core/controller.ts @@ -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") }, diff --git a/src/platform/locales/text/zh.json b/src/platform/locales/text/zh.json index fb0ace456..296105277 100644 --- a/src/platform/locales/text/zh.json +++ b/src/platform/locales/text/zh.json @@ -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": "本地历史记录保留时长",