CommandLineToolkit

Форк
0
/
ANSIKeyboard.swift 
288 строк · 8.3 Кб
1
enum ANSIKeyCode: UInt8 {
2
    case none      = 0    // null
3
    case up        = 65   // ESC [ A
4
    case down      = 66   // ESC [ B
5
    case right     = 67   // ESC [ C
6
    case left      = 68   // ESC [ D
7
    case end       = 70   // ESC [ F  or  ESC [ 4~
8
    case home      = 72   // ESC [ H  or  ESC [ 1~
9
    case insert    = 2    // ESC [ 2~
10
    case delete    = 3    // ESC [ 3~
11
    case pageUp    = 5    // ESC [ 5~
12
    case pageDown  = 6    // ESC [ 6~
13
    
14
    case f1        = 80   // ESC O P  or  ESC [ 11~
15
    case f2        = 81   // ESC O Q  or  ESC [ 12~
16
    case f3        = 82   // ESC O R  or  ESC [ 13~
17
    case f4        = 83   // ESC O S  or  ESC [ 14~
18
    case f5        = 15   // ESC [ 15~
19
    case f6        = 17   // ESC [ 17~
20
    case f7        = 18   // ESC [ 18~
21
    case f8        = 19   // ESC [ 19~
22
    case f9        = 20   // ESC [ 20~
23
    case f10       = 21   // ESC [ 21~
24
    case f11       = 23   // ESC [ 23~
25
    case f12       = 24   // ESC [ 24~
26
}
27

28
enum ANSIMetaCode: UInt8 {
29
    case control = 1
30
    case shift   = 2
31
    case alt     = 3
32
}
33

34
struct Position: Hashable {
35
    var row: Int
36
    var col: Int
37
}
38

39
struct Size: Hashable {
40
    var rows: Int
41
    var cols: Int
42
}
43

44
extension Size {
45
    static let max: Size = .init(rows: .max, cols: .max)
46
}
47

48
enum InputEscapeSequence {
49
    case key(code: ANSIKeyCode, meta: [ANSIMetaCode])
50
    case cursor(position: Position)
51
    case screen(size: Size)
52
    case unknown(raw: String)
53
}
54

55
private func SS3Letter(_ key: UInt8) -> ANSIKeyCode {
56
    switch key {
57
    case ANSIKeyCode.f1.rawValue:
58
        return .f1
59
    case ANSIKeyCode.f2.rawValue:
60
        return .f2
61
    case ANSIKeyCode.f3.rawValue:
62
        return .f3
63
    case ANSIKeyCode.f4.rawValue:
64
        return .f4
65
    default:
66
        return .none
67
    }
68
}
69

70
private func CSILetter(_ key: UInt8) -> ANSIKeyCode {
71
    switch key {
72
    case ANSIKeyCode.up.rawValue:
73
        return .up
74
    case ANSIKeyCode.down.rawValue:
75
        return .down
76
    case ANSIKeyCode.left.rawValue:
77
        return .left
78
    case ANSIKeyCode.right.rawValue:
79
        return .right
80
    case ANSIKeyCode.home.rawValue:
81
        return .home
82
    case ANSIKeyCode.end.rawValue:
83
        return .end
84
    case ANSIKeyCode.f1.rawValue:
85
        return .f1
86
    case ANSIKeyCode.f2.rawValue:
87
        return .f2
88
    case ANSIKeyCode.f3.rawValue:
89
        return .f3
90
    case ANSIKeyCode.f4.rawValue:
91
        return .f4
92
    default:
93
        return .none
94
    }
95
}
96

97
private func CSINumber(_ key: UInt8) -> ANSIKeyCode {
98
    switch key {
99
    case 1:
100
        return .home
101
    case 4:
102
        return .end
103
    case ANSIKeyCode.insert.rawValue:
104
        return .insert
105
    case ANSIKeyCode.delete.rawValue:
106
        return .delete
107
    case ANSIKeyCode.pageUp.rawValue:
108
        return .pageUp
109
    case ANSIKeyCode.pageDown.rawValue:
110
        return .pageDown
111
    case 11:
112
        return .f1
113
    case 12:
114
        return .f2
115
    case 13:
116
        return .f3
117
    case 14:
118
        return .f4
119
    case ANSIKeyCode.f5.rawValue:
120
        return .f5
121
    case ANSIKeyCode.f6.rawValue:
122
        return .f6
123
    case ANSIKeyCode.f7.rawValue:
124
        return .f7
125
    case ANSIKeyCode.f8.rawValue:
126
        return .f8
127
    case ANSIKeyCode.f9.rawValue:
128
        return .f9
129
    case ANSIKeyCode.f10.rawValue:
130
        return .f10
131
    case ANSIKeyCode.f11.rawValue:
132
        return .f11
133
    case ANSIKeyCode.f12.rawValue:
134
        return .f12
135
    default:
136
        return .none
137
    }
138
}
139

140
private extension FixedWidthInteger {
141
    var isLetter: Bool {
142
        return (65...90 ~= self)
143
    }
144

145
    var isNumber: Bool {
146
        return (48...57 ~= self)
147
    }
148
}
149

