Sources/OrgEditorAppKit/Keys.swift
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