#if os(macOS) import AppKit import OrgCore /// Which Option keys act as Meta. The others type characters, as usual on a Mac. public struct OptionAsMeta: OptionSet, Sendable { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } public static let left = OptionAsMeta(rawValue: 1) public static let right = OptionAsMeta(rawValue: 2) } /// A text view that offers key presses to the editor's keymap before the text system. final class OrgTextView: NSTextView { /// Returns true when the key was handled. var keyHandler: ((NSEvent) -> Bool)? var onMouseDown: (() -> Void)? override func keyDown(with event: NSEvent) { if !hasMarkedText(), keyHandler?(event) == true { return } super.keyDown(with: event) } override func performKeyEquivalent(with event: NSEvent) -> Bool { if window?.firstResponder === self, !hasMarkedText(), event.modifierFlags.contains(.command), keyHandler?(event) == true { return true } return super.performKeyEquivalent(with: event) } override func mouseDown(with event: NSEvent) { onMouseDown?() super.mouseDown(with: event) } } enum KeyEvents { /// Device-dependent bits for the left and right Option keys (`NX_DEVICELALTKEYMASK`, /// `NX_DEVICERALTKEYMASK`). static let leftOption: UInt = 0x20 static let rightOption: UInt = 0x40 static let named: [UInt16: String] = [ 48: "TAB", 36: "RET", 76: "RET", 49: "SPC", 53: "ESC", 51: "DEL", 117: "", 123: "", 124: "", 125: "", 126: "", 115: "", 119: "", 116: "", 121: "", 122: "", 120: "", 99: "", 118: "", 96: "", 97: "", 98: "", 100: "", 101: "", 109: "", 103: "", 111: "", ] /// The chord for a key press, or nil when it types a character with Option. static func chord(for event: NSEvent, optionAsMeta: OptionAsMeta) -> KeyChord? { guard event.type == .keyDown else { return nil } let flags = event.modifierFlags var modifiers: KeyChord.Modifiers = [] if flags.contains(.control) { modifiers.insert(.control) } if flags.contains(.command) { modifiers.insert(.command) } if flags.contains(.option) { let raw = flags.rawValue let meta = (raw & leftOption != 0 && optionAsMeta.contains(.left)) || (raw & rightOption != 0 && optionAsMeta.contains(.right)) if meta || !modifiers.isEmpty { modifiers.insert(.meta) } else { return nil } } if let name = named[event.keyCode] { if flags.contains(.shift) { modifiers.insert(.shift) } return KeyChord(name, modifiers) } guard let characters = event.charactersIgnoringModifiers, characters.count == 1 else { return nil } return KeyChord(characters, modifiers) } } #endif