Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 148 additions & 22 deletions src/features/editor/stores/buffer-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -208,6 +237,60 @@ const getPaneReplacementBufferId = (
return candidates.find((bufferId) => openBufferIds.has(bufferId)) ?? null;
};

const isReopenableBuffer = (
buffer: PaneContent,
): buffer is Extract<PaneContent, { type: ClosedBufferType }> => {
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.
*/
Expand Down Expand Up @@ -1057,18 +1140,17 @@ export const useBufferStore = createSelectors(
.catch((error) => {
logger.error("BufferStore", "Failed to stop LSP:", error);
});
}

// Add to closed history
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 closedBufferInfo = buildClosedBufferHistoryEntry(closedBuffer);
if (closedBufferInfo) {
const updatedHistory = [
closedBufferInfo,
...closedBuffersHistory.filter(
(entry) =>
getClosedBufferHistoryKey(entry) !== getClosedBufferHistoryKey(closedBufferInfo),
),
].slice(0, EDITOR_CONSTANTS.MAX_CLOSED_BUFFERS_HISTORY);

set((state) => {
state.closedBuffersHistory = updatedHistory;
Expand Down Expand Up @@ -1526,32 +1608,76 @@ 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({
type: "editor",
path: closedBuffer.path,
name: closedBuffer.name,
content,
});
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) {
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);
const { toast } = await import("@/ui/toast");
toast.error(`Couldn't reopen ${closedBuffer.name}`);
}
},
},
Expand Down
Loading