From 1fc13392a9b41f7518ae5c298c7817306dda8af4 Mon Sep 17 00:00:00 2001 From: Shreyan Gupta <64853271+RA1NCS@users.noreply.github.com> Date: Thu, 7 May 2026 16:04:34 -0400 Subject: [PATCH 1/4] Track all reopenable tabs and toast on empty reopen --- src/features/editor/stores/buffer-store.ts | 48 ++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/src/features/editor/stores/buffer-store.ts b/src/features/editor/stores/buffer-store.ts index b33b8367..ff53af53 100644 --- a/src/features/editor/stores/buffer-store.ts +++ b/src/features/editor/stores/buffer-store.ts @@ -1057,18 +1057,31 @@ export const useBufferStore = createSelectors( .catch((error) => { logger.error("BufferStore", "Failed to stop LSP:", error); }); + } - // Add to closed history + // Track every closeable editor buffer (real or preview, including images, + // pdfs, diffs) so Cmd+Shift+T can reopen the most recent one. Skip ephemeral + // types that have no path or can't be re-resolved from disk. + const isReopenable = + closedBuffer.type === "editor" || + closedBuffer.type === "image" || + closedBuffer.type === "pdf" || + closedBuffer.type === "binary" || + closedBuffer.type === "markdownPreview" || + closedBuffer.type === "htmlPreview" || + closedBuffer.type === "csvPreview"; + + if (isReopenable && closedBuffer.path) { const closedBufferInfo: ClosedBuffer = { path: closedBuffer.path, name: closedBuffer.name, isPinned: closedBuffer.isPinned, }; - const updatedHistory = [closedBufferInfo, ...closedBuffersHistory].slice( - 0, - EDITOR_CONSTANTS.MAX_CLOSED_BUFFERS_HISTORY, - ); + const updatedHistory = [ + closedBufferInfo, + ...closedBuffersHistory.filter((entry) => entry.path !== closedBuffer.path), + ].slice(0, EDITOR_CONSTANTS.MAX_CLOSED_BUFFERS_HISTORY); set((state) => { state.closedBuffersHistory = updatedHistory; @@ -1526,18 +1539,37 @@ export const useBufferStore = createSelectors( }, reopenClosedTab: async () => { - const { closedBuffersHistory } = get(); + const { closedBuffersHistory, buffers } = get(); if (closedBuffersHistory.length === 0) { + const { toast } = await import("@/ui/toast"); + toast.info("No recently closed tabs"); return; } - const [closedBuffer, ...remainingHistory] = closedBuffersHistory; + // Pop the most recently closed entry. Skip any entry that's already open + // (re-add to head would be a no-op) — pull the next one instead. + let closedBuffer: ClosedBuffer | undefined; + let remainingHistory = closedBuffersHistory; + while (remainingHistory.length > 0) { + const [head, ...rest] = remainingHistory; + remainingHistory = rest; + if (!buffers.some((b) => b.path === head.path)) { + closedBuffer = head; + break; + } + } set((state) => { state.closedBuffersHistory = remainingHistory; }); + if (!closedBuffer) { + const { toast } = await import("@/ui/toast"); + toast.info("No recently closed tabs"); + return; + } + try { const content = await readFileContent(closedBuffer.path); const bufferId = get().actions.openContent({ @@ -1552,6 +1584,8 @@ export const useBufferStore = createSelectors( } } catch (error) { logger.warn("Editor", `Failed to reopen closed tab: ${closedBuffer.path}`, error); + const { toast } = await import("@/ui/toast"); + toast.error(`Couldn't reopen ${closedBuffer.name}`); } }, }, From 1e6216aa21f109e8dab0f1df23fc621e2c369cbd Mon Sep 17 00:00:00 2001 From: Shreyan Gupta <64853271+RA1NCS@users.noreply.github.com> Date: Thu, 7 May 2026 23:01:31 -0400 Subject: [PATCH 2/4] Route reopen-closed-tab through handleFileSelect for type-aware open --- src/features/editor/stores/buffer-store.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/features/editor/stores/buffer-store.ts b/src/features/editor/stores/buffer-store.ts index ff53af53..8ce1997a 100644 --- a/src/features/editor/stores/buffer-store.ts +++ b/src/features/editor/stores/buffer-store.ts @@ -1571,16 +1571,17 @@ export const useBufferStore = createSelectors( } try { - const content = await readFileContent(closedBuffer.path); - const bufferId = get().actions.openContent({ - type: "editor", - path: closedBuffer.path, - name: closedBuffer.name, - content, - }); + // Delegate to handleFileSelect — it routes by extension (image, pdf, + // binary, diff, editor) so reopening an image doesn't fall back to + // the editor type and render as raw binary. + const { useFileSystemStore } = await import("@/features/file-system/controllers/store"); + await useFileSystemStore.getState().handleFileSelect(closedBuffer.path, false); if (closedBuffer.isPinned) { - get().actions.handleTabPin(bufferId); + const reopenedBufferId = get().activeBufferId; + if (reopenedBufferId) { + get().actions.handleTabPin(reopenedBufferId); + } } } catch (error) { logger.warn("Editor", `Failed to reopen closed tab: ${closedBuffer.path}`, error); From 3019634a978f59db0935ae05e666c2a0aff9a2e7 Mon Sep 17 00:00:00 2001 From: Shreyan Gupta <64853271+RA1NCS@users.noreply.github.com> Date: Sun, 10 May 2026 23:35:23 -0400 Subject: [PATCH 3/4] Fix reopen-closed-tab for previews and diffs - restore markdown, html, csv preview tabs with their preview state intact - reopen diff tabs from saved diff data instead of dropping back to file open flow - keep closed-tab history typed so different tab kinds do not overwrite each other --- src/features/editor/stores/buffer-store.ts | 143 +++++++++++++++++---- 1 file changed, 117 insertions(+), 26 deletions(-) diff --git a/src/features/editor/stores/buffer-store.ts b/src/features/editor/stores/buffer-store.ts index 8ce1997a..7c602ef4 100644 --- a/src/features/editor/stores/buffer-store.ts +++ b/src/features/editor/stores/buffer-store.ts @@ -51,12 +51,41 @@ interface PendingClose { keepBufferId?: string; } -interface ClosedBuffer { +type ClosedBufferType = + | "editor" + | "image" + | "pdf" + | "binary" + | "diff" + | "markdownPreview" + | "htmlPreview" + | "csvPreview"; + +interface ClosedBufferBase { + type: ClosedBufferType; path: string; name: string; isPinned: boolean; } +interface ClosedEditorLikeBuffer extends ClosedBufferBase { + type: "editor" | "image" | "pdf" | "binary"; +} + +interface ClosedDiffBuffer extends ClosedBufferBase { + type: "diff"; + content: string; + diffData?: GitDiff | MultiFileDiff; +} + +interface ClosedPreviewBuffer extends ClosedBufferBase { + type: "markdownPreview" | "htmlPreview" | "csvPreview"; + content: string; + sourceFilePath: string; +} + +type ClosedBuffer = ClosedEditorLikeBuffer | ClosedDiffBuffer | ClosedPreviewBuffer; + interface BufferState { buffers: PaneContent[]; activeBufferId: string | null; @@ -208,6 +237,60 @@ const getPaneReplacementBufferId = ( return candidates.find((bufferId) => openBufferIds.has(bufferId)) ?? null; }; +const isReopenableBuffer = ( + buffer: PaneContent, +): buffer is Extract => { + return ( + buffer.type === "editor" || + buffer.type === "image" || + buffer.type === "pdf" || + buffer.type === "binary" || + buffer.type === "diff" || + buffer.type === "markdownPreview" || + buffer.type === "htmlPreview" || + buffer.type === "csvPreview" + ); +}; + +const getClosedBufferHistoryKey = (buffer: ClosedBuffer) => `${buffer.type}:${buffer.path}`; + +const buildClosedBufferHistoryEntry = (buffer: PaneContent): ClosedBuffer | null => { + if (!isReopenableBuffer(buffer) || !buffer.path) return null; + + switch (buffer.type) { + case "editor": + case "image": + case "pdf": + case "binary": + return { + type: buffer.type, + path: buffer.path, + name: buffer.name, + isPinned: buffer.isPinned, + }; + case "diff": + return { + type: "diff", + path: buffer.path, + name: buffer.name, + isPinned: buffer.isPinned, + content: buffer.content, + diffData: buffer.diffData, + }; + case "markdownPreview": + case "htmlPreview": + case "csvPreview": + return { + type: buffer.type, + path: buffer.path, + name: buffer.name, + isPinned: buffer.isPinned, + content: buffer.content, + sourceFilePath: buffer.sourceFilePath, + }; + } +}; + /** * Run extension checking and LSP logic for a newly opened editor file. */ @@ -1059,28 +1142,13 @@ export const useBufferStore = createSelectors( }); } - // Track every closeable editor buffer (real or preview, including images, - // pdfs, diffs) so Cmd+Shift+T can reopen the most recent one. Skip ephemeral - // types that have no path or can't be re-resolved from disk. - const isReopenable = - closedBuffer.type === "editor" || - closedBuffer.type === "image" || - closedBuffer.type === "pdf" || - closedBuffer.type === "binary" || - closedBuffer.type === "markdownPreview" || - closedBuffer.type === "htmlPreview" || - closedBuffer.type === "csvPreview"; - - if (isReopenable && closedBuffer.path) { - const closedBufferInfo: ClosedBuffer = { - path: closedBuffer.path, - name: closedBuffer.name, - isPinned: closedBuffer.isPinned, - }; - + const closedBufferInfo = buildClosedBufferHistoryEntry(closedBuffer); + if (closedBufferInfo) { const updatedHistory = [ closedBufferInfo, - ...closedBuffersHistory.filter((entry) => entry.path !== closedBuffer.path), + ...closedBuffersHistory.filter( + (entry) => getClosedBufferHistoryKey(entry) !== getClosedBufferHistoryKey(closedBufferInfo), + ), ].slice(0, EDITOR_CONSTANTS.MAX_CLOSED_BUFFERS_HISTORY); set((state) => { @@ -1571,11 +1639,34 @@ export const useBufferStore = createSelectors( } try { - // Delegate to handleFileSelect — it routes by extension (image, pdf, - // binary, diff, editor) so reopening an image doesn't fall back to - // the editor type and render as raw binary. - const { useFileSystemStore } = await import("@/features/file-system/controllers/store"); - await useFileSystemStore.getState().handleFileSelect(closedBuffer.path, false); + if ( + closedBuffer.type === "markdownPreview" || + closedBuffer.type === "htmlPreview" || + closedBuffer.type === "csvPreview" + ) { + get().actions.openContent({ + type: closedBuffer.type, + path: closedBuffer.path, + name: closedBuffer.name, + content: closedBuffer.content, + sourceFilePath: closedBuffer.sourceFilePath, + }); + } else if (closedBuffer.type === "diff") { + get().actions.openContent({ + type: "diff", + path: closedBuffer.path, + name: closedBuffer.name, + content: closedBuffer.content, + diffData: closedBuffer.diffData, + }); + } else { + // Delegate file-backed types to handleFileSelect so reopen stays aligned + // with the main file-open routing. + const { useFileSystemStore } = await import( + "@/features/file-system/controllers/store" + ); + await useFileSystemStore.getState().handleFileSelect(closedBuffer.path, false); + } if (closedBuffer.isPinned) { const reopenedBufferId = get().activeBufferId; From 5806358e0eda2c9a081373d873a94d72548fe699 Mon Sep 17 00:00:00 2001 From: Shreyan Gupta <64853271+RA1NCS@users.noreply.github.com> Date: Mon, 11 May 2026 00:17:42 -0400 Subject: [PATCH 4/4] Format reopened buffer store --- src/features/editor/stores/buffer-store.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/editor/stores/buffer-store.ts b/src/features/editor/stores/buffer-store.ts index 7c602ef4..05a508ca 100644 --- a/src/features/editor/stores/buffer-store.ts +++ b/src/features/editor/stores/buffer-store.ts @@ -1147,7 +1147,8 @@ export const useBufferStore = createSelectors( const updatedHistory = [ closedBufferInfo, ...closedBuffersHistory.filter( - (entry) => getClosedBufferHistoryKey(entry) !== getClosedBufferHistoryKey(closedBufferInfo), + (entry) => + getClosedBufferHistoryKey(entry) !== getClosedBufferHistoryKey(closedBufferInfo), ), ].slice(0, EDITOR_CONSTANTS.MAX_CLOSED_BUFFERS_HISTORY); @@ -1662,9 +1663,8 @@ export const useBufferStore = createSelectors( } else { // Delegate file-backed types to handleFileSelect so reopen stays aligned // with the main file-open routing. - const { useFileSystemStore } = await import( - "@/features/file-system/controllers/store" - ); + const { useFileSystemStore } = + await import("@/features/file-system/controllers/store"); await useFileSystemStore.getState().handleFileSelect(closedBuffer.path, false); }