Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Sources/App/KeyboardShortcutSettings+TerminalDirectoryOpen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
enum TerminalDirectoryOpenShortcutBindings {
typealias Binding = (target: TerminalDirectoryOpenTarget, action: KeyboardShortcutSettings.Action)

static let all: [Binding] = TerminalDirectoryOpenTarget.commandPaletteShortcutTargets.map { target in
(target, KeyboardShortcutSettings.Action.terminalDirectoryOpenAction(for: target))
}

static let actions: [KeyboardShortcutSettings.Action] = all.map { $0.action }
}

extension KeyboardShortcutSettings.Action {
static var terminalDirectoryOpenActions: [Self] {
TerminalDirectoryOpenShortcutBindings.actions
}

static var terminalDirectoryOpenShortcutBindings: [TerminalDirectoryOpenShortcutBindings.Binding] {
TerminalDirectoryOpenShortcutBindings.all
}

static func terminalDirectoryOpenAction(for target: TerminalDirectoryOpenTarget) -> Self {
switch target {
case .androidStudio:
return .terminalOpenDirectoryAndroidStudio
case .antigravity:
return .terminalOpenDirectoryAntigravity
case .cursor:
return .terminalOpenDirectoryCursor
case .finder:
return .terminalOpenDirectoryFinder
case .ghostty:
return .terminalOpenDirectoryGhostty
case .intellij:
return .terminalOpenDirectoryIntelliJ
case .iterm2:
return .terminalOpenDirectoryITerm2
case .terminal:
return .terminalOpenDirectoryTerminal
case .tower:
return .terminalOpenDirectoryTower
case .vscode:
return .terminalOpenDirectoryVSCode
case .vscodeInline:
return .terminalOpenDirectoryVSCodeInline
case .warp:
return .terminalOpenDirectoryWarp
case .windsurf:
return .terminalOpenDirectoryWindsurf
case .xcode:
return .terminalOpenDirectoryXcode
case .zed:
return .terminalOpenDirectoryZed
}
}

var terminalDirectoryOpenTarget: TerminalDirectoryOpenTarget? {
TerminalDirectoryOpenShortcutBindings.all.first { $0.action == self }?.target
}
}
85 changes: 85 additions & 0 deletions Sources/App/TerminalDirectoryOpenLauncher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import AppKit
import Foundation

@MainActor
enum TerminalDirectoryOpenLauncher {
typealias ApplicationOpenCompletion = @MainActor (Error?) -> Void
typealias ApplicationOpenHandler = (
_ urls: [URL],
_ applicationURL: URL,
_ configuration: NSWorkspace.OpenConfiguration,
_ completion: @escaping ApplicationOpenCompletion
) -> Void

static func currentDirectoryURL(in tabManager: TabManager?) -> URL? {
guard let workspace = tabManager?.selectedWorkspace else { return nil }
let focusedPanelDirectory = workspace.focusedPanelId.flatMap { workspace.panelDirectories[$0] }
return firstValidDirectoryURL(in: [focusedPanelDirectory, workspace.currentDirectory])
}

static func firstValidDirectoryURL(in rawDirectories: [String?]) -> URL? {
for rawDirectory in rawDirectories {
let trimmed = rawDirectory?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty else { continue }
var isDirectory: ObjCBool = false
guard FileManager.default.fileExists(atPath: trimmed, isDirectory: &isDirectory),
isDirectory.boolValue else {
continue
}
return URL(fileURLWithPath: trimmed, isDirectory: true)
}
return nil
}

static func openCurrentDirectory(
in target: TerminalDirectoryOpenTarget,
tabManager: TabManager?,
onOpenFailure: ApplicationOpenCompletion? = nil
) -> Bool {
guard let directoryURL = currentDirectoryURL(in: tabManager) else { return false }
return openDirectory(
directoryURL,
in: target,
tabManager: tabManager,
onOpenFailure: onOpenFailure
)
}

static func openDirectory(
_ directoryURL: URL,
in target: TerminalDirectoryOpenTarget,
tabManager: TabManager?,
onOpenFailure: ApplicationOpenCompletion? = nil,
applicationURLProvider: (TerminalDirectoryOpenTarget) -> URL? = { $0.applicationURL() },
openWithApplication: ApplicationOpenHandler? = nil
) -> Bool {
switch target {
case .finder:
return NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: directoryURL.path)
case .vscodeInline:
return AppDelegate.shared?.openDirectoryInInlineVSCode(directoryURL, tabManager: tabManager) ?? false
default:
guard let applicationURL = applicationURLProvider(target) else { return false }
let configuration = NSWorkspace.OpenConfiguration()
let openWithApplication = openWithApplication ?? openURLsWithApplication
openWithApplication([directoryURL], applicationURL, configuration) { error in
guard error != nil else { return }
onOpenFailure?(error)
}
return true
}
}

