diff --git a/ora/Common/Constants/KeyboardShortcuts.swift b/ora/Common/Constants/KeyboardShortcuts.swift index 9411ee96..6dc3019d 100644 --- a/ora/Common/Constants/KeyboardShortcuts.swift +++ b/ora/Common/Constants/KeyboardShortcuts.swift @@ -15,6 +15,8 @@ enum KeyboardShortcuts { static let reopenClosed = KeyboardShortcut("t", modifiers: [.command, .shift]) static let next = KeyboardShortcut(.tab, modifiers: [.control]) static let previous = KeyboardShortcut(.tab, modifiers: [.control, .shift]) + static let nextAlt = KeyboardShortcut("]", modifiers: [.command, .shift]) + static let previousAlt = KeyboardShortcut("[", modifiers: [.command, .shift]) static let moveRight = KeyboardShortcut(.rightArrow, modifiers: [.option, .command]) static let moveLeft = KeyboardShortcut(.leftArrow, modifiers: [.option, .command]) static let pin = KeyboardShortcut("d", modifiers: [.command]) @@ -89,6 +91,8 @@ extension KeyboardShortcuts { .init(category: "Tabs", name: "Reopen Closed", display: "⇧⌘T"), .init(category: "Tabs", name: "Next Tab", display: "^⇥"), .init(category: "Tabs", name: "Previous Tab", display: "^⇧⇥"), + .init(category: "Tabs", name: "Next Tab (Alt)", display: "⇧⌘]"), + .init(category: "Tabs", name: "Previous Tab (Alt)", display: "⇧⌘["), .init(category: "Tabs", name: "Move Tab Right", display: "⌥⌘→"), .init(category: "Tabs", name: "Move Tab Left", display: "⌥⌘←"), .init(category: "Tabs", name: "Pin Tab", display: "⌘D"), diff --git a/ora/Services/TabManager.swift b/ora/Services/TabManager.swift index 4e8dedd4..d74ef7d0 100644 --- a/ora/Services/TabManager.swift +++ b/ora/Services/TabManager.swift @@ -346,6 +346,48 @@ class TabManager: ObservableObject { try? modelContext.save() } + func switchToNextTab() { + guard let container = activeContainer else { return } + + // Get all available tabs, preferring ready ones but falling back to all + let readyTabs = container.tabs.filter(\.isWebViewReady) + let tabs = readyTabs.isEmpty ? Array(container.tabs) : readyTabs + + guard tabs.count > 1, let currentTab = activeTab else { return } + + // Sort by order for consistent navigation + let sortedTabs = tabs.sorted { $0.order < $1.order } + + if let currentIndex = sortedTabs.firstIndex(where: { $0.id == currentTab.id }) { + let nextIndex = (currentIndex + 1) % sortedTabs.count + activateTab(sortedTabs[nextIndex]) + } else { + // Current tab not found, activate first tab + activateTab(sortedTabs[0]) + } + } + + func switchToPreviousTab() { + guard let container = activeContainer else { return } + + // Get all available tabs, preferring ready ones but falling back to all + let readyTabs = container.tabs.filter(\.isWebViewReady) + let tabs = readyTabs.isEmpty ? Array(container.tabs) : readyTabs + + guard tabs.count > 1, let currentTab = activeTab else { return } + + // Sort by order for consistent navigation + let sortedTabs = tabs.sorted { $0.order < $1.order } + + if let currentIndex = sortedTabs.firstIndex(where: { $0.id == currentTab.id }) { + let previousIndex = (currentIndex - 1 + sortedTabs.count) % sortedTabs.count + activateTab(sortedTabs[previousIndex]) + } else { + // Current tab not found, activate last tab + activateTab(sortedTabs[sortedTabs.count - 1]) + } + } + private func fetchContainers() -> [TabContainer] { do { let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.lastAccessedAt, order: .reverse)]) diff --git a/ora/oraApp.swift b/ora/oraApp.swift index b46a5c5d..d20e8512 100644 --- a/ora/oraApp.swift +++ b/ora/oraApp.swift @@ -101,8 +101,32 @@ struct OraApp: App { if event.keyCode == 48 { // Tab key if event.modifierFlags.contains(.control) { + if event.modifierFlags.contains(.shift) { + // Ctrl+Shift+Tab - Previous tab + DispatchQueue.main.async { + tabManager.switchToPreviousTab() + } + } else { + // Ctrl+Tab - Next tab + DispatchQueue.main.async { + tabManager.switchToNextTab() + } + } + return true + } + } else if event.keyCode == 33 { // ] key + if event.modifierFlags.contains([.command, .shift]) { + // Cmd+Shift+] - Next tab DispatchQueue.main.async { - appState.isFloatingTabSwitchVisible = true + tabManager.switchToNextTab() + } + return true + } + } else if event.keyCode == 30 { // [ key + if event.modifierFlags.contains([.command, .shift]) { + // Cmd+Shift+[ - Previous tab + DispatchQueue.main.async { + tabManager.switchToPreviousTab() } return true } @@ -222,12 +246,27 @@ struct OraApp: App { Divider() Button("Next Tab") { - appState.isFloatingTabSwitchVisible = true + tabManager.switchToNextTab() } + .keyboardShortcut(KeyboardShortcuts.Tabs.next) Button("Previous Tab") { - appState.isFloatingTabSwitchVisible = true + tabManager.switchToPreviousTab() + } + .keyboardShortcut(KeyboardShortcuts.Tabs.previous) + + // Alternative shortcuts for tab switching + Button("") { + tabManager.switchToNextTab() + } + .keyboardShortcut(KeyboardShortcuts.Tabs.nextAlt) + .hidden() + + Button("") { + tabManager.switchToPreviousTab() } + .keyboardShortcut(KeyboardShortcuts.Tabs.previousAlt) + .hidden() } } Settings {