@@ -147,14 +147,13 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
147147 }
148148
149149 let inputPreview = null ;
150+ let previewToken = 0 ;
150151
151152 let previewCompletionCounter = 0 ;
152153 let completionPreview = null ;
153154
154155 let hasCompletions = false ;
155156
156- let wrapped = false ;
157-
158157 let escaped = null ;
159158
160159 function getPreviewPos ( ) {
@@ -172,6 +171,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
172171 }
173172
174173 const clearPreview = ( key ) => {
174+ // Invalidate any preview computation that is still in flight.
175+ previewToken ++ ;
175176 if ( inputPreview !== null ) {
176177 const { displayPos, cursorPos } = getPreviewPos ( ) ;
177178 const rows = displayPos . rows - cursorPos . rows + 1 ;
@@ -222,65 +223,73 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
222223 }
223224 } ;
224225
225- function showCompletionPreview ( line , insertPreview ) {
226+ async function showCompletionPreview ( line , insertPreview ) {
226227 previewCompletionCounter ++ ;
227228
228229 const count = previewCompletionCounter ;
229230
230- repl . completer ( line , ( error , data ) => {
231- // Tab completion might be async and the result might already be outdated.
232- if ( count !== previewCompletionCounter ) {
233- return ;
234- }
231+ let data ;
232+ try {
233+ data = await new Promise ( ( resolve , reject ) => {
234+ repl . completer ( line , ( error , result ) => {
235+ if ( error ) {
236+ reject ( error ) ;
237+ } else {
238+ resolve ( result ) ;
239+ }
240+ } ) ;
241+ } ) ;
242+ } catch ( error ) {
243+ debug ( 'Error while generating completion preview' , error ) ;
244+ return ;
245+ }
235246
236- if ( error ) {
237- debug ( 'Error while generating completion preview' , error ) ;
238- return ;
239- }
247+ if ( count !== previewCompletionCounter ) {
248+ return ;
249+ }
240250
241- // Result and the text that was completed.
242- const { 0 : rawCompletions , 1 : completeOn } = data ;
251+ // Result and the text that was completed.
252+ const { 0 : rawCompletions , 1 : completeOn } = data ;
243253
244- if ( ! rawCompletions || rawCompletions . length === 0 ) {
245- return ;
246- }
254+ if ( ! rawCompletions || rawCompletions . length === 0 ) {
255+ return ;
256+ }
247257
248- hasCompletions = true ;
258+ hasCompletions = true ;
249259
250- // If there is a common prefix to all matches, then apply that portion.
251- const completions = ArrayPrototypeFilter ( rawCompletions , Boolean ) ;
252- const prefix = commonPrefix ( completions ) ;
260+ // If there is a common prefix to all matches, then apply that portion.
261+ const completions = ArrayPrototypeFilter ( rawCompletions , Boolean ) ;
262+ const prefix = commonPrefix ( completions ) ;
253263
254- // No common prefix found.
255- if ( prefix . length <= completeOn . length ) {
256- return ;
257- }
264+ // No common prefix found.
265+ if ( prefix . length <= completeOn . length ) {
266+ return ;
267+ }
258268
259- const suffix = StringPrototypeSlice ( prefix , completeOn . length ) ;
269+ const suffix = StringPrototypeSlice ( prefix , completeOn . length ) ;
260270
261- if ( insertPreview ) {
262- repl . _insertString ( suffix ) ;
263- return ;
264- }
271+ if ( insertPreview ) {
272+ repl . _insertString ( suffix ) ;
273+ return ;
274+ }
265275
266- completionPreview = suffix ;
276+ completionPreview = suffix ;
267277
268- const result = repl . useColors ?
269- `\u001b[90m${ suffix } \u001b[39m` :
270- ` // ${ suffix } ` ;
278+ const result = repl . useColors ?
279+ `\u001b[90m${ suffix } \u001b[39m` :
280+ ` // ${ suffix } ` ;
271281
272- const { cursorPos, displayPos } = getPreviewPos ( ) ;
273- if ( repl . line . length !== repl . cursor ) {
274- cursorTo ( repl . output , displayPos . cols ) ;
275- moveCursor ( repl . output , 0 , displayPos . rows - cursorPos . rows ) ;
276- }
277- repl . output . write ( result ) ;
278- cursorTo ( repl . output , cursorPos . cols ) ;
279- const totalLine = `${ repl . getPrompt ( ) } ${ repl . line } ${ suffix } ` ;
280- const newPos = repl . _getDisplayPos ( totalLine ) ;
281- const rows = newPos . rows - cursorPos . rows - ( newPos . cols === 0 ? 1 : 0 ) ;
282- moveCursor ( repl . output , 0 , - rows ) ;
283- } ) ;
282+ const { cursorPos, displayPos } = getPreviewPos ( ) ;
283+ if ( repl . line . length !== repl . cursor ) {
284+ cursorTo ( repl . output , displayPos . cols ) ;
285+ moveCursor ( repl . output , 0 , displayPos . rows - cursorPos . rows ) ;
286+ }
287+ repl . output . write ( result ) ;
288+ cursorTo ( repl . output , cursorPos . cols ) ;
289+ const totalLine = `${ repl . getPrompt ( ) } ${ repl . line } ${ suffix } ` ;
290+ const newPos = repl . _getDisplayPos ( totalLine ) ;
291+ const rows = newPos . rows - cursorPos . rows - ( newPos . cols === 0 ? 1 : 0 ) ;
292+ moveCursor ( repl . output , 0 , - rows ) ;
284293 }
285294
286295 function isInStrictMode ( repl ) {
@@ -295,77 +304,77 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
295304 }
296305
297306 // This returns a code preview for arbitrary input code.
298- function getInputPreview ( input , callback ) {
307+ async function getInputPreview ( input , allowWrap ) {
299308 // For similar reasons as `defaultEval`, wrap expressions starting with a
300309 // curly brace with parenthesis.
301- if ( ! wrapped && input [ 0 ] === '{' && input [ input . length - 1 ] !== ';' && isValidSyntax ( input ) ) {
310+ let didWrap = false ;
311+ if ( allowWrap && input [ 0 ] === '{' && input [ input . length - 1 ] !== ';' && isValidSyntax ( input ) ) {
302312 input = `(${ input } )` ;
303- wrapped = true ;
313+ didWrap = true ;
304314 }
305315
306316 const inspector = getReplInspector ( repl ) ;
307- ( async ( ) => {
308- let preview ;
317+ let preview ;
318+ try {
319+ preview = await inspector . post ( 'Runtime.evaluate' , {
320+ expression : input ,
321+ throwOnSideEffect : true ,
322+ timeout : 333 ,
323+ contextId : repl [ contextSymbol ] ,
324+ } ) ;
325+ } catch {
326+ return { inspected : null , didWrap } ;
327+ }
328+ const { result } = preview ;
329+ if ( result . value !== undefined ) {
330+ return { inspected : inspect ( result . value , previewOptions ) , didWrap } ;
331+ }
332+ // Ignore EvalErrors, SyntaxErrors and ReferenceErrors. It is not clear
333+ // where they came from and if they are recoverable or not. Other errors
334+ // may be inspected.
335+ if ( preview . exceptionDetails &&
336+ ( result . className === 'EvalError' ||
337+ result . className === 'SyntaxError' ||
338+ // Report ReferenceError in case the strict mode is active
339+ // for input that has no completions.
340+ ( result . className === 'ReferenceError' &&
341+ ( hasCompletions || ! isInStrictMode ( repl ) ) ) ) ) {
342+ return { inspected : null , didWrap } ;
343+ }
344+ if ( result . objectId ) {
345+ // The writer options might change and have influence on the inspect
346+ // output. The user might change e.g., `showProxy`, `getters` or
347+ // `showHidden`. Use `inspect` instead of `JSON.stringify` to keep
348+ // `Infinity` and similar intact.
349+ const inspectOptions = inspect ( {
350+ ...repl . writer . options ,
351+ colors : false ,
352+ depth : 1 ,
353+ compact : true ,
354+ breakLength : Infinity ,
355+ } , previewOptions ) ;
309356 try {
310- preview = await inspector . post ( 'Runtime.evaluate' , {
311- expression : input ,
312- throwOnSideEffect : true ,
313- timeout : 333 ,
314- contextId : repl [ contextSymbol ] ,
357+ const fnResult = await inspector . post ( 'Runtime.callFunctionOn' , {
358+ functionDeclaration :
359+ `(v) =>
360+ Reflect
361+ .getOwnPropertyDescriptor(globalThis, 'util')
362+ .get().inspect(v, ${ inspectOptions } )` ,
363+ objectId : result . objectId ,
364+ arguments : [ result ] ,
315365 } ) ;
316- } catch ( error ) {
317- callback ( error ) ;
318- return ;
366+ return { inspected : fnResult . result . value , didWrap } ;
367+ } catch {
368+ return { inspected : null , didWrap } ;
319369 }
320- const { result } = preview ;
321- if ( result . value !== undefined ) {
322- callback ( null , inspect ( result . value , previewOptions ) ) ;
323- // Ignore EvalErrors, SyntaxErrors and ReferenceErrors. It is not clear
324- // where they came from and if they are recoverable or not. Other errors
325- // may be inspected.
326- } else if ( preview . exceptionDetails &&
327- ( result . className === 'EvalError' ||
328- result . className === 'SyntaxError' ||
329- // Report ReferenceError in case the strict mode is active
330- // for input that has no completions.
331- ( result . className === 'ReferenceError' &&
332- ( hasCompletions || ! isInStrictMode ( repl ) ) ) ) ) {
333- callback ( null , null ) ;
334- } else if ( result . objectId ) {
335- // The writer options might change and have influence on the inspect
336- // output. The user might change e.g., `showProxy`, `getters` or
337- // `showHidden`. Use `inspect` instead of `JSON.stringify` to keep
338- // `Infinity` and similar intact.
339- const inspectOptions = inspect ( {
340- ...repl . writer . options ,
341- colors : false ,
342- depth : 1 ,
343- compact : true ,
344- breakLength : Infinity ,
345- } , previewOptions ) ;
346- try {
347- const fnResult = await inspector . post ( 'Runtime.callFunctionOn' , {
348- functionDeclaration :
349- `(v) =>
350- Reflect
351- .getOwnPropertyDescriptor(globalThis, 'util')
352- .get().inspect(v, ${ inspectOptions } )` ,
353- objectId : result . objectId ,
354- arguments : [ result ] ,
355- } ) ;
356- callback ( null , fnResult . result . value ) ;
357- } catch ( error ) {
358- callback ( error ) ;
359- }
360- } else {
361- // Either not serializable or undefined.
362- callback ( null , result . unserializableValue || result . type ) ;
363- }
364- } ) ( ) ;
370+ }
371+ // Either not serializable or undefined.
372+ return { inspected : result . unserializableValue || result . type , didWrap } ;
365373 }
366374
367- const showPreview = ( showCompletion = true ) => {
368- // Prevent duplicated previews after a refresh or in a multiline command.
375+ const showPreview = async ( showCompletion = true ) => {
376+ // Prevent previews after a refresh, in a multiline command, or when
377+ // previews are disabled.
369378 if ( inputPreview !== null ||
370379 repl [ kIsMultiline ] ||
371380 ! repl . isCompletionEnabled ||
@@ -382,33 +391,18 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
382391
383392 hasCompletions = false ;
384393
385- // Add the autocompletion preview.
386- if ( showCompletion ) {
387- const insertPreview = false ;
388- showCompletionPreview ( repl . line , insertPreview ) ;
389- }
394+ const token = ++ previewToken ;
390395
391- // Do not preview if the command is buffered.
392- if ( repl [ bufferSymbol ] ) {
393- return ;
394- }
395-
396- const inputPreviewCallback = ( error , inspected ) => {
396+ const renderInputPreview = ( inspected ) => {
397397 if ( inspected == null ) {
398398 return ;
399399 }
400400
401- wrapped = false ;
402-
403401 // Ignore the output if the value is identical to the current line.
404402 if ( line === inspected ) {
405403 return ;
406404 }
407405
408- if ( error ) {
409- debug ( 'Error while generating preview' , error ) ;
410- return ;
411- }
412406 // Do not preview `undefined` if colors are deactivated or explicitly
413407 // requested.
414408 if ( inspected === 'undefined' &&
@@ -462,6 +456,19 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
462456 moveCursor ( repl . output , 0 , - rows - 1 ) ;
463457 } ;
464458
459+ // Add the autocompletion preview first, so the resolved completion can be
460+ // appended to the previewed expression below.
461+ if ( showCompletion ) {
462+ const insertPreview = false ;
463+ await showCompletionPreview ( repl . line , insertPreview ) ;
464+ }
465+
466+ // Bail if a newer keystroke superseded this preview while the completion
467+ // was being resolved, or if the command is buffered.
468+ if ( token !== previewToken || repl [ bufferSymbol ] ) {
469+ return ;
470+ }
471+
465472 let previewLine = line ;
466473
467474 if ( completionPreview !== null &&
@@ -470,11 +477,19 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
470477 previewLine += completionPreview ;
471478 }
472479
473- getInputPreview ( previewLine , inputPreviewCallback ) ;
474- if ( wrapped ) {
475- getInputPreview ( previewLine , inputPreviewCallback ) ;
480+ // Object literals (`{...}`) are first evaluated wrapped in parentheses; if
481+ // that yields no preview, fall back once to evaluating the input unwrapped.
482+ let { inspected, didWrap } = await getInputPreview ( previewLine , true ) ;
483+ if ( inspected == null && didWrap ) {
484+ ( { inspected } = await getInputPreview ( previewLine , false ) ) ;
476485 }
477- wrapped = false ;
486+
487+ // Ignore the result if a newer keystroke superseded this preview.
488+ if ( token !== previewToken ) {
489+ return ;
490+ }
491+
492+ renderInputPreview ( inspected ) ;
478493 } ;
479494
480495 // -------------------------------------------------------------------------//
0 commit comments