From 00624d271d0bd342bc27858ebffb45ac4d8a20f1 Mon Sep 17 00:00:00 2001 From: William Laverty Date: Fri, 13 Feb 2026 01:03:29 -0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Add=20=E2=87=A7=E2=8C=98T=20shortcu?= =?UTF-8?q?t=20to=20reopen=20recently=20closed=20tabs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements a recently-closed tab stack on the Editor model. When a tab is closed, its file reference is pushed onto the stack (up to 20 entries). Pressing ⇧⌘T (Shift+Command+T) pops the most recently closed tab and reopens it in the active editor. Changes: - Editor: Add recentlyClosedTabs stack, addToRecentlyClosed(), reopenClosedTab() - CodeEditWindowController: Add reopenClosedTab(_:) action - FileCommands: Add 'Reopen Closed Tab' menu item with ⇧⌘T shortcut Closes #1656 --- .../CodeEditWindowController.swift | 4 +++ .../Editor/Models/Editor/Editor.swift | 32 +++++++++++++++++++ .../WindowCommands/FileCommands.swift | 7 ++++ 3 files changed, 43 insertions(+) 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.swift b/CodeEdit/Features/Editor/Models/Editor/Editor.swift index 782b956b71..aeafee8d4e 100644 --- a/CodeEdit/Features/Editor/Models/Editor/Editor.swift +++ b/CodeEdit/Features/Editor/Models/Editor/Editor.swift @@ -57,6 +57,12 @@ final class Editor: ObservableObject, Identifiable { @Published var temporaryTab: Tab? + /// Stack of recently closed files, used to support reopening closed tabs via ⇧⌘T. + @Published var recentlyClosedTabs: [CEWorkspaceFile] = [] + + /// Maximum number of recently closed tabs to remember. + private static let maxRecentlyClosedTabs = 20 + var id = UUID() weak var parent: SplitViewData? @@ -145,6 +151,9 @@ final class Editor: ObservableObject, Identifiable { func closeTab(file: CEWorkspaceFile, fromHistory: Bool = false) { guard canCloseTab(file: file) else { return } + // Remember the closed tab so it can be reopened with ⇧⌘T + addToRecentlyClosed(file) + if temporaryTab?.file == file { temporaryTab = nil } @@ -322,6 +331,29 @@ final class Editor: ObservableObject, Identifiable { /// Remove the given file from tabs. /// - Parameter file: The file to remove. + // MARK: - Recently Closed Tabs + + /// Pushes a file onto the recently-closed stack. + private func addToRecentlyClosed(_ file: CEWorkspaceFile) { + // Remove duplicates so the most recent close is always on top + recentlyClosedTabs.removeAll(where: { $0 == file }) + recentlyClosedTabs.append(file) + if recentlyClosedTabs.count > Self.maxRecentlyClosedTabs { + recentlyClosedTabs.removeFirst() + } + } + + /// Reopens the most recently closed tab, if any. + func reopenClosedTab() { + guard let file = recentlyClosedTabs.popLast() else { return } + openTab(file: file) + } + + /// Whether there are any recently closed tabs that can be reopened. + var canReopenClosedTab: Bool { + !recentlyClosedTabs.isEmpty + } + func removeTab(_ file: CEWorkspaceFile) { tabs.removeAll(where: { tab in tab.file == file }) if temporaryTab?.file == file { diff --git a/CodeEdit/Features/WindowCommands/FileCommands.swift b/CodeEdit/Features/WindowCommands/FileCommands.swift index 130ce0e5b1..238a38f650 100644 --- a/CodeEdit/Features/WindowCommands/FileCommands.swift +++ b/CodeEdit/Features/WindowCommands/FileCommands.swift @@ -51,6 +51,13 @@ struct FileCommands: Commands { } .keyboardShortcut("w") + Button("Reopen Closed Tab") { + NSApp.sendAction(#selector(CodeEditWindowController.reopenClosedTab(_:)), to: nil, from: nil) + } + .keyboardShortcut("t", modifiers: [.shift, .command]) + + Divider() + Button("Close Editor") { if NSApp.target(forAction: #selector(CodeEditWindowController.closeActiveEditor(_:))) != nil { NSApp.sendAction( From 74d1c3bb8f5859d7e082bd7bf868276af2fcf123 Mon Sep 17 00:00:00 2001 From: William Laverty Date: Fri, 13 Feb 2026 01:08:38 -0800 Subject: [PATCH 2/2] fix: Auto-select name for editing when creating new files/folders When creating a new file or folder via the project navigator context menu, the name is now automatically selected for editing (matching the behavior already present in 'New File from Clipboard'). This matches standard IDE behavior where newly created items immediately enter rename mode, allowing the user to type a custom name without an extra step. Fixes #2029 --- .../OutlineView/ProjectNavigatorMenuActions.swift | 2 ++ 1 file changed, 2 insertions(+) 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)