diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift index be082d6fee..967324b2b2 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.reopenClosedTab() + } + @IBAction func closeActiveEditor(_ sender: Any) { if workspace?.editorManager?.editorLayout.findSomeEditor( except: workspace?.editorManager?.activeEditor diff --git a/CodeEdit/Features/Editor/Models/Editor/Editor+History.swift b/CodeEdit/Features/Editor/Models/Editor/Editor+History.swift index 8dfcf228d5..570c88b942 100644 --- a/CodeEdit/Features/Editor/Models/Editor/Editor+History.swift +++ b/CodeEdit/Features/Editor/Models/Editor/Editor+History.swift @@ -39,6 +39,25 @@ extension Editor { } } + /// Reopens the most recently closed tab. + /// Pops from the ``closedTabs`` stack and opens it as a new tab. + func reopenClosedTab() { + guard let file = closedTabs.popLast() else { return } + // Skip files that are already open. + if tabs.contains(where: { $0.file == file }) { + // Already open — select it instead and try the next closed tab. + setSelectedTab(file) + reopenClosedTab() + return + } + openTab(file: file) + } + + /// Whether there are closed tabs available to reopen. + var canReopenClosedTab: Bool { + !closedTabs.isEmpty + } + // TODO: move to @Observable so this works better /// Warning: NOT published! var canGoBackInHistory: Bool { diff --git a/CodeEdit/Features/Editor/Models/Editor/Editor.swift b/CodeEdit/Features/Editor/Models/Editor/Editor.swift index 782b956b71..7b0dc8bed6 100644 --- a/CodeEdit/Features/Editor/Models/Editor/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor/Editor.swift @@ -52,6 +52,10 @@ final class Editor: ObservableObject, Identifiable { /// - Warning: Use the ``addToHistory(_:)`` or ``clearFuture()`` methods to modify this. Do not modify directly. @Published var history: Deque = [] + /// Stack of recently closed tabs, used for ⇧⌘T reopen functionality. + /// Most recently closed tab is at the end. + @Published var closedTabs: [CEWorkspaceFile] = [] + /// Currently selected tab. @Published private(set) var selectedTab: Tab? @@ -145,6 +149,15 @@ final class Editor: ObservableObject, Identifiable { func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) { guard canCloseTab(file: file) else { return } + // Track closed tab for ⇧⌘T reopen. Avoid duplicates at the top of the stack. + if closedTabs.last != file { + closedTabs.append(file) + // Cap the stack to avoid unbounded growth. + if closedTabs.count > 25 { + closedTabs.removeFirst() + } + } + if temporaryTab?.file == file { temporaryTab = nil } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index 1aa65af926..b9d4e18201 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -90,6 +90,7 @@ extension ProjectNavigatorMenu { if let newFile = try workspace?.workspaceFileManager?.addFile(fileName: "untitled", toFile: item) { workspace?.listenerModel.highlightedFileItem = newFile workspace?.editorManager?.openTab(item: newFile) + renameFile() } } catch { let alert = NSAlert(error: error) @@ -147,6 +148,7 @@ extension ProjectNavigatorMenu { do { if let newFolder = try workspace?.workspaceFileManager?.addFolder(folderName: "untitled", toFile: item) { workspace?.listenerModel.highlightedFileItem = newFolder + renameFile() } } catch { let alert = NSAlert(error: error) diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index 130ce0e5b1..297db94971 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -51,6 +51,15 @@ struct FileCommands: Commands { } .keyboardShortcut("w") + Button("Reopen Closed Tab") { + NSApp.sendAction( + #selector(CodeEditWindowController.reopenClosedTab(_:)), + to: nil, + from: nil + ) + } + .keyboardShortcut("t", modifiers: [.shift, .command]) + Button("Close Editor") { if NSApp.target(forAction: #selector(CodeEditWindowController.closeActiveEditor(_:))) != nil { NSApp.sendAction(