Skip to content

Commit 1b0af18

Browse files
committed
fixup!
1 parent 679e65d commit 1b0af18

2 files changed

Lines changed: 165 additions & 138 deletions

File tree

lib/internal/repl/utils.js

Lines changed: 142 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)