mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-05-05 15:33:00 +00:00
macOS: extract TerminalCommandPalette
This commit is contained in:
parent
5fab6faf04
commit
0915a7af46
@ -34,9 +34,10 @@
|
||||
A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
|
||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; };
|
||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; };
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A297A2DB2E49400B6E02C /* CommandPalette.swift */; };
|
||||
A53A297F2DB4480F00B6E02C /* EventModifiers+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */; };
|
||||
A53A29812DB44A6100B6E02C /* KeyboardShortcut+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */; };
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A297A2DB2E49400B6E02C /* CommandPalette.swift */; };
|
||||
A53A29882DB69D2F00B6E02C /* TerminalCommandPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */; };
|
||||
A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; };
|
||||
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
||||
A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C932B53B43700305CE6 /* iOSApp.swift */; };
|
||||
@ -141,9 +142,10 @@
|
||||
A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = "<group>"; };
|
||||
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
|
||||
A53A297A2DB2E49400B6E02C /* CommandPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPalette.swift; sourceTree = "<group>"; };
|
||||
A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventModifiers+Extension.swift"; sourceTree = "<group>"; };
|
||||
A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyboardShortcut+Extension.swift"; sourceTree = "<group>"; };
|
||||
A53A297A2DB2E49400B6E02C /* CommandPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPalette.swift; sourceTree = "<group>"; };
|
||||
A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalCommandPalette.swift; sourceTree = "<group>"; };
|
||||
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Action.swift; sourceTree = "<group>"; };
|
||||
A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
|
||||
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = "<group>"; };
|
||||
@ -332,6 +334,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A53A297A2DB2E49400B6E02C /* CommandPalette.swift */,
|
||||
A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */,
|
||||
);
|
||||
path = "Command Palette";
|
||||
sourceTree = "<group>";
|
||||
@ -726,6 +729,7 @@
|
||||
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */,
|
||||
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */,
|
||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
||||
A53A29882DB69D2F00B6E02C /* TerminalCommandPalette.swift in Sources */,
|
||||
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */,
|
||||
|
@ -0,0 +1,77 @@
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
struct TerminalCommandPaletteView: View {
|
||||
/// The surface that this command palette represents.
|
||||
let surfaceView: Ghostty.SurfaceView
|
||||
|
||||
/// Set this to true to show the view, this will be set to false if any actions
|
||||
/// result in the view disappearing.
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
/// The configuration so we can lookup keyboard shortcuts.
|
||||
@ObservedObject var ghosttyConfig: Ghostty.Config
|
||||
|
||||
/// The callback when an action is submitted.
|
||||
var onAction: ((String) -> Void)
|
||||
|
||||
// The commands available to the command palette.
|
||||
private var commandOptions: [CommandOption] {
|
||||
guard let surface = surfaceView.surface else { return [] }
|
||||
|
||||
var ptr: UnsafeMutablePointer<ghostty_command_s>? = nil
|
||||
var count: Int = 0
|
||||
ghostty_surface_commands(surface, &ptr, &count)
|
||||
guard let ptr else { return [] }
|
||||
|
||||
let buffer = UnsafeBufferPointer(start: ptr, count: count)
|
||||
return Array(buffer).map { c in
|
||||
let action = String(cString: c.action)
|
||||
return CommandOption(
|
||||
title: String(cString: c.title),
|
||||
shortcut: ghosttyConfig.keyboardShortcut(for: action)?.description
|
||||
) {
|
||||
onAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if isPresented {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
Spacer().frame(height: geometry.size.height * 0.1)
|
||||
|
||||
CommandPaletteView(
|
||||
isPresented: $isPresented,
|
||||
backgroundColor: ghosttyConfig.backgroundColor,
|
||||
options: commandOptions
|
||||
)
|
||||
.transition(
|
||||
.move(edge: .top)
|
||||
.combined(with: .opacity)
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.8))
|
||||
) // Spring animation
|
||||
.zIndex(1) // Ensure it's on top
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: isPresented) { newValue in
|
||||
// When the command palette disappears we need to send focus back to the
|
||||
// surface view we were overlaid on top of. There's probably a better way
|
||||
// to handle the first responder state here but I don't know it.
|
||||
if !newValue {
|
||||
// Has to be on queue because onChange happens on a user-interactive
|
||||
// thread and Xcode is mad about this call on that.
|
||||
DispatchQueue.main.async {
|
||||
surfaceView.window?.makeFirstResponder(surfaceView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,9 @@ class BaseTerminalController: NSWindowController,
|
||||
didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) }
|
||||
}
|
||||
|
||||
/// This can be set to show/hide the command palette.
|
||||
@Published var commandPaletteIsShowing: Bool = false
|
||||
|
||||
/// Whether the terminal surface should focus when the mouse is over it.
|
||||
var focusFollowsMouse: Bool {
|
||||
self.derivedConfig.focusFollowsMouse
|
||||
@ -209,12 +212,12 @@ class BaseTerminalController: NSWindowController,
|
||||
// We only care if the configuration is a global configuration, not a
|
||||
// surface-specific one.
|
||||
guard notification.object == nil else { return }
|
||||
|
||||
|
||||
// Get our managed configuration object out
|
||||
guard let config = notification.userInfo?[
|
||||
Notification.Name.GhosttyConfigChangeKey
|
||||
] as? Ghostty.Config else { return }
|
||||
|
||||
|
||||
// Update our derived config
|
||||
self.derivedConfig = DerivedConfig(config)
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ protocol TerminalViewModel: ObservableObject {
|
||||
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
||||
/// and children. This should be @Published.
|
||||
var surfaceTree: Ghostty.SplitNode? { get set }
|
||||
|
||||
/// The command palette state.
|
||||
var commandPaletteIsShowing: Bool { get set }
|
||||
}
|
||||
|
||||
/// The main terminal view. This terminal view supports splits.
|
||||
@ -72,27 +75,6 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
return URL(fileURLWithPath: surfacePwd)
|
||||
}
|
||||
|
||||
// The commands available to the command palette.
|
||||
private var commandOptions: [CommandOption] {
|
||||
guard let surface = lastFocusedSurface.value?.surface else { return [] }
|
||||
|
||||
var ptr: UnsafeMutablePointer<ghostty_command_s>? = nil
|
||||
var count: Int = 0
|
||||
ghostty_surface_commands(surface, &ptr, &count)
|
||||
guard let ptr else { return [] }
|
||||
|
||||
let buffer = UnsafeBufferPointer(start: ptr, count: count)
|
||||
return Array(buffer).map { c in
|
||||
let action = String(cString: c.action)
|
||||
return CommandOption(
|
||||
title: String(cString: c.title),
|
||||
shortcut: ghostty.config.keyEquivalent(for: action)?.description
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@State var showingCommandPalette = false
|
||||
|
||||
var body: some View {
|
||||
switch ghostty.readiness {
|
||||
case .loading:
|
||||
@ -111,7 +93,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Command Palette") {
|
||||
showingCommandPalette.toggle()
|
||||
viewModel.commandPaletteIsShowing.toggle()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
@ -154,27 +136,12 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
||||
// Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style
|
||||
.ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : [])
|
||||
|
||||
if showingCommandPalette {
|
||||
// The Palette View Itself
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
Spacer().frame(height: geometry.size.height * 0.1)
|
||||
|
||||
CommandPaletteView(
|
||||
isPresented: $showingCommandPalette,
|
||||
backgroundColor: ghostty.config.backgroundColor,
|
||||
options: commandOptions
|
||||
)
|
||||
.transition(
|
||||
.move(edge: .top)
|
||||
.combined(with: .opacity)
|
||||
.animation(.spring(response: 0.4, dampingFraction: 0.8))
|
||||
) // Spring animation
|
||||
.zIndex(1) // Ensure it's on top
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .top)
|
||||
if let surfaceView = lastFocusedSurface.value {
|
||||
TerminalCommandPaletteView(
|
||||
surfaceView: surfaceView,
|
||||
isPresented: $viewModel.commandPaletteIsShowing,
|
||||
ghosttyConfig: ghostty.config) { action in
|
||||
print(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user