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