private static func openURLsWithApplication(
_ urls: [URL],
applicationURL: URL,
configuration: NSWorkspace.OpenConfiguration,
completion: @escaping ApplicationOpenCompletion
) {
NSWorkspace.shared.open(urls, withApplicationAt: applicationURL, configuration: configuration) { _, error in
Task { @MainActor in
completion(error)
}
}
}
}
6 changes: 6 additions & 0 deletions Sources/App/TerminalDirectoryOpenSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable {
Array(allCases)
}

init?(commandPaletteCommandId: String) {
let prefix = "palette.terminalOpenDirectory."
guard commandPaletteCommandId.hasPrefix(prefix) else { return nil }
self.init(rawValue: String(commandPaletteCommandId.dropFirst(prefix.count)))
}

static func availableTargets(in environment: DetectionEnvironment = .live) -> Set<Self> {
Set(commandPaletteShortcutTargets.filter { $0.isAvailable(in: environment) })
}
Expand Down
32 changes: 32 additions & 0 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11940,6 +11940,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return true
}

if handleTerminalDirectoryOpenShortcut(event: event) {
return true
}

if handleConfiguredCmuxShortcut(
event: event,
actions: configuredCmuxShortcutActions,
Expand Down Expand Up @@ -13632,6 +13636,34 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
)
}

private func handleTerminalDirectoryOpenShortcut(event: NSEvent) -> Bool {
if activeConfiguredShortcutChordPrefixForCurrentEvent == nil,
armConfiguredShortcutChordIfNeeded(
event: event,
actions: KeyboardShortcutSettings.Action.terminalDirectoryOpenActions
) {
return true
}

for binding in KeyboardShortcutSettings.Action.terminalDirectoryOpenShortcutBindings {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
guard matchConfiguredShortcut(event: event, action: binding.action) else {
continue
}
let targetTabManager = preferredMainWindowContextForShortcutRouting(event: event)?.tabManager ?? tabManager
if !TerminalDirectoryOpenLauncher.openCurrentDirectory(
in: binding.target,
tabManager: targetTabManager,
onOpenFailure: { _ in
NSSound.beep()
}
) {
NSSound.beep()
}
return true
}
return false
}

