From 3f191d2f4f53e27b2275f1116010790797d307b3 Mon Sep 17 00:00:00 2001 From: Priyanshu Jain Date: Wed, 8 Jul 2026 17:22:32 +0530 Subject: [PATCH 1/2] fix: implement editor tab history navigation (#1819) --- src/cm/commandRegistry.js | 32 +++++++++++++++ src/lib/commands.js | 12 ++++++ src/lib/editorManager.js | 86 ++++++++++++++++++++++++++++++++++++++- src/lib/keyBindings.js | 14 +++++++ 4 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/cm/commandRegistry.js b/src/cm/commandRegistry.js index 41a08304b5..df148c2722 100644 --- a/src/cm/commandRegistry.js +++ b/src/cm/commandRegistry.js @@ -363,6 +363,26 @@ function registerCoreCommands() { return true; }, }); + addCommand({ + name: "nextFileHistory", + description: "Open next file tab from history", + readOnly: true, + requiresView: false, + run() { + acode.exec("next-file-history"); + return true; + }, + }); + addCommand({ + name: "prevFileHistory", + description: "Open previous file tab from history", + readOnly: true, + requiresView: false, + run() { + acode.exec("prev-file-history"); + return true; + }, + }); addCommand({ name: "showSettingsMenu", description: "Show settings menu", @@ -1637,7 +1657,19 @@ async function loadCustomKeyBindings() { if (await bindingsFile.exists()) { const bindings = await bindingsFile.readFile("json"); if (bindings && typeof bindings === "object") { + let updated = false; + Object.keys(keyBindings).forEach((key) => { + if (!(key in bindings)) { + bindings[key] = keyBindings[key]; + updated = true; + } + }); resolvedKeyBindings = bindings; + if (updated) { + bindingsFile + .writeFile(JSON.stringify(bindings, undefined, 2)) + .catch(() => {}); + } } } else { throw new Error("Key binding file not found"); diff --git a/src/lib/commands.js b/src/lib/commands.js index c704008b03..125f4bc288 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -300,6 +300,12 @@ export default { files[fileIndex].makeActive(); }, + "next-file-history"() { + editorManager.openNextEditorFromHistory?.(); + }, + openNextEditorFromHistory() { + editorManager.openNextEditorFromHistory?.(); + }, async open(page) { switch (page) { case "settings": @@ -378,6 +384,12 @@ export default { files[fileIndex].makeActive(); }, + "prev-file-history"() { + editorManager.openPreviousEditorFromHistory?.(); + }, + openPreviousEditorFromHistory() { + editorManager.openPreviousEditorFromHistory?.(); + }, "read-only"() { const file = editorManager.activeFile; file.editable = !file.editable; diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 3a12f61841..989ee2a2f3 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -138,6 +138,9 @@ async function EditorManager($header, $body) { let touchSelectionSyncRaf = 0; let nativeContextMenuDisabled = null; const recoverableWarningKeys = new Set(); + let historyStack = []; + let historyIndex = -1; + let isNavigatingHistory = false; function warnRecoverable(message, error, key) { if (key) { @@ -705,6 +708,7 @@ async function EditorManager($header, $body) { updateActivePaneScrollbars(); toggleProblemButton(); if (options.emitSwitch !== false && manager.activeFile) { + recordHistory(manager.activeFile); manager.onupdate("switch-file"); events.emit("switch-file", manager.activeFile); } @@ -3152,6 +3156,15 @@ async function EditorManager($header, $body) { getEditorHeight, getEditorWidth, header: $header, + openPreviousEditorFromHistory, + openNextEditorFromHistory, + recordHistory, + get editorHistory() { + return historyStack; + }, + get editorHistoryIndex() { + return historyIndex; + }, getLspMetadata: buildLspMetadata, get editor() { return getActivePane()?.editor || editor; @@ -3647,6 +3660,7 @@ async function EditorManager($header, $body) { }); manager.on(["remove-file"], (file) => { + removeFileFromHistory(file); clearDocSyncTimers(file); detachLspForFile(file); toggleProblemButton(); @@ -4204,7 +4218,7 @@ async function EditorManager($header, $body) { setNativeContextMenuDisabled(isFocused); function handleContentFocus(_event) { - setActivePane(pane); + setActivePane(pane, { emitSwitch: false }); setNativeContextMenuDisabled(true); const activeFile = pane.activeFile; if (activeFile) { @@ -4746,6 +4760,7 @@ async function EditorManager($header, $body) { file.tab?.classList.add("active"); updateHeaderForFile(file); if (isPaneTabLayout()) syncGlobalOpenFileListMirror(); + recordHistory(file); manager.onupdate("switch-file"); events.emit("switch-file", file); toggleProblemButton(); @@ -4812,6 +4827,7 @@ async function EditorManager($header, $body) { } } } + recordHistory(file); manager.onupdate("switch-file"); events.emit("switch-file", file); @@ -4824,6 +4840,74 @@ async function EditorManager($header, $body) { applyFileToEditor(file, { forceRecreate: true }); } + function recordHistory(file) { + if (!file || isNavigatingHistory) return; + const current = historyStack[historyIndex]; + if (current?.id === file.id) return; + + historyStack = historyStack.slice(0, historyIndex + 1); + historyStack.push(file); + historyIndex = historyStack.length - 1; + + const MAX_HISTORY = 100; + if (historyStack.length > MAX_HISTORY) { + historyStack.shift(); + historyIndex = Math.max(0, historyIndex - 1); + } + } + + function removeFileFromHistory(file) { + if (!file) return; + for (let i = historyStack.length - 1; i >= 0; i--) { + if (historyStack[i]?.id === file.id) { + historyStack.splice(i, 1); + if (i <= historyIndex) { + historyIndex--; + } + } + } + if (historyIndex < 0 && historyStack.length > 0) { + historyIndex = 0; + } + } + + function openPreviousEditorFromHistory() { + while (historyIndex > 0) { + historyIndex--; + const file = historyStack[historyIndex]; + if (file && getFile(file.id, "id")) { + isNavigatingHistory = true; + try { + file.makeActive(); + } finally { + isNavigatingHistory = false; + } + return true; + } + historyStack.splice(historyIndex, 1); + } + return false; + } + + function openNextEditorFromHistory() { + while (historyIndex < historyStack.length - 1) { + historyIndex++; + const file = historyStack[historyIndex]; + if (file && getFile(file.id, "id")) { + isNavigatingHistory = true; + try { + file.makeActive(); + } finally { + isNavigatingHistory = false; + } + return true; + } + historyStack.splice(historyIndex, 1); + historyIndex--; + } + return false; + } + /** * Initializes the file tab container. */ diff --git a/src/lib/keyBindings.js b/src/lib/keyBindings.js index 1f9a00feb5..5ac9841663 100644 --- a/src/lib/keyBindings.js +++ b/src/lib/keyBindings.js @@ -122,6 +122,20 @@ const APP_BINDING_CONFIG = [ action: "prev-file", readOnly: true, }, + { + name: "nextFileHistory", + description: "Open next file tab from history", + key: null, + action: "next-file-history", + readOnly: true, + }, + { + name: "prevFileHistory", + description: "Open previous file tab from history", + key: null, + action: "prev-file-history", + readOnly: true, + }, { name: "splitPaneRight", description: "Split editor pane right", From e2d1d015a197aed87b55b20eae83a030d165275b Mon Sep 17 00:00:00 2001 From: Priyanshu Jain Date: Wed, 8 Jul 2026 17:33:24 +0530 Subject: [PATCH 2/2] refactor: address Greptile code review feedback --- src/cm/commandRegistry.js | 5 ++++- src/lib/commands.js | 6 ------ src/lib/editorManager.js | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/cm/commandRegistry.js b/src/cm/commandRegistry.js index df148c2722..fcab86b770 100644 --- a/src/cm/commandRegistry.js +++ b/src/cm/commandRegistry.js @@ -1668,7 +1668,10 @@ async function loadCustomKeyBindings() { if (updated) { bindingsFile .writeFile(JSON.stringify(bindings, undefined, 2)) - .catch(() => {}); + .catch((error) => { + window.log?.("error", "Failed to back-fill new key bindings!"); + window.log?.("error", error); + }); } } } else { diff --git a/src/lib/commands.js b/src/lib/commands.js index 125f4bc288..9c20cf0385 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -303,9 +303,6 @@ export default { "next-file-history"() { editorManager.openNextEditorFromHistory?.(); }, - openNextEditorFromHistory() { - editorManager.openNextEditorFromHistory?.(); - }, async open(page) { switch (page) { case "settings": @@ -387,9 +384,6 @@ export default { "prev-file-history"() { editorManager.openPreviousEditorFromHistory?.(); }, - openPreviousEditorFromHistory() { - editorManager.openPreviousEditorFromHistory?.(); - }, "read-only"() { const file = editorManager.activeFile; file.editable = !file.editable; diff --git a/src/lib/editorManager.js b/src/lib/editorManager.js index 989ee2a2f3..cfa40d87e5 100644 --- a/src/lib/editorManager.js +++ b/src/lib/editorManager.js @@ -4218,7 +4218,7 @@ async function EditorManager($header, $body) { setNativeContextMenuDisabled(isFocused); function handleContentFocus(_event) { - setActivePane(pane, { emitSwitch: false }); + setActivePane(pane); setNativeContextMenuDisabled(true); const activeFile = pane.activeFile; if (activeFile) {