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 */; };
|
A5E408402E04532C0035FEAC /* CommandEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E4083F2E04532A0035FEAC /* CommandEntity.swift */; };
|
||||||
A5E408432E047D0B0035FEAC /* CommandPaletteIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */; };
|
A5E408432E047D0B0035FEAC /* CommandPaletteIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */; };
|
||||||
A5E408452E0483FD0035FEAC /* KeybindIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E408442E0483F80035FEAC /* KeybindIntent.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 */; };
|
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
|
||||||
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = "<group>"; };
|
||||||
@@ -626,6 +628,7 @@
|
|||||||
A5E4082D2E0237410035FEAC /* NewTerminalIntent.swift */,
|
A5E4082D2E0237410035FEAC /* NewTerminalIntent.swift */,
|
||||||
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */,
|
A5E408332E03200F0035FEAC /* GetTerminalDetailsIntent.swift */,
|
||||||
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */,
|
A5E408422E047D060035FEAC /* CommandPaletteIntent.swift */,
|
||||||
|
A5E408462E0485270035FEAC /* InputIntent.swift */,
|
||||||
A5E408442E0483F80035FEAC /* KeybindIntent.swift */,
|
A5E408442E0483F80035FEAC /* KeybindIntent.swift */,
|
||||||
A5E4082F2E0271320035FEAC /* GhosttyIntentError.swift */,
|
A5E4082F2E0271320035FEAC /* GhosttyIntentError.swift */,
|
||||||
);
|
);
|
||||||
@@ -819,6 +822,7 @@
|
|||||||
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */,
|
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */,
|
||||||
A5874D992DAD751B00E83852 /* CGS.swift in Sources */,
|
A5874D992DAD751B00E83852 /* CGS.swift in Sources */,
|
||||||
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */,
|
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */,
|
||||||
|
A5E408472E04852B0035FEAC /* InputIntent.swift in Sources */,
|
||||||
A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */,
|
A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */,
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
||||||
A56D58862ACDDB4100508D2C /* Ghostty.Shell.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 AppKit
|
||||||
import AppIntents
|
import AppIntents
|
||||||
|
|
||||||
/// App intent that invokes a command palette entry.
|
|
||||||
struct KeybindIntent: AppIntent {
|
struct KeybindIntent: AppIntent {
|
||||||
static var title: LocalizedStringResource = "Invoke a Keybind Action"
|
static var title: LocalizedStringResource = "Invoke a Keybind Action"
|
||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
title: "Terminal",
|
title: "Terminal",
|
||||||
description: "The terminal to base available commands from."
|
description: "The terminal to invoke the action on."
|
||||||
)
|
)
|
||||||
var terminal: TerminalEntity
|
var terminal: TerminalEntity
|
||||||
|
|
||||||
|
@@ -208,4 +208,15 @@ extension Ghostty {
|
|||||||
0x43: GHOSTTY_KEY_NUMPAD_MULTIPLY,
|
0x43: GHOSTTY_KEY_NUMPAD_MULTIPLY,
|
||||||
0x4E: GHOSTTY_KEY_NUMPAD_SUBTRACT,
|
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.
|
/// Perform a keybinding action.
|
||||||
///
|
///
|
||||||
/// The action can be any valid keybind parameter. e.g. `keybind = goto_tab:4`
|
/// 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) {
|
func insertText(_ string: Any, replacementRange: NSRange) {
|
||||||
// We must have an associated event
|
// We must have an associated event
|
||||||
guard NSApp.currentEvent != nil else { return }
|
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
|
// We want the string view of the any value
|
||||||
var chars = ""
|
var chars = ""
|
||||||
@@ -1724,13 +1724,7 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = chars.utf8CString.count
|
surfaceModel.sendText(chars)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function needs to exist for two reasons:
|
/// This function needs to exist for two reasons:
|
||||||
|
Reference in New Issue
Block a user