11'use strict' ;
22
33const {
4+ MathMin,
45 Symbol,
56} = primordials ;
67
7- const acorn = require ( 'internal/deps/acorn/acorn/dist/acorn' ) ;
8+ const { tokTypes : tt , Parser : AcornParser } =
9+ require ( 'internal/deps/acorn/acorn/dist/acorn' ) ;
810const privateMethods =
911 require ( 'internal/deps/acorn-plugins/acorn-private-methods/index' ) ;
1012const classFields =
@@ -13,7 +15,30 @@ const numericSeparator =
1315 require ( 'internal/deps/acorn-plugins/acorn-numeric-separator/index' ) ;
1416const staticClassFeatures =
1517 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 } ) ;
1742
1843// If the error is that we've unexpectedly ended the input,
1944// then let the user try to recover by adding more input.
@@ -91,7 +116,144 @@ function isRecoverableError(e, code) {
91116 }
92117}
93118
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+
94255module . exports = {
95256 isRecoverableError,
96- kStandaloneREPL : Symbol ( 'kStandaloneREPL' )
257+ kStandaloneREPL : Symbol ( 'kStandaloneREPL' ) ,
258+ setupPreview
97259} ;
0 commit comments