From 74cbf2489fb3a348b17ed0ab19474be3759b7781 Mon Sep 17 00:00:00 2001 From: William Laverty Date: Thu, 5 Feb 2026 01:05:37 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20=E2=87=A7=E2=8C=98T=20shortcut=20?= =?UTF-8?q?to=20reopen=20closed=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements #1656 This PR adds the ability to reopen recently closed editor tabs using the ⇧⌘T (Shift+Command+T) keyboard shortcut, a common convention in most editors and browsers. Changes: - Add closedTabsHistory to Editor to track recently closed tabs (up to 20) - Modify closeTab() to save tabs to the closed history before closing - Add reopenLastClosedTab() method to Editor - Add reopenClosedTab action to CodeEditWindowController - Add 'Reopen Closed Tab' menu item to File menu with ⇧⌘T shortcut The history is per-editor, so each split view maintains its own closed tab history. Users can repeatedly press ⇧⌘T to reopen multiple tabs in the order they were closed. --- .../CodeEditWindowController.swift | 4 ++ .../Editor/Models/Editor/Editor.swift | 50 +++++++++++++++++++ .../WindowCommands/FileCommands.swift | 5 ++ 3 files changed, 59 insertions(+) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index be082d6fee..6b937c870a 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift @@ -198,6 +198,10 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs } } + @IBAction func reopenClosedTab(_ sender: Any) { + workspace?.editorManager?.activeEditor.reopenLastClosedTab() + } + @IBAction func closeActiveEditor(_ sender: Any) { if workspace?.editorManager?.editorLayout.findSomeEditor( except: workspace?.editorManager?.activeEditor diff --git a/CodeEdit/Features/Editor/Models/Editor/Editor.swift b/CodeEdit/Features/Editor/Models/Editor/Editor.swift index 782b956b71..9e47137eec 100644 --- a/CodeEdit/Features/Editor/Models/Editor/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor/Editor.swift @@ -52,6 +52,13 @@ final class Editor: ObservableObject, Identifiable { /// - Warning: Use the ``addToHistory(_:)`` or ``clearFuture()`` methods to modify this. Do not modify directly. @Published var history: Deque = [] + /// Maintains the list of recently closed tabs that can be reopened. + /// The most recently closed tab is at the front of the deque. + @Published var closedTabsHistory: Deque = [] + + /// Maximum number of closed tabs to remember. + private static let maxClosedTabsHistory = 20 + /// Currently selected tab. @Published private(set) var selectedTab: Tab? @@ -154,6 +161,10 @@ final class Editor: ObservableObject, Identifiable { if file != selectedTab?.file { addToHistory(EditorInstance(workspace: workspace, file: file)) } + + // Add to closed tabs history for reopening later + addToClosedTabsHistory(file) + removeTab(file) if let selectedTab { addToHistory(selectedTab) @@ -167,6 +178,45 @@ final class Editor: ObservableObject, Identifiable { file.fileDocument = nil } + /// Adds a file to the closed tabs history. + /// - Parameter file: The file to add to the history. + private func addToClosedTabsHistory(_ file: CEWorkspaceFile) { + // Don't add duplicates (in case the same file is closed multiple times) + closedTabsHistory.removeAll(where: { $0 == file }) + + // Add to the front of the history + closedTabsHistory.prepend(file) + + // Trim to max size + while closedTabsHistory.count > Self.maxClosedTabsHistory { + closedTabsHistory.removeLast() + } + } + + /// Whether there are closed tabs that can be reopened. + var canReopenClosedTab: Bool { + !closedTabsHistory.isEmpty + } + + /// Reopens the most recently closed tab. + /// - Returns: The file that was reopened, or nil if there are no closed tabs. + @discardableResult + func reopenLastClosedTab() -> CEWorkspaceFile? { + guard let file = closedTabsHistory.popFirst() else { + return nil + } + + // Only reopen if the tab isn't already open + if !tabs.contains(where: { $0.file == file }) { + openTab(file: file) + } else { + // If already open, just select it + setSelectedTab(file) + } + + return file + } + /// Closes the currently opened tab in the tab group. func closeSelectedTab() { guard let file = selectedTab?.file else { diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index 130ce0e5b1..1df9e991be 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -51,6 +51,11 @@ struct FileCommands: Commands { } .keyboardShortcut("w") + Button("Reopen Closed Tab") { + NSApp.sendAction(#selector(CodeEditWindowController.reopenClosedTab(_:)), to: nil, from: nil) + } + .keyboardShortcut("t", modifiers: [.command, .shift]) + Button("Close Editor") { if NSApp.target(forAction: #selector(CodeEditWindowController.closeActiveEditor(_:))) != nil { NSApp.sendAction(