macos: input intent

This commit is contained in:
Mitchell Hashimoto
2025-06-19 11:07:46 -07:00
parent c904e86883
commit a6074040e7
6 changed files with 123 additions and 10 deletions

View File

@@ -129,6 +129,7 @@
A5E408402E04532C0035FEAC /* CommandEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E4083F2E04532A0035FEAC /* CommandEntity.swift */; };
A5E408432E047D0B0035FEAC /* CommandPaletteIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */; };
A5E408452E0483FD0035FEAC /* KeybindIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408442E0483F80035FEAC /* KeybindIntent.swift */; };
A5E408472E04852B0035FEAC /* InputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408462E0485270035FEAC /* InputIntent.swift */; };
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */; };
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
@@ -258,6 +259,7 @@
A5E4083F2E04532A0035FEAC /* CommandEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandEntity.swift; sourceTree = "<group>"; };
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPaletteIntent.swift; sourceTree = "<group>"; };
A5E408442E0483F80035FEAC /* KeybindIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeybindIntent.swift; sourceTree = "<group>"; };
A5E408462E0485270035FEAC /* InputIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputIntent.swift; sourceTree = "<group>"; };
A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extension.swift"; sourceTree = "<group>"; };
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = "<group>"; };
@@ -626,6 +628,7 @@
A5E4082D2E0237410035FEAC /* NewTerminalIntent.swift */,
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */,
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */,
A5E408462E0485270035FEAC /* InputIntent.swift */,
A5E408442E0483F80035FEAC /* KeybindIntent.swift */,
A5E4082F2E0271320035FEAC /* GhosttyIntentError.swift */,
);
@@ -819,6 +822,7 @@
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */,
A5874D992DAD751B00E83852 /* CGS.swift in Sources */,
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */,
A5E408472E04852B0035FEAC /* InputIntent.swift in Sources */,
A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */,
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,

View File

@@ -0,0 +1,92 @@
import AppKit
import AppIntents
/// App intent to input text in a terminal.
struct InputTextIntent: AppIntent {
static var title: LocalizedStringResource = "Input Text to Terminal"
@Parameter(
title: "Text",
description: "The text to input to the terminal. The text will be inputted as if it was pasted.",
inputOptions: String.IntentInputOptions(
capitalizationType: .none,
multiline: true,
autocorrect: false,
smartQuotes: false,
smartDashes: false
)
)
var text: String
@Parameter(
title: "Terminal",
description: "The terminal to scope this action to."
)
var terminal: TerminalEntity
@available(macOS 26.0, *)
static var supportedModes: IntentModes = [.background, .foreground]
@MainActor
func perform() async throws -> some IntentResult {
guard let surface = terminal.surfaceModel else {
throw GhosttyIntentError.surfaceNotFound
}
surface.sendText(text)
return .result()
}
}
/// App intent to trigger a keyboard event.
struct KeyEventIntent: AppIntent {
static var title: LocalizedStringResource = "Send Keyboard Event to Terminal"
static var description = IntentDescription("Simulate a keyboard event. This will not handle text encoding; use the 'Input Text' action for that.")
@Parameter(
title: "Text",
description: "The key to send to the terminal."
)
var key: KeyIntentKey
@Parameter(
title: "Terminal",
description: "The terminal to scope this action to."
)
var terminal: TerminalEntity
@available(macOS 26.0, *)
static var supportedModes: IntentModes = [.background, .foreground]
@MainActor
func perform() async throws -> some IntentResult {
guard let surface = terminal.surfaceModel else {
throw GhosttyIntentError.surfaceNotFound
}
surface.sendText(text)
return .result()
}
}
// MARK: TerminalDetail
enum KeyIntentKey: String {
case title
case workingDirectory
case allContents
case selectedText
case visibleText
}
extension KeyIntentKey: AppEnum {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Terminal Detail")
static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [
.title: .init(title: "Title"),
.workingDirectory: .init(title: "Working Directory"),
.allContents: .init(title: "Full Contents"),
.selectedText: .init(title: "Selected Text"),
.visibleText: .init(title: "Visible Text"),
]
}

View File

@@ -1,13 +1,12 @@
import AppKit
import AppIntents
/// App intent that invokes a command palette entry.
struct KeybindIntent: AppIntent {
static var title: LocalizedStringResource = "Invoke a Keybind Action"
@Parameter(
title: "Terminal",
description: "The terminal to base available commands from."
description: "The terminal to invoke the action on."
)
var terminal: TerminalEntity

View File

@@ -208,4 +208,15 @@ extension Ghostty {
0x43: GHOSTTY_KEY_NUMPAD_MULTIPLY,
0x4E: GHOSTTY_KEY_NUMPAD_SUBTRACT,
];
/// `ghostty_input_key_e`
enum Key: String {
case undentified
var cKey: ghostty_input_key_e {
switch self {
case .undentified: GHOSTTY_KEY_UNIDENTIFIED
}
}
}
}

View File

@@ -35,6 +35,19 @@ extension Ghostty {
}
}
/// Send text to the terminal as if it was typed. This doesn't send the key events so keyboard
/// shortcuts and other encodings do not take effect.
@MainActor
func sendText(_ text: String) {
let len = text.utf8CString.count
if (len == 0) { return }
text.withCString { ptr in
// len includes the null terminator so we do len - 1
ghostty_surface_text(surface, ptr, UInt(len - 1))
}
}
/// Perform a keybinding action.
///
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`

View File

@@ -1700,7 +1700,7 @@ extension Ghostty.SurfaceView: NSTextInputClient {
func insertText(_ string: Any, replacementRange: NSRange) {
// We must have an associated event
guard NSApp.currentEvent != nil else { return }
guard let surface = self.surface else { return }
guard let surfaceModel else { return }
// We want the string view of the any value
var chars = ""
@@ -1724,13 +1724,7 @@ extension Ghostty.SurfaceView: NSTextInputClient {
return
}
let len = chars.utf8CString.count
if (len == 0) { return }
chars.withCString { ptr in
// len includes the null terminator so we do len - 1
ghostty_surface_text(surface, ptr, UInt(len - 1))
}
surfaceModel.sendText(chars)
}
/// This function needs to exist for two reasons: