repl: show lexically scoped vars in tab completion
Use the V8 inspector protocol, if available, to query the list of lexically scoped variables (defined with `let`, `const` or `class`). PR-URL: https://github.com/nodejs/node/pull/16591 Fixes: https://github.com/nodejs/node/issues/983 Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
parent
d1d6b54b69
commit
416c0ec952
25
lib/internal/util/inspector.js
Normal file
25
lib/internal/util/inspector.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const hasInspector = process.config.variables.v8_enable_inspector === 1;
|
||||
const inspector = hasInspector ? require('inspector') : undefined;
|
||||
|
||||
let session;
|
||||
|
||||
function sendInspectorCommand(cb, onError) {
|
||||
if (!hasInspector) return onError();
|
||||
if (session === undefined) session = new inspector.Session();
|
||||
try {
|
||||
session.connect();
|
||||
try {
|
||||
return cb(session);
|
||||
} finally {
|
||||
session.disconnect();
|
||||
}
|
||||
} catch (e) {
|
||||
return onError();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendInspectorCommand
|
||||
};
|
28
lib/repl.js
28
lib/repl.js
@ -59,6 +59,7 @@ const Module = require('module');
|
||||
const domain = require('domain');
|
||||
const debug = util.debuglog('repl');
|
||||
const errors = require('internal/errors');
|
||||
const { sendInspectorCommand } = require('internal/util/inspector');
|
||||
|
||||
const parentModule = module;
|
||||
const replMap = new WeakMap();
|
||||
@ -76,6 +77,7 @@ for (var n = 0; n < GLOBAL_OBJECT_PROPERTIES.length; n++) {
|
||||
GLOBAL_OBJECT_PROPERTIES[n];
|
||||
}
|
||||
const kBufferedCommandSymbol = Symbol('bufferedCommand');
|
||||
const kContextId = Symbol('contextId');
|
||||
|
||||
try {
|
||||
// hack for require.resolve("./relative") to work properly.
|
||||
@ -158,6 +160,8 @@ function REPLServer(prompt,
|
||||
self.last = undefined;
|
||||
self.breakEvalOnSigint = !!breakEvalOnSigint;
|
||||
self.editorMode = false;
|
||||
// Context id for use with the inspector protocol.
|
||||
self[kContextId] = undefined;
|
||||
|
||||
// just for backwards compat, see github.com/joyent/node/pull/7127
|
||||
self.rli = this;
|
||||
@ -755,7 +759,16 @@ REPLServer.prototype.createContext = function() {
|
||||
if (this.useGlobal) {
|
||||
context = global;
|
||||
} else {
|
||||
context = vm.createContext();
|
||||
sendInspectorCommand((session) => {
|
||||
session.post('Runtime.enable');
|
||||
session.on('Runtime.executionContextCreated', ({ params }) => {
|
||||
this[kContextId] = params.context.id;
|
||||
});
|
||||
context = vm.createContext();
|
||||
session.post('Runtime.disable');
|
||||
}, () => {
|
||||
context = vm.createContext();
|
||||
});
|
||||
context.global = context;
|
||||
const _console = new Console(this.outputStream);
|
||||
Object.defineProperty(context, 'console', {
|
||||
@ -890,6 +903,18 @@ function filteredOwnPropertyNames(obj) {
|
||||
return Object.getOwnPropertyNames(obj).filter(intFilter);
|
||||
}
|
||||
|
||||
function getGlobalLexicalScopeNames(contextId) {
|
||||
return sendInspectorCommand((session) => {
|
||||
let names = [];
|
||||
session.post('Runtime.globalLexicalScopeNames', {
|
||||
executionContextId: contextId
|
||||
}, (error, result) => {
|
||||
if (!error) names = result.names;
|
||||
});
|
||||
return names;
|
||||
}, () => []);
|
||||
}
|
||||
|
||||
REPLServer.prototype.complete = function() {
|
||||
this.completer.apply(this, arguments);
|
||||
};
|
||||
@ -1053,6 +1078,7 @@ function complete(line, callback) {
|
||||
// If context is instance of vm.ScriptContext
|
||||
// Get global vars synchronously
|
||||
if (this.useGlobal || vm.isContext(this.context)) {
|
||||
completionGroups.push(getGlobalLexicalScopeNames(this[kContextId]));
|
||||
var contextProto = this.context;
|
||||
while (contextProto = Object.getPrototypeOf(contextProto)) {
|
||||
completionGroups.push(
|
||||
|
1
node.gyp
1
node.gyp
@ -129,6 +129,7 @@
|
||||
'lib/internal/url.js',
|
||||
'lib/internal/util.js',
|
||||
'lib/internal/util/comparisons.js',
|
||||
'lib/internal/util/inspector.js',
|
||||
'lib/internal/util/types.js',
|
||||
'lib/internal/http2/core.js',
|
||||
'lib/internal/http2/compat.js',
|
||||
|
34
test/parallel/test-repl-inspector.js
Normal file
34
test/parallel/test-repl-inspector.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const repl = require('repl');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
// This test verifies that the V8 inspector API is usable in the REPL.
|
||||
|
||||
const putIn = new common.ArrayStream();
|
||||
let output = '';
|
||||
putIn.write = function(data) {
|
||||
output += data;
|
||||
};
|
||||
|
||||
const testMe = repl.start('', putIn);
|
||||
|
||||
putIn.run(['const myVariable = 42']);
|
||||
|
||||
testMe.complete('myVar', common.mustCall((error, data) => {
|
||||
assert.deepStrictEqual(data, [['myVariable'], 'myVar']);
|
||||
}));
|
||||
|
||||
putIn.run([
|
||||
'const inspector = require("inspector")',
|
||||
'const session = new inspector.Session()',
|
||||
'session.connect()',
|
||||
'session.post("Runtime.evaluate", { expression: "1 + 1" }, console.log)',
|
||||
'session.disconnect()'
|
||||
]);
|
||||
|
||||
assert(output.includes(
|
||||
"null { result: { type: 'number', value: 2, description: '2' } }"));
|
@ -24,6 +24,7 @@
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const hasInspector = process.config.variables.v8_enable_inspector === 1;
|
||||
|
||||
// We have to change the directory to ../fixtures before requiring repl
|
||||
// in order to make the tests for completion of node_modules work properly
|
||||
@ -529,3 +530,23 @@ editorStream.run(['.editor']);
|
||||
editor.completer('var log = console.l', common.mustCall((error, data) => {
|
||||
assert.deepStrictEqual(data, [['console.log'], 'console.l']);
|
||||
}));
|
||||
|
||||
{
|
||||
// tab completion of lexically scoped variables
|
||||
const stream = new common.ArrayStream();
|
||||
const testRepl = repl.start({ stream });
|
||||
|
||||
stream.run([`
|
||||
let lexicalLet = true;
|
||||
const lexicalConst = true;
|
||||
class lexicalKlass {}
|
||||
`]);
|
||||
|
||||
['Let', 'Const', 'Klass'].forEach((type) => {
|
||||
const query = `lexical${type[0]}`;
|
||||
const expected = hasInspector ? [[`lexical${type}`], query] : [];
|
||||
testRepl.complete(query, common.mustCall((error, data) => {
|
||||
assert.deepStrictEqual(data, expected);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user