#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)? /// Draw the caret as a block over the next character, for normal and visual states. var blockCursor = false { didSet { guard blockCursor != oldValue else { return } needsDisplay = true } } override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) { guard blockCursor else { return super.drawInsertionPoint(in: rect, color: color, turnedOn: flag) } var block = rect block.size.width = max(rect.width, blockWidth) if flag { color.withAlphaComponent(0.45).setFill() block.fill(using: .sourceOver) } else { setNeedsDisplay(block, avoidAdditionalLayout: true) } } override func setNeedsDisplay(_ rect: NSRect, avoidAdditionalLayout flag: Bool) { var widened = rect if blockCursor { widened.size.width += blockWidth } super.setNeedsDisplay(widened, avoidAdditionalLayout: flag) } private var blockWidth: CGFloat { let font = self.font ?? NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) return ("x" as NSString).size(withAttributes: [.font: font]).width } 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