diff --git a/src/cm/commandRegistry.js b/src/cm/commandRegistry.js index 41a08304b5..fcab86b770 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,22 @@ 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((error) => { + window.log?.("error", "Failed to back-fill new key bindings!"); + window.log?.("error", error); + }); + } } } else { throw new Error("Key binding file not found"); diff --git a/src/lib/commands.js b/src/lib/commands.js index c704008b03..9c20cf0385 100644 --- a/src/lib/commands.js +++ b/src/lib/commands.js @@ -300,6 +300,9 @@ export default { files[fileIndex].makeActive(); }, + "next-file-history"() { + editorManager.openNextEditorFromHistory?.(); + }, async open(page) { switch (page) { case "settings": @@ -378,6 +381,9 @@ export default { files[fileIndex].makeActive(); }, + "prev-file-history"() { + 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..cfa40d87e5 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(); @@ -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",