native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
7.1 kB · 235 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236import AppKitimport CoreTextimport GhosttyKit
extension GhosttySurfaceView: NSTextInputClient { func hasMarkedText() -> Bool { markedText.length > 0 }
func markedRange() -> NSRange { guard markedText.length > 0 else { return NSRange() } return NSRange(location: 0, length: markedText.length) }
func selectedRange() -> NSRange { guard let surface else { return NSRange() } var text = ghostty_text_s() guard ghostty_surface_read_selection(surface, &text) else { return NSRange() } defer { ghostty_surface_free_text(surface, &text) } return NSRange(location: Int(text.offset_start), length: Int(text.offset_len)) }
func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) { switch string { case let attributedText as NSAttributedString: markedText = NSMutableAttributedString(attributedString: attributedText) case let stringValue as String: markedText = NSMutableAttributedString(string: stringValue) default: return } if markedText.length > 0 { recordEditingActivity() } if keyTextAccumulator == nil { syncPreedit() } }
func unmarkText() { if markedText.length > 0 { markedText.mutableString.setString("") syncPreedit() } }
func validAttributesForMarkedText() -> [NSAttributedString.Key] { [] }
func attributedSubstring( forProposedRange range: NSRange, actualRange: NSRangePointer? ) -> NSAttributedString? { guard let surface else { return nil } guard range.length > 0 else { return nil } var text = ghostty_text_s() guard ghostty_surface_read_selection(surface, &text) else { return nil } defer { ghostty_surface_free_text(surface, &text) } var attributes: [NSAttributedString.Key: Any] = [:] if let fontRaw = ghostty_surface_quicklook_font(surface) { let font = Unmanaged<CTFont>.fromOpaque(fontRaw) attributes[.font] = font.takeUnretainedValue() font.release() } return NSAttributedString(string: Self.string(from: text), attributes: attributes) }
func characterIndex(for point: NSPoint) -> Int { 0 }
func firstRect(forCharacterRange range: NSRange, actualRange: NSRangePointer?) -> NSRect { guard let surface else { return NSRect(x: frame.origin.x, y: frame.origin.y, width: 0, height: 0) } var caretX: Double = 0 var caretY: Double = 0 var width: Double = cellSize.width var height: Double = cellSize.height if range.length > 0, range != selectedRange() { var text = ghostty_text_s() if ghostty_surface_read_selection(surface, &text) { caretX = text.tl_px_x - 2 caretY = text.tl_px_y + 2 ghostty_surface_free_text(surface, &text) } else { ghostty_surface_ime_point(surface, &caretX, &caretY, &width, &height) } } else { ghostty_surface_ime_point(surface, &caretX, &caretY, &width, &height) } if range.length == 0, width > 0 { width = 0 caretX += cellSize.width * Double(range.location + range.length) } let viewRect = NSRect( x: caretX, y: frame.size.height - caretY, width: width, height: max(height, cellSize.height) ) let winRect = convert(viewRect, to: nil) guard let window else { return winRect } return window.convertToScreen(winRect) }
func insertText(_ string: Any, replacementRange _: NSRange) { guard NSApp.currentEvent != nil else { return } guard surface != nil else { return } var chars = "" switch string { case let attributedText as NSAttributedString: chars = attributedText.string case let stringValue as String: chars = stringValue default: return } unmarkText() if var acc = keyTextAccumulator { acc.append(chars) keyTextAccumulator = acc onCommittedText?(chars) return } insertCommittedTextForBroadcast(chars) onCommittedText?(chars) }
func insertCommittedTextForBroadcast(_ text: String) { guard let surface else { return } guard !text.isEmpty else { return } recordEditingActivity() unmarkText() let len = text.utf8CString.count guard len > 0 else { return } text.withCString { ptr in ghostty_surface_text(surface, ptr, UInt(len - 1)) } }
@discardableResult func applyMirroredKeyForBroadcast(_ key: MirroredTerminalKey) -> Bool { let windowNumber = window?.windowNumber ?? 0 guard let keyDownEvent = key.keyDownEvent(windowNumber: windowNumber) else { return false } keyDown(with: keyDownEvent) if !key.isRepeat, let keyUpEvent = key.keyUpEvent(windowNumber: windowNumber) { keyUp(with: keyUpEvent) } return true }
@discardableResult func submitLine() -> Bool { let timestamp = ProcessInfo.processInfo.systemUptime let windowNumber = window?.windowNumber ?? 0 guard let keyDownEvent = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [], timestamp: timestamp, windowNumber: windowNumber, context: nil, characters: "\r", charactersIgnoringModifiers: "\r", isARepeat: false, keyCode: 36 ), let keyUpEvent = NSEvent.keyEvent( with: .keyUp, location: .zero, modifierFlags: [], timestamp: timestamp, windowNumber: windowNumber, context: nil, characters: "\r", charactersIgnoringModifiers: "\r", isARepeat: false, keyCode: 36 ) else { return false } keyDown(with: keyDownEvent) keyUp(with: keyUpEvent) return true }
// MARK: - CLI key token delivery
/// Send a single key event for a canonical CLI token (e.g. "enter", "ctrl-c", "pageup"). /// Creates synthetic keyDown/keyUp NSEvents matching the token spec. @discardableResult func sendCLIKeyToken(_ token: String) -> Bool { guard surface != nil else { return false } guard let spec = CLIKeySpec.from(token: token) else { return false } let timestamp = ProcessInfo.processInfo.systemUptime let windowNumber = window?.windowNumber ?? 0 guard let keyDownEvent = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: spec.modifiers, timestamp: timestamp, windowNumber: windowNumber, context: nil, characters: spec.characters, charactersIgnoringModifiers: spec.charactersIgnoringModifiers, isARepeat: false, keyCode: spec.keyCode ), let keyUpEvent = NSEvent.keyEvent( with: .keyUp, location: .zero, modifierFlags: spec.modifiers, timestamp: timestamp, windowNumber: windowNumber, context: nil, characters: spec.characters, charactersIgnoringModifiers: spec.charactersIgnoringModifiers, isARepeat: false, keyCode: spec.keyCode ) else { return false } keyDown(with: keyDownEvent) keyUp(with: keyUpEvent) return true }}