150
private extension UInt8 {
151
    var char: Character {
152
        .init(.init(self))
153
    }
154
}
155

156
extension Character {
157
    static let none: Self      = "\u{00}"   // \0 NUL
158
    static let bell: Self      = "\u{07}"   // \a BELL
159
    static let erase: Self     = "\u{08}"   // BS
160
    static let tab: Self       = "\u{09}"   // \t TAB (horizontal)
161
    static let linefeed: Self  = "\u{0A}"   // \n LF
162
    static let vtab: Self      = "\u{0B}"   // \v VT (vertical tab)
163
    static let formfeed: Self  = "\u{0C}"   // \f FF
164
    static let enter: Self     = "\u{0D}"   // \r CR
165
    static let endOfLine: Self = "\u{1A}"   // SUB or EOL
166
    static let escape: Self    = "\u{1B}"   // \e ESC
167
    static let space: Self     = "\u{20}"   // SPACE
168
    static let del: Self       = "\u{7F}"   // DEL
169

170
    var isNonPrintable: Bool {
171
        return self < " " || self == "\u{7F}"
172
    }
173
}
174

175
private func CSIMeta(_ key: UInt8) -> [ANSIMetaCode] {
176
    //! NOTE: if x = 1 then ~ becomes letter
177
    switch key {
178
    case 2:
179
        return [.shift]                     // ESC [ x ; 2~
180
    case 3:
181
        return [.alt]                       // ESC [ x ; 3~
182
    case 4:
183
        return [.shift, .alt]               // ESC [ x ; 4~
184
    case 5:
185
        return [.control]                   // ESC [ x ; 5~
186
    case 6:
187
        return [.shift, .control]           // ESC [ x ; 6~
188
    case 7:
189
        return [.alt, .control]             // ESC [ x ; 7~
190
    case 8:
191
        return [.shift, .alt, .control]     // ESC [ x ; 8~
192
    default:
193
        return []
194
    }
195
}
196

197
extension ANSITerminal {
198
    func readEscapeSequence() -> InputEscapeSequence {
199
        let nonBlock = isNonBlockingMode
200
        if !nonBlock { enableNonBlockingTerminal() }
201
        defer {
202
            if !nonBlock { disableNonBlockingTerminal() }
203
        }
204

205
        var code = ANSIKeyCode.none
206

207
        var byte: UInt8 = 0
208
        var cmd: String = .ESC
209

210
        func readNextByte() {
211
            byte = readByte()
212
            cmd.append(byte.char)
213
        }
214

215
        func readNumber(beginning: String = "") -> UInt8 {
216
            var numberString = beginning
217
            repeat {
218
                readNextByte()
219
                if byte.isNumber { numberString.append(byte.char) }
220
            } while byte.isNumber
221
            return UInt8(numberString) ?? 0
222
        }
223

224
        // make sure there is data in stdin
225
        if !keyPressed() { return .unknown(raw: cmd) }
226

227
        while true {                                // read key sequence
228
            cmd.append(readChar())                  // check for ESC combination
229

230
            switch cmd {
231
            case .CSI:
232
                readNextByte()
233

234
                if byte.isLetter {                              // CSI + letter
235
                    code = CSILetter(byte)
236
                    return .key(code: code, meta: [])
237
                } else if byte.isNumber {                       // CSI + numbers
238
                    let firstNumber = readNumber(beginning: .init(byte.char))
239

240
                    switch byte.char {
241
                    case "~":
242
                        code = CSINumber(UInt8(firstNumber))
243
                        return .key(code: code, meta: [])
244
                    case ";":
245
                        let secondNumber = readNumber()
246

247
                        switch byte.char {
248
                        case "~":
249
                            return .key(code: CSINumber(firstNumber), meta: CSIMeta(secondNumber))
250
                        case "R":
251
                            return .cursor(position: Position(
252
                                row: Int(firstNumber),
253
                                col: Int(secondNumber)
254
                            ))
255
                        case ";":
256
                            let thirdNumber = readNumber()
257

258
                            switch byte.char {
259
                            case "t" where firstNumber == 8:
260
                                return .screen(size: .init(rows: Int(secondNumber), cols: Int(thirdNumber)))
261
                            default:
262
                                return .unknown(raw: cmd)
263
                            }
264

265
                        default:
266
                            return .key(code: CSILetter(byte), meta: CSIMeta(secondNumber))
267
                        }
268
                    default:
269
                        return .unknown(raw: cmd)
270
                    }
271
                } else {
272
                    // neither letter nor numbers
273
                    break
274
                }
275
            case .SS3:
276
                readNextByte()
277
                if byte.isLetter { code = SS3Letter(byte) }
278
                return .key(code: code, meta: [])
279
            case .BACK:
280
                return .key(code: .left, meta: [.alt])
281
            case .FORV:
282
                return .key(code: .right, meta: [.alt])
283
            default:
284
                return .unknown(raw: cmd)
285
            }
286
        }
287
    }
288
}
289

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.