mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-28 05:58:36 +00:00
macos: input intent
This commit is contained in:
@@ -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 */,
|
||||
|
92
macos/Sources/Features/App Intents/InputIntent.swift
Normal file
92
macos/Sources/Features/App Intents/InputIntent.swift
Normal 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"),
|
||||
]
|
||||
}
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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`
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user