UNPKG

10.2 kBJavaScriptView Raw
1/*
2 Terminal Kit
3
4 Copyright (c) 2009 - 2020 Cédric Ronvel
5
6 The MIT License (MIT)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26
27"use strict" ;
28
29
30
31const fromOutputSequence = require( './fromOutputSequence.js' ) ;
32const string = require( 'string-kit' ) ;
33const NextGenEvents = require( 'nextgen-events' ) ;
34const Promise = require( 'seventh' ) ;
35
36
37
38function SequencesReader( options = {} ) {
39}
40
41module.exports = SequencesReader ;
42
43SequencesReader.prototype = Object.create( NextGenEvents.prototype ) ;
44SequencesReader.prototype.constructor = SequencesReader ;
45
46
47
48async function readStream( stream , size = 1 ) {
49 var data ;
50
51 //stream.on( 'readable' , () => console.error( 'yay readable' ) ) ;
52 data = stream.read( size ) ;
53 //console.error( 'read stream:' , data ) ;
54
55 while ( data === null ) {
56 await Promise.onceEventOrError( stream , 'readable' , [ 'close' , 'end' ] ) ;
57 data = stream.read( size ) ;
58 //console.error( 'read stream (loop):' , data ) ;
59 }
60
61 return data ;
62}
63
64
65
66function charCodeIsAlpha( charCode ) {
67 return ( charCode >= 0x41 && charCode <= 0x5a ) || ( charCode >= 0x61 && charCode <= 0x7a ) ;
68}
69
70function toCharCodeStr( charCode ) {
71 var charCodeStr = charCode.toString( 16 ) ;
72 charCodeStr = charCodeStr.length > 1 ? '\\x' + charCodeStr : '\\x0' + charCodeStr ;
73 return charCodeStr ;
74}
75
76function toCharOrCharCodeStr( charCode ) {
77 if ( charCode <= 0x1f || charCode === 0x7f ) { return toCharCodeStr( charCode ) ; }
78 return String.fromCharCode( charCode ) ;
79}
80
81function defaultArgs( args , defaultArgs_ ) {
82 if ( ! defaultArgs_ || ! defaultArgs_.length ) { return args ; }
83
84 args = Array.from( args ) ;
85
86 defaultArgs_.forEach( ( e , index ) => {
87 if ( e !== undefined && args[ index ] === undefined ) {
88 args[ index ] = e ;
89 }
90 } ) ;
91
92 return args ;
93}
94
95// ESC sequence type that eat 3 bytes
96const ESC_3_BYTES = new Set(
97 // Set character set
98 '(' , ')' , '*' , '+' , '-' , '.' , '/' ,
99 // Misc
100 ' ' , '#' , '%'
101) ;
102
103// E.g.: ESC [ ? ... letter
104const CSI_TYPE_EXTRA_CHAR_BEFORE = new Set(
105 '?' , '>' , '<'
106) ;
107
108// E.g.: ESC [ ... space letter
109const CSI_TYPE_EXTRA_CHAR_AFTER = new Set(
110 ' ' , '$' , '#' , '"' , "'" , '*'
111) ;
112
113// E.g.: ESC [ ... ; ? \x07
114const OSC_TYPE_EXTRA_PARAM_AFTER = new Set(
115 '?'
116) ;
117
118
119
120SequencesReader.prototype.streamToEvent = async function( stream ) {
121 var charCode , charCodeStr , char , bytes , codepoint ,
122 type , args , argIndex , emitParsedSequence , event , subTree , subSubTree , basePtr ,
123 charBuffer = Buffer.alloc( 6 ) ;
124
125 for ( ;; ) {
126 charCode = ( await readStream( stream ) )[ 0 ] ;
127 //console.error( 'got charCode:' , charCode.toString(16) ) ;
128
129 if ( charCode <= 0x1f || charCode === 0x7f ) {
130
131 charCodeStr = toCharCodeStr( charCode ) ;
132 //console.error( 'control:' , charCodeStr ) ;
133
134 if ( charCode === 0x1b ) { // Escape, probably an escape sequence!
135 charCode = ( await readStream( stream ) )[ 0 ] ;
136
137 if ( charCode === 0x5b ) { // [ =CSI
138 args = '' ;
139
140 while ( ! charCodeIsAlpha( charCode = ( await readStream( stream ) )[ 0 ] ) ) {
141 args += String.fromCharCode( charCode ) ;
142 }
143
144 type = String.fromCharCode( charCode ) ;
145 args = args ? args.split( ';' ) : [] ;
146 emitParsedSequence = true ;
147
148 if ( args.length && CSI_TYPE_EXTRA_CHAR_BEFORE.has( args[ 0 ][ 0 ] ) ) {
149 type = args[ 0 ][ 0 ] + type ;
150 args[ 0 ] = args[ 0 ].slice( 1 ) ;
151 }
152
153 if ( args.length && CSI_TYPE_EXTRA_CHAR_AFTER.has( args[ args.length - 1 ][ args[ args.length - 1 ] - 1 ] ) ) {
154 type = args[ args.length - 1 ][ args[ args.length - 1 ] - 1 ] + type ;
155 args[ args.length - 1 ] = args[ args.length - 1 ].slice( 0 , -1 ) ;
156 }
157
158 subTree = fromOutputSequence.CSI[ type ] ;
159 console.error( ">>>>>>>>>> CSI parsing:" , type , args ) ;
160
161 if ( subTree ) {
162 if ( subTree.subTree && args.length ) {
163 basePtr = subTree ;
164
165 for ( argIndex = 0 ; argIndex < args.length ; argIndex ++ ) {
166 subSubTree = basePtr.subTree[ args[ argIndex ] ] ;
167
168 if ( subSubTree ) {
169 if ( subSubTree.subTree ) {
170 basePtr = subSubTree ;
171 continue ;
172 }
173
174 event = subSubTree.event || basePtr.event || subTree.event ;
175
176 if ( event !== 'none' ) {
177 this.emit(
178 event ,
179 subSubTree.subType || basePtr.subType || subTree.subType ,
180 subSubTree.arg !== undefined ? subSubTree.arg : ( basePtr.arg !== undefined ? basePtr.arg : subTree.arg ) , /* eslint-disable-line no-nested-ternary */
181 subSubTree.extraArgs || basePtr.extraArgs || subTree.extraArgs || defaultArgs( args.slice( argIndex + 1 ) , subSubTree.defaultExtraArgs )
182 ) ;
183 }
184
185 emitParsedSequence = false ;
186
187 if ( subSubTree.continue ) {
188 basePtr = subTree ;
189 continue ;
190 }
191 }
192 else {
193 emitParsedSequence = true ;
194 }
195 break ;
196 }
197 }
198 else if ( subTree.event ) {
199 if ( subTree.event !== 'none' ) {
200 this.emit(
201 subTree.event ,
202 subTree.subType ,
203 subTree.arg ,
204 subTree.extraArgs || defaultArgs( args , subTree.defaultExtraArgs )
205 ) ;
206 }
207
208 emitParsedSequence = false ;
209 }
210 }
211
212 if ( emitParsedSequence ) {
213 this.emit( 'CSI' , type , args ) ;
214 }
215
216 continue ;
217 }
218 else if ( charCode === 0x5d ) { // ] =OSC
219 args = '' ;
220
221 for ( ;; ) {
222 charCode = ( await readStream( stream ) )[ 0 ] ;
223
224 if ( charCode === 0x07 ) { break ; } // This is the OSC terminator, leave
225
226 if ( charCode === 0x1b ) {
227 charCode = ( await readStream( stream ) )[ 0 ] ;
228 if ( charCode === 0x5c ) { break ; } // ESC \ =string terminator, so we leave
229 // Ok, that was not the string terminator, so add the ESC key to the arg, but it is a bit strange to do that...
230 args += String.fromCharCode( 0x1b ) ;
231 }
232
233 args += String.fromCharCode( charCode ) ;
234 }
235
236 args = args ? args.split( ';' ) : [] ;
237 type = args.shift() ;
238
239 if ( OSC_TYPE_EXTRA_PARAM_AFTER.has( args[ args.length - 1 ] ) ) {
240 type += args.pop() ;
241 }
242
243 emitParsedSequence = true ;
244
245 subTree = fromOutputSequence.OSC[ type ] ;
246
247 if ( subTree ) {
248 if ( subTree.subTree ) {
249 for ( argIndex = 0 ; argIndex < args.length ; argIndex ++ ) {
250 subSubTree = subTree.subTree[ args[ argIndex ] ] ;
251 if ( subSubTree ) {
252 event = subSubTree.event || subTree.event ;
253
254 if ( event !== 'none' ) {
255 this.emit( event , subSubTree.subType || subTree.subType , args.slice( argIndex + 1 ) ) ;
256 }
257
258 emitParsedSequence = false ;
259 }
260 else {
261 emitParsedSequence = true ;
262 }
263 break ;
264 }
265 }
266 else if ( subTree.event ) {
267 if ( subTree.event !== 'none' ) {
268 this.emit( subTree.event , subTree.subType , args ) ;
269 }
270
271 emitParsedSequence = false ;
272 }
273 }
274
275 if ( emitParsedSequence ) {
276 this.emit( 'OSC' , type , args ) ;
277 }
278
279 continue ;
280 }
281 else {
282 // Single/simple escape
283 type = toCharOrCharCodeStr( charCode ) ;
284 args = null ;
285
286 if ( ESC_3_BYTES.has( type ) ) {
287 // This is a 3 bytes ESC
288 args = [ toCharOrCharCodeStr( ( await readStream( stream ) )[ 0 ] ) ] ;
289 }
290
291 emitParsedSequence = true ;
292 subTree = fromOutputSequence.ESC[ type ] ;
293
294 if ( subTree ) {
295 if ( subTree.event !== 'none' ) {
296 this.emit( subTree.event , subTree.subType , subTree.arg , args || subTree.extraArgs ) ;
297 }
298
299 emitParsedSequence = false ;
300 }
301
302 if ( emitParsedSequence ) {
303 this.emit( 'ESC' , type , args ) ;
304 }
305
306 continue ;
307 }
308 }
309 else if ( fromOutputSequence.control[ charCodeStr ] ) {
310 subTree = fromOutputSequence.control[ charCodeStr ] ;
311
312 if ( subTree.event !== 'none' ) {
313 this.emit( subTree.event , subTree.subType , subTree.arg , subTree.extraArgs ) ;
314 }
315
316 continue ;
317 }
318 else {
319 this.emit( 'control' , charCodeStr ) ;
320 continue ;
321 }
322 }
323
324 if ( charCode >= 0x80 ) {
325 // Unicode bytes per char guessing
326 if ( charCode < 0xc0 ) { continue ; } // We are in a middle of an unicode multibyte sequence... Something fails somewhere, we will just continue for now...
327 else if ( charCode < 0xe0 ) { bytes = 2 ; }
328 else if ( charCode < 0xf0 ) { bytes = 3 ; }
329 else if ( charCode < 0xf8 ) { bytes = 4 ; }
330 else if ( charCode < 0xfc ) { bytes = 5 ; }
331 else { bytes = 6 ; }
332
333 charBuffer[ 0 ] = charCode ;
334 charBuffer[ 1 ] = charBuffer[ 2 ] = charBuffer[ 3 ] = charBuffer[ 4 ] = charBuffer[ 5 ] = 0 ;
335 ( await readStream( stream , bytes - 1 ) ).copy( charBuffer , 1 ) ;
336
337 char = charBuffer.toString( 'utf8' ) ;
338 codepoint = string.unicode.firstCodePoint( char ) ;
339
340 //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: codepoint , code: charBuffer } ) ;
341 this.emit( 'char' , char , codepoint ) ;
342 }
343 else {
344 // Standard ASCII
345 char = String.fromCharCode( charCode ) ;
346 //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: charCode , code: charCode } ) ;
347 this.emit( 'char' , char , charCode ) ;
348 }
349 }
350} ;
351