macos: intent to open quick terminal

pull/7634/head
Mitchell Hashimoto 2025-06-20 07:03:40 -07:00
parent f096675eaf
commit 2c1e83ba2f
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
5 changed files with 68 additions and 12 deletions

View File

@ -14,6 +14,7 @@
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; };
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; };
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; };
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
@ -151,6 +152,7 @@
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = "<group>"; };
A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = "<group>"; };
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
A51544FD2DFB1110009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarTabsTahoeTerminalWindow.swift; sourceTree = "<group>"; };
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebarTahoe.xib; sourceTree = "<group>"; };
@ -630,6 +632,7 @@
A511940E2E050590007258CC /* CloseTerminalIntent.swift */,
A5E4082D2E0237410035FEAC /* NewTerminalIntent.swift */,
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */,
A51194102E05A480007258CC /* QuickTerminalIntent.swift */,
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */,
A5E408462E0485270035FEAC /* InputIntent.swift */,
A5E408442E0483F80035FEAC /* KeybindIntent.swift */,
@ -806,6 +809,7 @@
A53A29812DB44A6100B6E02C /* KeyboardShortcut+Extension.swift in Sources */,
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */,
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */,
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */,
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */,
A5593FDF2DF8D57C00B47B10 /* TerminalWindow.swift in Sources */,
A58636712DF298FB00E04A10 /* ExpiringUndoManager.swift in Sources */,

View File

@ -92,7 +92,10 @@ class AppDelegate: NSObject,
lazy var undoManager = ExpiringUndoManager()
/// Our quick terminal. This starts out uninitialized and only initializes if used.
private var quickController: QuickTerminalController? = nil
private(set) lazy var quickController = QuickTerminalController(
ghostty,
position: derivedConfig.quickTerminalPosition
)
/// Manages updates
let updaterController: SPUStandardUpdaterController
@ -286,7 +289,7 @@ class AppDelegate: NSObject,
// NOTE(mitchellh): I don't think we need this check at all anymore. I'm keeping it
// here because I don't want to remove it in a patch release cycle but we should
// target removing it soon.
if (self.quickController == nil && windows.allSatisfy { !$0.isVisible }) {
if (windows.allSatisfy { !$0.isVisible }) {
return .terminateNow
}
@ -919,14 +922,6 @@ class AppDelegate: NSObject,
}
@IBAction func toggleQuickTerminal(_ sender: Any) {
if quickController == nil {
quickController = QuickTerminalController(
ghostty,
position: derivedConfig.quickTerminalPosition
)
}
guard let quickController = self.quickController else { return }
quickController.toggle()
}

View File

@ -11,6 +11,9 @@ struct TerminalEntity: AppEntity {
@Property(title: "Working Directory")
var workingDirectory: String?
@Property(title: "Kind")
var kind: Kind
@MainActor
@DeferredProperty(title: "Full Contents")
@available(macOS 26.0, *)
@ -67,6 +70,27 @@ struct TerminalEntity: AppEntity {
self.title = view.title
self.workingDirectory = view.pwd
self.screenshot = view.screenshot()
// Determine the kind based on the window controller type
if view.window?.windowController is QuickTerminalController {
self.kind = .quick
} else {
self.kind = .normal
}
}
}
extension TerminalEntity {
enum Kind: String, AppEnum {
case normal
case quick
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Terminal Kind")
static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [
.normal: .init(title: "Normal"),
.quick: .init(title: "Quick")
]
}
}
@ -101,7 +125,8 @@ struct TerminalQuery: EntityStringQuery, EnumerableEntityQuery {
@MainActor
var all: [Ghostty.SurfaceView] {
// Find all of our terminal windows (includes quick terminal)
// Find all of our terminal windows. This will include the quick terminal
// but only if it was previously opened.
let controllers = NSApp.windows.compactMap {
$0.windowController as? BaseTerminalController
}

View File

@ -0,0 +1,28 @@
import AppKit
import AppIntents
struct QuickTerminalIntent: AppIntent {
static var title: LocalizedStringResource = "Open the Quick Terminal"
static var description = IntentDescription("Open the Quick Terminal. If it is already open, then do nothing.")
@available(macOS 26.0, *)
static var supportedModes: IntentModes = .background
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<[TerminalEntity]> {
guard let delegate = NSApp.delegate as? AppDelegate else {
throw GhosttyIntentError.appUnavailable
}
// This is safe to call even if it is already shown.
let c = delegate.quickController
c.animateIn()
// Grab all our terminals
let terminals = c.surfaceTree.root?.leaves().map {
TerminalEntity($0)
} ?? []
return .result(value: terminals)
}
}

View File

@ -42,7 +42,11 @@ class QuickTerminalController: BaseTerminalController {
) {
self.position = position
self.derivedConfig = DerivedConfig(ghostty.config)
super.init(ghostty, baseConfig: base, surfaceTree: tree)
// Important detail here: we initialize with an empty surface tree so
// that we don't start a terminal process. This gets started when the
// first terminal is shown in `animateIn`.
super.init(ghostty, baseConfig: base, surfaceTree: .init())
// Setup our notifications for behaviors
let center = NotificationCenter.default