@discardableResult
private func executeConfiguredCmuxAction(
_ action: CmuxResolvedConfigAction,
Expand Down
4 changes: 4 additions & 0 deletions Sources/ContentView+RightSidebarCommandPalette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import AppKit

extension ContentView {
static func commandPaletteShortcutAction(forCommandID commandId: String) -> KeyboardShortcutSettings.Action? {
if let target = TerminalDirectoryOpenTarget(commandPaletteCommandId: commandId) {
return KeyboardShortcutSettings.Action.terminalDirectoryOpenAction(for: target)
}

if let rightSidebarModeAction = commandPaletteRightSidebarModeShortcutAction(forCommandID: commandId) {
return rightSidebarModeAction
}
Expand Down
43 changes: 7 additions & 36 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9157,27 +9157,13 @@ struct ContentView: View {
}

private func openFocusedDirectory(in target: TerminalDirectoryOpenTarget) -> Bool {
guard let directoryURL = focusedTerminalDirectoryURL() else { return false }
return openFocusedDirectory(directoryURL, in: target)
}

private func openFocusedDirectory(_ directoryURL: URL, in target: TerminalDirectoryOpenTarget) -> Bool {
switch target {
case .finder:
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: directoryURL.path)
return true
case .vscodeInline:
return openFocusedDirectoryInInlineVSCode(directoryURL)
default:
guard let applicationURL = target.applicationURL() else { return false }
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.open([directoryURL], withApplicationAt: applicationURL, configuration: configuration)
return true
}
}

private func openFocusedDirectoryInInlineVSCode(_ directoryURL: URL) -> Bool {
AppDelegate.shared?.openDirectoryInInlineVSCode(directoryURL, tabManager: tabManager) ?? false
TerminalDirectoryOpenLauncher.openCurrentDirectory(
in: target,
tabManager: tabManager,
onOpenFailure: { _ in
NSSound.beep()
}
)
}

private func stopInlineVSCodeServeWeb() {
Expand All @@ -9196,21 +9182,6 @@ struct ContentView: View {
return true
}

private func focusedTerminalDirectoryURL() -> URL? {
guard let workspace = tabManager.selectedWorkspace else { return nil }
let rawDirectory: String = {
if let focusedPanelId = workspace.focusedPanelId,
let directory = workspace.panelDirectories[focusedPanelId] {
return directory
}
return workspace.currentDirectory
}()
let trimmed = rawDirectory.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
guard FileManager.default.fileExists(atPath: trimmed) else { return nil }
return URL(fileURLWithPath: trimmed, isDirectory: true)
}

#if DEBUG
private func debugShortWorkspaceId(_ id: UUID?) -> String {
guard let id else { return "nil" }
Expand Down
46 changes: 46 additions & 0 deletions Sources/KeyboardShortcutSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ enum KeyboardShortcutSettings {
case toggleSidebar
case newTab
case openFolder
case terminalOpenDirectoryAndroidStudio = "palette.terminalOpenDirectory.androidStudio"
case terminalOpenDirectoryAntigravity = "palette.terminalOpenDirectory.antigravity"
case terminalOpenDirectoryCursor = "palette.terminalOpenDirectory.cursor"
case terminalOpenDirectoryFinder = "palette.terminalOpenDirectory.finder"
case terminalOpenDirectoryGhostty = "palette.terminalOpenDirectory.ghostty"
case terminalOpenDirectoryIntelliJ = "palette.terminalOpenDirectory.intellij"
case terminalOpenDirectoryITerm2 = "palette.terminalOpenDirectory.iterm2"
case terminalOpenDirectoryTerminal = "palette.terminalOpenDirectory.terminal"
case terminalOpenDirectoryTower = "palette.terminalOpenDirectory.tower"
case terminalOpenDirectoryVSCode = "palette.terminalOpenDirectory.vscode"
case terminalOpenDirectoryVSCodeInline = "palette.terminalOpenDirectory.vscodeInline"
case terminalOpenDirectoryWarp = "palette.terminalOpenDirectory.warp"
case terminalOpenDirectoryWindsurf = "palette.terminalOpenDirectory.windsurf"
case terminalOpenDirectoryXcode = "palette.terminalOpenDirectory.xcode"
case terminalOpenDirectoryZed = "palette.terminalOpenDirectory.zed"
case reopenPreviousSession
case goToWorkspace
case commandPalette
Expand Down Expand Up @@ -156,6 +171,21 @@ enum KeyboardShortcutSettings {
case .toggleSidebar: return String(localized: "shortcut.toggleLeftSidebar.label", defaultValue: "Toggle Left Sidebar")
case .newTab: return String(localized: "shortcut.newWorkspace.label", defaultValue: "New Workspace")
case .openFolder: return String(localized: "shortcut.openFolder.label", defaultValue: "Open Folder")
case .terminalOpenDirectoryAndroidStudio: return TerminalDirectoryOpenTarget.androidStudio.commandPaletteTitle
case .terminalOpenDirectoryAntigravity: return TerminalDirectoryOpenTarget.antigravity.commandPaletteTitle
case .terminalOpenDirectoryCursor: return TerminalDirectoryOpenTarget.cursor.commandPaletteTitle
case .terminalOpenDirectoryFinder: return TerminalDirectoryOpenTarget.finder.commandPaletteTitle
case .terminalOpenDirectoryGhostty: return TerminalDirectoryOpenTarget.ghostty.commandPaletteTitle
case .terminalOpenDirectoryIntelliJ: return TerminalDirectoryOpenTarget.intellij.commandPaletteTitle
case .terminalOpenDirectoryITerm2: return TerminalDirectoryOpenTarget.iterm2.commandPaletteTitle
case .terminalOpenDirectoryTerminal: return TerminalDirectoryOpenTarget.terminal.commandPaletteTitle
case .terminalOpenDirectoryTower: return TerminalDirectoryOpenTarget.tower.commandPaletteTitle
case .terminalOpenDirectoryVSCode: return TerminalDirectoryOpenTarget.vscode.commandPaletteTitle
case .terminalOpenDirectoryVSCodeInline: return TerminalDirectoryOpenTarget.vscodeInline.commandPaletteTitle
case .terminalOpenDirectoryWarp: return TerminalDirectoryOpenTarget.warp.commandPaletteTitle
case .terminalOpenDirectoryWindsurf: return TerminalDirectoryOpenTarget.windsurf.commandPaletteTitle
case .terminalOpenDirectoryXcode: return TerminalDirectoryOpenTarget.xcode.commandPaletteTitle
case .terminalOpenDirectoryZed: return TerminalDirectoryOpenTarget.zed.commandPaletteTitle
case .reopenPreviousSession: return String(localized: "shortcut.reopenPreviousSession.label", defaultValue: "Reopen Previous Session")
case .goToWorkspace: return String(localized: "menu.file.goToWorkspace", defaultValue: "Go to Workspace…")
case .commandPalette: return String(localized: "menu.file.commandPalette", defaultValue: "Command Palette…")
Expand Down Expand Up @@ -264,6 +294,22 @@ enum KeyboardShortcutSettings {
return StoredShortcut(key: "n", command: true, shift: false, option: false, control: false)
case .openFolder:
return StoredShortcut(key: "o", command: true, shift: false, option: false, control: false)
case .terminalOpenDirectoryAndroidStudio,
.terminalOpenDirectoryAntigravity,
.terminalOpenDirectoryCursor,
.terminalOpenDirectoryFinder,
.terminalOpenDirectoryGhostty,
.terminalOpenDirectoryIntelliJ,
.terminalOpenDirectoryITerm2,
.terminalOpenDirectoryTerminal,
.terminalOpenDirectoryTower,
.terminalOpenDirectoryVSCode,
.terminalOpenDirectoryVSCodeInline,
.terminalOpenDirectoryWarp,
.terminalOpenDirectoryWindsurf,
.terminalOpenDirectoryXcode,
.terminalOpenDirectoryZed:
return .unbound
case .reopenPreviousSession:
return StoredShortcut(key: "o", command: true, shift: true, option: false, control: false)
case .goToWorkspace:
Expand Down
Loading
Loading