44 ArrayPrototypeToSorted,
55 RegExpPrototypeExec,
66 StringFromCharCode,
7+ StringFromCodePoint,
78 StringPrototypeCharCodeAt,
89 StringPrototypeCodePointAt,
910 StringPrototypeSlice,
@@ -15,6 +16,26 @@ const {
1516const kUTF16SurrogateThreshold = 0x10000 ; // 2 ** 16
1617const kEscape = '\x1b' ;
1718const kSubstringSearch = Symbol ( 'kSubstringSearch' ) ;
19+ const kKittyModifierShift = 1 ;
20+ const kKittyModifierAlt = 2 ;
21+ const kKittyModifierCtrl = 4 ;
22+ const kKittyModifierRightAlt = 8 ;
23+
24+ const kittyEventTypes = {
25+ __proto__ : null ,
26+ 1 : 'press' ,
27+ 2 : 'repeat' ,
28+ 3 : 'release' ,
29+ } ;
30+
31+ const kittySpecialKeyNames = {
32+ __proto__ : null ,
33+ 9 : 'tab' ,
34+ 13 : 'return' ,
35+ 27 : 'escape' ,
36+ 32 : 'space' ,
37+ 127 : 'backspace' ,
38+ } ;
1839
1940function CSI ( strings , ...args ) {
2041 let ret = `${ kEscape } [` ;
@@ -56,6 +77,77 @@ function charLengthAt(str, i) {
5677 return StringPrototypeCodePointAt ( str , i ) >= kUTF16SurrogateThreshold ? 2 : 1 ;
5778}
5879
80+ function decodeKittyCodePoints ( text ) {
81+ if ( text === undefined || text === '' )
82+ return undefined ;
83+ const chars = StringPrototypeSplit ( text , ':' ) ;
84+ let ret = '' ;
85+ for ( let i = 0 ; i < chars . length ; i ++ ) {
86+ const code = Number ( chars [ i ] ) ;
87+ if ( ! Number . isInteger ( code ) )
88+ return undefined ;
89+ ret += StringFromCodePoint ( code ) ;
90+ }
91+ return ret ;
92+ }
93+
94+ function getKittyBaseName ( codepoint , text ) {
95+ if ( kittySpecialKeyNames [ codepoint ] !== undefined )
96+ return kittySpecialKeyNames [ codepoint ] ;
97+
98+ const source = text ?. length ? text :
99+ ( codepoint > 0 ? StringFromCodePoint ( codepoint ) : '' ) ;
100+ if ( RegExpPrototypeExec ( / ^ [ 0 - 9 A - Z a - z ] $ / , source ) !== null )
101+ return StringPrototypeToLowerCase ( source ) ;
102+ return undefined ;
103+ }
104+
105+ function parseKittySequence ( code , key ) {
106+ const match = RegExpPrototypeExec (
107+ / ^ ( \d + (?: : \d + ) * ) ( (?: ; (?: \d * (?: : \d + ) ? ) ) ? ) (?: ; ( \d + (?: : \d + ) * ) ) ? u $ / ,
108+ code ,
109+ ) ;
110+ if ( match === null )
111+ return false ;
112+
113+ const codepoints = StringPrototypeSplit ( match [ 1 ] , ':' ) ;
114+ const primaryCodepoint = Number ( codepoints [ 0 ] ) ;
115+ if ( ! Number . isInteger ( primaryCodepoint ) )
116+ return false ;
117+
118+ let modifiers = 1 ;
119+ let eventType = 1 ;
120+ if ( match [ 2 ] !== '' ) {
121+ const modifierField = StringPrototypeSlice ( match [ 2 ] , 1 ) ;
122+ if ( modifierField !== '' ) {
123+ const modifierParts = StringPrototypeSplit ( modifierField , ':' ) ;
124+ modifiers = Number ( modifierParts [ 0 ] || '1' ) ;
125+ if ( ! Number . isInteger ( modifiers ) )
126+ return false ;
127+ if ( modifierParts . length > 1 ) {
128+ eventType = Number ( modifierParts [ 1 ] || '1' ) ;
129+ if ( ! Number . isInteger ( eventType ) )
130+ return false ;
131+ }
132+ }
133+ }
134+
135+ const modifierFlags = modifiers - 1 ;
136+ key . ctrl = ! ! ( modifierFlags & kKittyModifierCtrl ) ;
137+ key . meta = ! ! ( modifierFlags & ( kKittyModifierAlt | kKittyModifierRightAlt ) ) ;
138+ key . shift = ! ! ( modifierFlags & kKittyModifierShift ) ;
139+ key . modifiers = modifierFlags ;
140+ key . eventType = kittyEventTypes [ eventType ] || 'press' ;
141+ key . code = `[${ code } ` ;
142+
143+ const text = decodeKittyCodePoints ( match [ 3 ] ) ;
144+ if ( text !== undefined )
145+ key . text = text ;
146+
147+ key . name = getKittyBaseName ( primaryCodepoint , text ) ;
148+ return true ;
149+ }
150+
59151/*
60152 Some patterns seen in terminal key escape codes, derived from combos seen
61153 at http://www.midnight-commander.org/browser/lib/tty/key.c
@@ -165,27 +257,8 @@ function* emitKeys(stream) {
165257 *
166258 */
167259 const cmdStart = s . length - 1 ;
168-
169- // Skip one or two leading digits
170- if ( ch >= '0' && ch <= '9' ) {
260+ while ( ch >= '0' && ch <= '9' || ch === ';' || ch === ':' ) {
171261 s += ( ch = yield ) ;
172-
173- if ( ch >= '0' && ch <= '9' ) {
174- s += ( ch = yield ) ;
175-
176- if ( ch >= '0' && ch <= '9' ) {
177- s += ( ch = yield ) ;
178- }
179- }
180- }
181-
182- // skip modifier
183- if ( ch === ';' ) {
184- s += ( ch = yield ) ;
185-
186- if ( ch >= '0' && ch <= '9' ) {
187- s += yield ;
188- }
189262 }
190263
191264 /*
@@ -202,6 +275,9 @@ function* emitKeys(stream) {
202275 code += match [ 1 ] + match [ 3 ] ;
203276 modifier = ( match [ 2 ] || 1 ) - 1 ;
204277 }
278+ } else if ( cmd . endsWith ( 'u' ) && parseKittySequence ( cmd , key ) ) {
279+ code += cmd ;
280+ modifier = key . modifiers ;
205281 } else if (
206282 ( match = RegExpPrototypeExec ( / ^ ( ( \d ; ) ? ( \d ) ) ? ( [ A - Z a - z ] ) $ / , cmd ) )
207283 ) {
@@ -216,10 +292,10 @@ function* emitKeys(stream) {
216292 key . ctrl = ! ! ( modifier & 4 ) ;
217293 key . meta = ! ! ( modifier & 10 ) ;
218294 key . shift = ! ! ( modifier & 1 ) ;
219- key . code = code ;
295+ key . code ?? = code ;
220296
221297 // Parse the key itself
222- switch ( code ) {
298+ if ( key . name === undefined ) switch ( code ) {
223299 /* xterm/gnome ESC [ letter (with modifier) */
224300 case '[P' : key . name = 'f1' ; break ;
225301 case '[Q' : key . name = 'f2' ; break ;
@@ -366,9 +442,11 @@ function* emitKeys(stream) {
366442
367443 key . sequence = s ;
368444
445+ const keypress = escaped && typeof key . text === 'string' ? key . text : s ;
446+
369447 if ( s . length !== 0 && ( key . name !== undefined || escaped ) ) {
370448 /* Named character or sequence */
371- stream . emit ( 'keypress' , escaped ? undefined : s , key ) ;
449+ stream . emit ( 'keypress' , escaped ? keypress : s , key ) ;
372450 } else if ( charLengthAt ( s , 0 ) === s . length ) {
373451 /* Single unnamed character, e.g. "." */
374452 stream . emit ( 'keypress' , s , key ) ;
0 commit comments