mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-05-05 23:43: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 */; };
|
A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
|
||||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; };
|
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; };
|
||||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.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 */; };
|
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 */; };
|
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 */; };
|
A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; };
|
||||||
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
||||||
A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C932B53B43700305CE6 /* iOSApp.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = "<group>"; };
|
||||||
@ -332,6 +334,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A53A297A2DB2E49400B6E02C /* CommandPalette.swift */,
|
A53A297A2DB2E49400B6E02C /* CommandPalette.swift */,
|
||||||
|
A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */,
|
||||||
);
|
);
|
||||||
path = "Command Palette";
|
path = "Command Palette";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -726,6 +729,7 @@
|
|||||||
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */,
|
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */,
|
||||||
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */,
|
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */,
|
||||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
||||||
|
A53A29882DB69D2F00B6E02C /* TerminalCommandPalette.swift in Sources */,
|
||||||
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
||||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
||||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.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) }
|
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.
|
/// Whether the terminal surface should focus when the mouse is over it.
|
||||||
var focusFollowsMouse: Bool {
|
var focusFollowsMouse: Bool {
|
||||||
self.derivedConfig.focusFollowsMouse
|
self.derivedConfig.focusFollowsMouse
|
||||||
@ -209,12 +212,12 @@ class BaseTerminalController: NSWindowController,
|
|||||||
// We only care if the configuration is a global configuration, not a
|
// We only care if the configuration is a global configuration, not a
|
||||||
// surface-specific one.
|
// surface-specific one.
|
||||||
guard notification.object == nil else { return }
|
guard notification.object == nil else { return }
|
||||||
|
|
||||||
// Get our managed configuration object out
|
// Get our managed configuration object out
|
||||||
guard let config = notification.userInfo?[
|
guard let config = notification.userInfo?[
|
||||||
Notification.Name.GhosttyConfigChangeKey
|
Notification.Name.GhosttyConfigChangeKey
|
||||||
] as? Ghostty.Config else { return }
|
] as? Ghostty.Config else { return }
|
||||||
|
|
||||||
// Update our derived config
|
// Update our derived config
|
||||||
self.derivedConfig = DerivedConfig(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
|
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
||||||
/// and children. This should be @Published.
|
/// and children. This should be @Published.
|
||||||
var surfaceTree: Ghostty.SplitNode? { get set }
|
var surfaceTree: Ghostty.SplitNode? { get set }
|
||||||
|
|
||||||
|
/// The command palette state.
|
||||||
|
var commandPaletteIsShowing: Bool { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main terminal view. This terminal view supports splits.
|
/// The main terminal view. This terminal view supports splits.
|
||||||
@ -72,27 +75,6 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
return URL(fileURLWithPath: surfacePwd)
|
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 {
|
var body: some View {
|
||||||
switch ghostty.readiness {
|
switch ghostty.readiness {
|
||||||
case .loading:
|
case .loading:
|
||||||
@ -111,7 +93,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Command Palette") {
|
Button("Command Palette") {
|
||||||
showingCommandPalette.toggle()
|
viewModel.commandPaletteIsShowing.toggle()
|
||||||
}
|
}
|
||||||
Spacer()
|
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
|
// 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 : [])
|
.ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : [])
|
||||||
|
|
||||||
if showingCommandPalette {
|
if let surfaceView = lastFocusedSurface.value {
|
||||||
// The Palette View Itself
|
TerminalCommandPaletteView(
|
||||||
GeometryReader { geometry in
|
surfaceView: surfaceView,
|
||||||
VStack {
|
isPresented: $viewModel.commandPaletteIsShowing,
|
||||||
Spacer().frame(height: geometry.size.height * 0.1)
|
ghosttyConfig: ghostty.config) { action in
|
||||||
|
print(action)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user