Skip to content
Open
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
10 changes: 7 additions & 3 deletions SupacodeSettingsShared/App/AppShortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftUI

// Compile-time checkable shortcut identifier.
public nonisolated enum AppShortcutID: Codable, Hashable, Sendable, CodingKeyRepresentable {
case commandPalette, openSettings, checkForUpdates, showMainWindow
case commandPalette, projectSwitcher, openSettings, checkForUpdates, showMainWindow
case toggleLeftSidebar, revealInSidebar
case newWorktree, refreshWorktrees, archivedWorktrees, archiveWorktree
case deleteWorktree, confirmWorktreeAction
Expand Down Expand Up @@ -36,6 +36,7 @@ public nonisolated enum AppShortcutID: Codable, Hashable, Sendable, CodingKeyRep
private var stableKey: String {
switch self {
case .commandPalette: "commandPalette"
case .projectSwitcher: "projectSwitcher"
case .openSettings: "openSettings"
case .checkForUpdates: "checkForUpdates"
case .showMainWindow: "showMainWindow"
Expand Down Expand Up @@ -65,6 +66,7 @@ public nonisolated enum AppShortcutID: Codable, Hashable, Sendable, CodingKeyRep

private static let stableKeyMap: [String: AppShortcutID] = [
"commandPalette": .commandPalette,
"projectSwitcher": .projectSwitcher,
"openSettings": .openSettings,
"checkForUpdates": .checkForUpdates,
"showMainWindow": .showMainWindow,
Expand Down Expand Up @@ -106,6 +108,7 @@ public nonisolated enum AppShortcutID: Codable, Hashable, Sendable, CodingKeyRep
public var displayName: String {
switch self {
case .commandPalette: "Command Palette"
case .projectSwitcher: "Project Switcher"
case .openSettings: "Open Settings"
case .checkForUpdates: "Check For Updates"
case .showMainWindow: "Show Main Window"
Expand Down Expand Up @@ -286,7 +289,8 @@ public enum AppShortcuts {

// MARK: - Shortcut definitions.

public static let commandPalette = AppShortcut(id: .commandPalette, key: "p", modifiers: .command)
public static let commandPalette = AppShortcut(id: .commandPalette, key: "p", modifiers: [.command, .shift])
public static let projectSwitcher = AppShortcut(id: .projectSwitcher, key: "p", modifiers: .command)
public static let openSettings = AppShortcut(id: .openSettings, key: ",", modifiers: .command)
public static let checkForUpdates = AppShortcut(id: .checkForUpdates, key: "u", modifiers: .command)
public static let showMainWindow = AppShortcut(id: .showMainWindow, key: "0", modifiers: .command)
Expand Down Expand Up @@ -378,7 +382,7 @@ public enum AppShortcuts {
public static let groups: [AppShortcutGroup] = [
AppShortcutGroup(
category: .general,
shortcuts: [commandPalette, openSettings, checkForUpdates, showMainWindow]
shortcuts: [projectSwitcher, commandPalette, openSettings, checkForUpdates, showMainWindow]
),
AppShortcutGroup(category: .sidebar, shortcuts: [toggleLeftSidebar, revealInSidebar]),
AppShortcutGroup(
Expand Down
3 changes: 2 additions & 1 deletion supacode/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ private struct CommandPaletteOverlayHost: View {
#endif
return CommandPaletteOverlayView(
store: store.scope(state: \.commandPalette, action: \.commandPalette),
items: CommandPaletteFeature.commandPaletteItems(
items: CommandPaletteFeature.items(
in: store.commandPalette.mode,
from: repositoriesStore.state,
ghosttyCommands: ghosttyShortcuts.commandPaletteEntries,
scripts: store.allScripts,
Expand Down
7 changes: 6 additions & 1 deletion supacode/App/supacodeApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,13 @@ struct SupacodeApp: App {
TerminalCommands(ghosttyShortcuts: ghosttyShortcuts)
WindowCommands(ghosttyShortcuts: ghosttyShortcuts)
CommandGroup(after: .textEditing) {
Button("Project Switcher") {
store.send(.commandPalette(.presentInMode(.projectSwitcher)))
}
.appKeyboardShortcut(AppShortcuts.projectSwitcher.effective(from: store.settings.shortcutOverrides))
.help("Switch between projects, sorted by most recently used")
Button("Command Palette") {
store.send(.commandPalette(.togglePresented))
store.send(.commandPalette(.presentInMode(.commands)))
}
.appKeyboardShortcut(AppShortcuts.commandPalette.effective(from: store.settings.shortcutOverrides))
.help("Command Palette")
Expand Down
34 changes: 33 additions & 1 deletion supacode/Features/App/Reducer/AppFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,39 @@ struct AppFeature {
return .none

case .commandPalette(.delegate(.selectWorktree(let worktreeID))):
return .send(.repositories(.selectWorktree(worktreeID)))
// Always-focused-terminal: palette completion lands focus in the
// chosen worktree's terminal, matching the menu/deeplink paths
// that already passed focusTerminal: true.
return .send(.repositories(.selectWorktree(worktreeID, focusTerminal: true)))

case .commandPalette(.delegate(.selectProject(let repositoryID))):
// Resolve project → last-used worktree, falling back to the
// project's main worktree when the MRU pointer is empty or
// points at a since-removed worktree. The selectWorktree call
// carries focusTerminal: true (the default) so activation
// lands the user in the terminal.
let repositories = state.repositories
let mruWorktree =
repositories.lastWorktreeByProject[repositoryID]
.flatMap { repositories.worktreeExists($0) ? $0 : nil }
let resolved: Worktree.ID? =
mruWorktree
?? repositories.repositories[id: repositoryID]?
.worktrees.first(where: repositories.isMainWorktree)?.id
?? repositories.repositories[id: repositoryID]?.worktrees.first?.id
guard let resolved else { return .none }
return .send(.repositories(.selectWorktree(resolved)))

case .commandPalette(.delegate(.dismissedWithoutSelection)):
// Always-focused-terminal invariant. Cancellation paths (Esc, outside
// tap, programmatic close) don't carry a destination; refocus the
// current worktree's terminal so the cursor never lingers nowhere.
guard let worktreeID = state.repositories.selectedWorktreeID,
state.repositories.sidebarItems[id: worktreeID] != nil
else { return .none }
return .send(
.repositories(.sidebarItems(.element(id: worktreeID, action: .focusTerminalRequested)))
)

case .commandPalette(.delegate(.checkForUpdates)):
return .send(.updates(.checkForUpdates))
Expand Down
15 changes: 14 additions & 1 deletion supacode/Features/CommandPalette/CommandPaletteItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,34 @@ struct CommandPaletteItem: Identifiable, Equatable {
let subtitle: String?
let kind: Kind
let priorityTier: Int
/// `true` for the project-switcher row that represents the project the
/// user is already in. The switcher still renders it (so you can see
/// where you are), but the overlay skips it for the default selection so
/// Cmd+P then Enter lands on the previous project instead of being a
/// no-op. Always `false` outside the project switcher.
let isCurrentProject: Bool

init(
id: String,
title: String,
subtitle: String?,
kind: Kind,
priorityTier: Int = defaultPriorityTier
priorityTier: Int = defaultPriorityTier,
isCurrentProject: Bool = false
) {
self.id = id
self.title = title
self.subtitle = subtitle
self.kind = kind
self.priorityTier = priorityTier
self.isCurrentProject = isCurrentProject
}

enum Kind: Equatable {
case checkForUpdates
case openRepository
case worktreeSelect(Worktree.ID)
case selectProject(Repository.ID)
case openSettings
case newWorktree
case removeWorktree(Worktree.ID, Repository.ID)
Expand Down Expand Up @@ -70,6 +79,8 @@ struct CommandPaletteItem: Identifiable, Equatable {
true
case .worktreeSelect, .removeWorktree, .archiveWorktree:
false
case .selectProject:
true
case .renameBranch:
true
case .runScript, .stopScript:
Expand Down Expand Up @@ -97,6 +108,7 @@ struct CommandPaletteItem: Identifiable, Equatable {
.rerunFailedJobs,
.openFailingCheckDetails,
.worktreeSelect,
.selectProject,
.removeWorktree,
.archiveWorktree,
.renameBranch:
Expand Down Expand Up @@ -128,6 +140,7 @@ struct CommandPaletteItem: Identifiable, Equatable {
.rerunFailedJobs,
.openFailingCheckDetails,
.worktreeSelect,
.selectProject,
.removeWorktree,
.archiveWorktree,
.renameBranch,
Expand Down
Loading