Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: improve repl preview #33282

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 10 additions & 48 deletions lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
ObjectDefineProperty,
ObjectPrototypeHasOwnProperty,
SafeMap,
} = primordials;
const {
Expand Down Expand Up @@ -109,55 +110,17 @@ function stripBOM(content) {
return content;
}

const builtinLibs = [
'assert',
'async_hooks',
'buffer',
'child_process',
'cluster',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'http2',
'https',
'net',
'os',
'path',
'perf_hooks',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'tls',
'trace_events',
'tty',
'url',
'util',
'v8',
'vm',
'worker_threads',
'zlib',
];

if (internalBinding('config').experimentalWasi) {
builtinLibs.push('wasi');
builtinLibs.sort();
}

if (typeof internalBinding('inspector').open === 'function') {
builtinLibs.push('inspector');
builtinLibs.sort();
}

function addBuiltinLibsToObject(object) {
// Make built-in modules available directly (loaded lazily).
builtinLibs.forEach((name) => {
const { builtinModules } = require('internal/modules/cjs/loader').Module;
builtinModules.forEach((name) => {
// Neither add underscored modules, nor ones that contain slashes (e.g.,
// 'fs/promises') or ones that are already defined.
if (name.startsWith('_') ||
name.includes('/') ||
ObjectPrototypeHasOwnProperty(object, name)) {
return;
}
// Goals of this mechanism are:
// - Lazy loading of built-in modules
// - Having all built-in modules available as non-enumerable properties
Expand Down Expand Up @@ -203,7 +166,6 @@ function normalizeReferrerURL(referrer) {

module.exports = {
addBuiltinLibsToObject,
builtinLibs,
loadNativeModule,
makeRequireFunction,
normalizeReferrerURL,
Expand Down
67 changes: 55 additions & 12 deletions lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const previewOptions = {
showHidden: false
};

const REPL_MODE_STRICT = Symbol('repl-strict');

// If the error is that we've unexpectedly ended the input,
// then let the user try to recover by adding more input.
// Note: `e` (the original exception) is not used by the current implementation,
Expand Down Expand Up @@ -136,8 +138,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
let previewCompletionCounter = 0;
let completionPreview = null;

let hasCompletions = false;

let wrapped = false;

let escaped = null;

function getPreviewPos() {
const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`);
const cursorPos = repl.line.length !== repl.cursor ?
Expand All @@ -146,7 +152,13 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
return { displayPos, cursorPos };
}

const clearPreview = () => {
function isCursorAtInputEnd() {
const { cursorPos, displayPos } = getPreviewPos();
return cursorPos.rows === displayPos.rows &&
cursorPos.cols === displayPos.cols;
}

const clearPreview = (key) => {
if (inputPreview !== null) {
const { displayPos, cursorPos } = getPreviewPos();
const rows = displayPos.rows - cursorPos.rows + 1;
Expand Down Expand Up @@ -179,8 +191,23 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
cursorTo(repl.output, pos.cursorPos.cols);
moveCursor(repl.output, 0, -rows);
}
if (!key.ctrl && !key.shift) {
if (key.name === 'escape') {
if (escaped === null && key.meta) {
escaped = repl.line;
}
} else if ((key.name === 'return' || key.name === 'enter') &&
!key.meta &&
escaped !== repl.line &&
isCursorAtInputEnd()) {
repl._insertString(completionPreview);
}
}
completionPreview = null;
}
if (escaped !== repl.line) {
escaped = null;
}
};

function showCompletionPreview(line, insertPreview) {
Expand All @@ -206,6 +233,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
return;
}

hasCompletions = true;

// If there is a common prefix to all matches, then apply that portion.
const completions = rawCompletions.filter((e) => e);
const prefix = commonPrefix(completions);
Expand Down Expand Up @@ -242,6 +271,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
});
}

function isInStrictMode(repl) {
return repl.replMode === REPL_MODE_STRICT || process.execArgv
.map((e) => e.toLowerCase().replace(/_/g, '-'))
.includes('--use-strict');
}

// This returns a code preview for arbitrary input code.
function getInputPreview(input, callback) {
// For similar reasons as `defaultEval`, wrap expressions starting with a
Expand Down Expand Up @@ -269,8 +304,11 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
// may be inspected.
} else if (preview.exceptionDetails &&
(result.className === 'EvalError' ||
result.className === 'SyntaxError' ||
result.className === 'ReferenceError')) {
result.className === 'SyntaxError' ||
// Report ReferenceError in case the strict mode is active
// for input that has no completions.
(result.className === 'ReferenceError' &&
(hasCompletions || !isInStrictMode(repl))))) {
callback(null, null);
} else if (result.objectId) {
// The writer options might change and have influence on the inspect
Expand Down Expand Up @@ -316,14 +354,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
return;
}

hasCompletions = false;

// Add the autocompletion preview.
// TODO(BridgeAR): Trigger the input preview after the completion preview.
// That way it's possible to trigger the input prefix including the
// potential completion suffix. To do so, we also have to change the
// behavior of `enter` and `escape`:
// Enter should automatically add the suffix to the current line as long as
// escape was not pressed. We might even remove the preview in case any
// cursor movement is triggered.
const insertPreview = false;
showCompletionPreview(repl.line, insertPreview);

Expand Down Expand Up @@ -397,9 +430,17 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
moveCursor(repl.output, 0, -rows - 1);
};

getInputPreview(line, inputPreviewCallback);
let previewLine = line;

if (completionPreview !== null &&
isCursorAtInputEnd() &&
escaped !== repl.line) {
previewLine += completionPreview;
}

getInputPreview(previewLine, inputPreviewCallback);
if (wrapped) {
getInputPreview(line, inputPreviewCallback);
getInputPreview(previewLine, inputPreviewCallback);
}
wrapped = false;
};
Expand Down Expand Up @@ -679,6 +720,8 @@ function setupReverseSearch(repl) {
}

module.exports = {
REPL_MODE_SLOPPY: Symbol('repl-sloppy'),
REPL_MODE_STRICT,
isRecoverableError,
kStandaloneREPL: Symbol('kStandaloneREPL'),
setupPreview,
Expand Down
Loading