1
1
'use strict' ;
2
2
3
3
const {
4
+ MathMin,
4
5
Symbol,
5
6
} = primordials ;
6
7
7
- const acorn = require ( 'internal/deps/acorn/acorn/dist/acorn' ) ;
8
+ const { tokTypes : tt , Parser : AcornParser } =
9
+ require ( 'internal/deps/acorn/acorn/dist/acorn' ) ;
8
10
const privateMethods =
9
11
require ( 'internal/deps/acorn-plugins/acorn-private-methods/index' ) ;
10
12
const classFields =
@@ -13,7 +15,30 @@ const numericSeparator =
13
15
require ( 'internal/deps/acorn-plugins/acorn-numeric-separator/index' ) ;
14
16
const staticClassFeatures =
15
17
require ( 'internal/deps/acorn-plugins/acorn-static-class-features/index' ) ;
16
- const { tokTypes : tt , Parser : AcornParser } = acorn ;
18
+
19
+ const { sendInspectorCommand } = require ( 'internal/util/inspector' ) ;
20
+
21
+ const {
22
+ ERR_INSPECTOR_NOT_AVAILABLE
23
+ } = require ( 'internal/errors' ) . codes ;
24
+
25
+ const {
26
+ clearLine,
27
+ cursorTo,
28
+ moveCursor,
29
+ } = require ( 'readline' ) ;
30
+
31
+ const { inspect } = require ( 'util' ) ;
32
+
33
+ const debug = require ( 'internal/util/debuglog' ) . debuglog ( 'repl' ) ;
34
+
35
+ const inspectOptions = {
36
+ depth : 1 ,
37
+ colors : false ,
38
+ compact : true ,
39
+ breakLength : Infinity
40
+ } ;
41
+ const inspectedOptions = inspect ( inspectOptions , { colors : false } ) ;
17
42
18
43
// If the error is that we've unexpectedly ended the input,
19
44
// then let the user try to recover by adding more input.
@@ -91,7 +116,144 @@ function isRecoverableError(e, code) {
91
116
}
92
117
}
93
118
119
+ function setupPreview ( repl , contextSymbol , bufferSymbol , active ) {
120
+ // Simple terminals can't handle previews.
121
+ if ( process . env . TERM === 'dumb' || ! active ) {
122
+ return { showInputPreview ( ) { } , clearPreview ( ) { } } ;
123
+ }
124
+
125
+ let preview = null ;
126
+ let lastPreview = '' ;
127
+
128
+ const clearPreview = ( ) => {
129
+ if ( preview !== null ) {
130
+ moveCursor ( repl . output , 0 , 1 ) ;
131
+ clearLine ( repl . output ) ;
132
+ moveCursor ( repl . output , 0 , - 1 ) ;
133
+ lastPreview = preview ;
134
+ preview = null ;
135
+ }
136
+ } ;
137
+
138
+ // This returns a code preview for arbitrary input code.
139
+ function getPreviewInput ( input , callback ) {
140
+ // For similar reasons as `defaultEval`, wrap expressions starting with a
141
+ // curly brace with parenthesis.
142
+ if ( input . startsWith ( '{' ) && ! input . endsWith ( ';' ) ) {
143
+ input = `(${ input } )` ;
144
+ }
145
+ sendInspectorCommand ( ( session ) => {
146
+ session . post ( 'Runtime.evaluate' , {
147
+ expression : input ,
148
+ throwOnSideEffect : true ,
149
+ timeout : 333 ,
150
+ contextId : repl [ contextSymbol ] ,
151
+ } , ( error , preview ) => {
152
+ if ( error ) {
153
+ callback ( error ) ;
154
+ return ;
155
+ }
156
+ const { result } = preview ;
157
+ if ( result . value !== undefined ) {
158
+ callback ( null , inspect ( result . value , inspectOptions ) ) ;
159
+ // Ignore EvalErrors, SyntaxErrors and ReferenceErrors. It is not clear
160
+ // where they came from and if they are recoverable or not. Other errors
161
+ // may be inspected.
162
+ } else if ( preview . exceptionDetails &&
163
+ ( result . className === 'EvalError' ||
164
+ result . className === 'SyntaxError' ||
165
+ result . className === 'ReferenceError' ) ) {
166
+ callback ( null , null ) ;
167
+ } else if ( result . objectId ) {
168
+ session . post ( 'Runtime.callFunctionOn' , {
169
+ functionDeclaration : `(v) => util.inspect(v, ${ inspectedOptions } )` ,
170
+ objectId : result . objectId ,
171
+ arguments : [ result ]
172
+ } , ( error , preview ) => {
173
+ if ( error ) {
174
+ callback ( error ) ;
175
+ } else {
176
+ callback ( null , preview . result . value ) ;
177
+ }
178
+ } ) ;
179
+ } else {
180
+ // Either not serializable or undefined.
181
+ callback ( null , result . unserializableValue || result . type ) ;
182
+ }
183
+ } ) ;
184
+ } , ( ) => callback ( new ERR_INSPECTOR_NOT_AVAILABLE ( ) ) ) ;
185
+ }
186
+
187
+ const showInputPreview = ( ) => {
188
+ // Prevent duplicated previews after a refresh.
189
+ if ( preview !== null ) {
190
+ return ;
191
+ }
192
+
193
+ const line = repl . line . trim ( ) ;
194
+
195
+ // Do not preview if the command is buffered or if the line is empty.
196
+ if ( repl [ bufferSymbol ] || line === '' ) {
197
+ return ;
198
+ }
199
+
200
+ getPreviewInput ( line , ( error , inspected ) => {
201
+ // Ignore the output if the value is identical to the current line and the
202
+ // former preview is not identical to this preview.
203
+ if ( ( line === inspected && lastPreview !== inspected ) ||
204
+ inspected === null ) {
205
+ return ;
206
+ }
207
+ if ( error ) {
208
+ debug ( 'Error while generating preview' , error ) ;
209
+ return ;
210
+ }
211
+ // Do not preview `undefined` if colors are deactivated or explicitly
212
+ // requested.
213
+ if ( inspected === 'undefined' &&
214
+ ( ! repl . useColors || repl . ignoreUndefined ) ) {
215
+ return ;
216
+ }
217
+
218
+ preview = inspected ;
219
+
220
+ // Limit the output to maximum 250 characters. Otherwise it becomes a)
221
+ // difficult to read and b) non terminal REPLs would visualize the whole
222
+ // output.
223
+ const maxColumns = MathMin ( repl . columns , 250 ) ;
224
+
225
+ if ( inspected . length > maxColumns ) {
226
+ inspected = `${ inspected . slice ( 0 , maxColumns - 6 ) } ...` ;
227
+ }
228
+ const lineBreakPos = inspected . indexOf ( '\n' ) ;
229
+ if ( lineBreakPos !== - 1 ) {
230
+ inspected = `${ inspected . slice ( 0 , lineBreakPos ) } ` ;
231
+ }
232
+ const result = repl . useColors ?
233
+ `\u001b[90m${ inspected } \u001b[39m` :
234
+ `// ${ inspected } ` ;
235
+
236
+ repl . output . write ( `\n${ result } ` ) ;
237
+ moveCursor ( repl . output , 0 , - 1 ) ;
238
+ cursorTo ( repl . output , repl . cursor + repl . _prompt . length ) ;
239
+ } ) ;
240
+ } ;
241
+
242
+ // Refresh prints the whole screen again and the preview will be removed
243
+ // during that procedure. Print the preview again. This also makes sure
244
+ // the preview is always correct after resizing the terminal window.
245
+ const tmpRefresh = repl . _refreshLine . bind ( repl ) ;
246
+ repl . _refreshLine = ( ) => {
247
+ preview = null ;
248
+ tmpRefresh ( ) ;
249
+ showInputPreview ( ) ;
250
+ } ;
251
+
252
+ return { showInputPreview, clearPreview } ;
253
+ }
254
+
94
255
module . exports = {
95
256
isRecoverableError,
96
- kStandaloneREPL : Symbol ( 'kStandaloneREPL' )
257
+ kStandaloneREPL : Symbol ( 'kStandaloneREPL' ) ,
258
+ setupPreview
97
259
} ;
0 commit comments