krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgEditorAppKit/Keys.swift

85c633a83a4744bef9dde8b52462f9c2a1c037e4
orgstar/Sources/OrgEditorAppKit/Keys.swift history · blame · raw

75 lines · 3021 bytes

 1#if os(macOS)
 2import AppKit
 3import OrgCore
 4
 5/// Which Option keys act as Meta. The others type characters, as usual on a Mac.
 6public struct OptionAsMeta: OptionSet, Sendable {
 7    public let rawValue: Int
 8    public init(rawValue: Int) { self.rawValue = rawValue }
 9    public static let left = OptionAsMeta(rawValue: 1)
10    public static let right = OptionAsMeta(rawValue: 2)
11}
12
13/// A text view that offers key presses to the editor's keymap before the text system.
14final class OrgTextView: NSTextView {
15    /// Returns true when the key was handled.
16    var keyHandler: ((NSEvent) -> Bool)?
17    var onMouseDown: (() -> Void)?
18
19    override func keyDown(with event: NSEvent) {
20        if !hasMarkedText(), keyHandler?(event) == true { return }
21        super.keyDown(with: event)
22    }
23
24    override func performKeyEquivalent(with event: NSEvent) -> Bool {
25        if window?.firstResponder === self, !hasMarkedText(), event.modifierFlags.contains(.command), keyHandler?(event) == true {
26            return true
27        }
28        return super.performKeyEquivalent(with: event)
29    }
30
31    override func mouseDown(with event: NSEvent) {
32        onMouseDown?()
33        super.mouseDown(with: event)
34    }
35}
36
37enum KeyEvents {
38    /// Device-dependent bits for the left and right Option keys (`NX_DEVICELALTKEYMASK`,
39    /// `NX_DEVICERALTKEYMASK`).
40    static let leftOption: UInt = 0x20
41    static let rightOption: UInt = 0x40
42
43    static let named: [UInt16: String] = [
44        48: "TAB", 36: "RET", 76: "RET", 49: "SPC", 53: "ESC", 51: "DEL", 117: "<delete>",
45        123: "<left>", 124: "<right>", 125: "<down>", 126: "<up>",
46        115: "<home>", 119: "<end>", 116: "<prior>", 121: "<next>",
47        122: "<f1>", 120: "<f2>", 99: "<f3>", 118: "<f4>", 96: "<f5>", 97: "<f6>",
48        98: "<f7>", 100: "<f8>", 101: "<f9>", 109: "<f10>", 103: "<f11>", 111: "<f12>",
49    ]
50
51    /// The chord for a key press, or nil when it types a character with Option.
52    static func chord(for event: NSEvent, optionAsMeta: OptionAsMeta) -> KeyChord? {
53        guard event.type == .keyDown else { return nil }
54        let flags = event.modifierFlags
55        var modifiers: KeyChord.Modifiers = []
56        if flags.contains(.control) { modifiers.insert(.control) }
57        if flags.contains(.command) { modifiers.insert(.command) }
58        if flags.contains(.option) {
59            let raw = flags.rawValue
60            let meta = (raw & leftOption != 0 && optionAsMeta.contains(.left)) || (raw & rightOption != 0 && optionAsMeta.contains(.right))
61            if meta || !modifiers.isEmpty {
62                modifiers.insert(.meta)
63            } else {
64                return nil
65            }
66        }
67        if let name = named[event.keyCode] {
68            if flags.contains(.shift) { modifiers.insert(.shift) }
69            return KeyChord(name, modifiers)
70        }
71        guard let characters = event.charactersIgnoringModifiers, characters.count == 1 else { return nil }
72        return KeyChord(characters, modifiers)
73    }
74}
75#endif