Bug 1604411 - Add expression variables in autocomplete popup. r=Honza.

Variables are retrieved from CodeMirror state and sent to the
webconsole actor, where the filtering is done.

Differential Revision: https://phabricator.services.mozilla.com/D57427

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Chevobbe 2019-12-18 11:29:06 +00:00
Родитель b3e862d056
Коммит b4e6628c95
9 изменённых файлов: 142 добавлений и 10 удалений

Просмотреть файл

@ -18,8 +18,9 @@ const {
* from the cache).
* @param {Array<String>} getterPath: Array representing the getter access (i.e.
* `a.b.c.d.` is described as ['a', 'b', 'c', 'd'] ).
* @param {Array<String>} expressionVars: Array of the variables defined in the expression.
*/
function autocompleteUpdate(force, getterPath) {
function autocompleteUpdate(force, getterPath, expressionVars) {
return async ({ dispatch, getState, webConsoleUI, hud }) => {
if (hud.inputHasSelection()) {
return dispatch(autocompleteClear());
@ -82,6 +83,7 @@ function autocompleteUpdate(force, getterPath) {
webConsoleFront,
authorizedEvaluations,
force,
expressionVars,
})
);
};
@ -133,6 +135,7 @@ function autocompleteDataFetch({
force,
webConsoleFront,
authorizedEvaluations,
expressionVars,
}) {
return ({ dispatch, webConsoleUI }) => {
const selectedNodeActor = webConsoleUI.getSelectedNodeActor();
@ -144,7 +147,8 @@ function autocompleteDataFetch({
undefined,
frameActorId,
selectedNodeActor,
authorizedEvaluations
authorizedEvaluations,
expressionVars
)
.then(data => {
dispatch(
@ -155,6 +159,7 @@ function autocompleteDataFetch({
frameActorId,
data,
authorizedEvaluations,
expressionVars,
})
);
})

Просмотреть файл

@ -241,7 +241,10 @@ class JSTerm extends Component {
autoCloseBrackets: false,
lineNumbers: this.props.editorMode,
lineWrapping: true,
mode: Editor.modes.js,
mode: {
name: "javascript",
globalVars: true,
},
styleActiveLine: false,
tabIndex: "0",
viewportMargin: Infinity,
@ -464,7 +467,11 @@ class JSTerm extends Component {
"Ctrl-Space": () => {
if (!this.autocompletePopup.isOpen) {
this.props.autocompleteUpdate(true);
this.props.autocompleteUpdate(
true,
null,
this._getExpressionVariables()
);
return null;
}
@ -770,6 +777,35 @@ class JSTerm extends Component {
}
}
/**
* Retrieve variable declared in the expression from the CodeMirror state, in order
* to display them in the autocomplete popup.
*/
_getExpressionVariables() {
const cm = this.editor.codeMirror;
const { state } = cm.getTokenAt(cm.getCursor());
const variables = [];
if (state.context) {
for (let c = state.context; c; c = c.prev) {
for (let v = c.vars; v; v = v.next) {
variables.push(v.name);
}
}
}
const keys = ["localVars", "globalVars"];
for (const key of keys) {
if (state[key]) {
for (let v = state[key]; v; v = v.next) {
variables.push(v.name);
}
}
}
return variables;
}
/**
* The editor "changes" event handler.
*/
@ -787,7 +823,7 @@ class JSTerm extends Component {
!isJsTermChangeOnly &&
(this.props.autocomplete || this.hasAutocompletionSuggestion())
) {
this.autocompleteUpdate();
this.autocompleteUpdate(false, null, this._getExpressionVariables());
}
this.lastInputValue = value;
this.terminalInputChanged(value);
@ -1281,8 +1317,8 @@ function mapDispatchToProps(dispatch) {
return {
updateHistoryPosition: (direction, expression) =>
dispatch(actions.updateHistoryPosition(direction, expression)),
autocompleteUpdate: (force, getterPath) =>
dispatch(actions.autocompleteUpdate(force, getterPath)),
autocompleteUpdate: (force, getterPath, expressionVars) =>
dispatch(actions.autocompleteUpdate(force, getterPath, expressionVars)),
autocompleteClear: () => dispatch(actions.autocompleteClear()),
evaluateExpression: expression =>
dispatch(actions.evaluateExpression(expression)),

Просмотреть файл

@ -211,6 +211,7 @@ skip-if = verify
[browser_jsterm_autocomplete_crossdomain_iframe.js]
[browser_jsterm_autocomplete_disabled.js]
[browser_jsterm_autocomplete_escape_key.js]
[browser_jsterm_autocomplete_expression_variables.js]
[browser_jsterm_autocomplete_extraneous_closing_brackets.js]
[browser_jsterm_autocomplete_getters_cache.js]
[browser_jsterm_autocomplete_getters_cancel.js]

Просмотреть файл

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that variable created in the expression are displayed in the autocomplete.
"use strict";
const TEST_URI = `data:text/html;charset=utf8,Test autocompletion for expression variables<script>
var testGlobal;
</script>`;
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
const { jsterm } = hud;
const { autocompletePopup } = jsterm;
await setInputValueForAutocompletion(
hud,
`
var testVar;
let testLet;
const testConst;
class testClass {}
function testFunc(testParam1, testParam2, ...testParamRest) {
var [testParamRestFirst] = testParamRest;
let {testDeconstruct1,testDeconstruct2, ...testDeconstructRest} = testParam1;
test`
);
is(
getPopupLabels(autocompletePopup).join("\n"),
[
"testClass",
"testConst",
"testDeconstruct1",
"testDeconstruct2",
"testDeconstructRest",
"testFunc",
"testGlobal",
"testLet",
"testParam1",
"testParam2",
"testParamRest",
"testParamRestFirst",
"testVar",
].join("\n"),
"Autocomplete popup displays both global and local variables"
);
});
function getPopupLabels(popup) {
return popup.getItems().map(item => item.label);
}

Просмотреть файл

@ -119,7 +119,7 @@ add_task(async function() {
"Hitting Enter quickly after a letter that should close the popup evaluates the expression"
);
onPopupOpened = autocompletePopup.once("popup-opened");
await setInputValueForAutocompletion(hud, "var docx = 1; doc");
await setInputValueForAutocompletion(hud, "doc");
await onPopupOpened;
checkInputCompletionValue(hud, "ument", "completeNode has expected value");

Просмотреть файл

@ -1346,7 +1346,8 @@ const WebConsoleActor = ActorClassWithSpec(webconsoleSpec, {
cursor,
frameActorId,
selectedNodeActor,
authorizedEvaluations
authorizedEvaluations,
expressionVars = []
) {
let dbgObject = null;
let environment = null;
@ -1393,6 +1394,7 @@ const WebConsoleActor = ActorClassWithSpec(webconsoleSpec, {
webconsoleActor: this,
selectedNodeActor,
authorizedEvaluations,
expressionVars,
});
if (result === null) {

Просмотреть файл

@ -179,6 +179,7 @@ const webconsoleSpecPrototype = {
frameActor: Arg(2, "nullable:string"),
selectedNodeActor: Arg(3, "nullable:string"),
authorizedEvaluations: Arg(4, "nullable:json"),
expressionVars: Arg(5, "nullable:json"),
},
response: RetVal("console.autocomplete"),
},

Просмотреть файл

@ -57,6 +57,9 @@ const MAX_AUTOCOMPLETIONS = (exports.MAX_AUTOCOMPLETIONS = 1500);
* evaluation result or create a debuggee value.
* - {String}: selectedNodeActor
* The actor id of the selected node in the inspector.
* - {Array<string>}: expressionVars
* Optional array containing variable defined in the expression. Those variables
* are extracted from CodeMirror state.
* @returns null or object
* If the inputValue is an unsafe getter and invokeUnsafeGetter is false, the
* following form is returned:
@ -88,6 +91,7 @@ function JSPropertyProvider({
authorizedEvaluations = [],
webconsoleActor,
selectedNodeActor,
expressionVars = [],
}) {
if (cursor === undefined) {
cursor = inputValue.length;
@ -242,10 +246,19 @@ function JSPropertyProvider({
let obj = dbgObject;
if (properties.length === 0) {
const environmentProperties = getMatchedPropsInEnvironment(env, search);
const expressionVariables = new Set(
expressionVars.filter(variableName => variableName.startsWith(matchProp))
);
for (const prop of environmentProperties) {
expressionVariables.add(prop);
}
return {
isElementAccess,
matchProp,
matches: getMatchedPropsInEnvironment(env, search),
matches: expressionVariables,
};
}

Просмотреть файл

@ -573,6 +573,27 @@ function runChecks(dbgObject, environment, sandbox) {
results = propertyProvider(`/*I'm a comment\n \t */\n\nt`);
test_has_result(results, "testObject");
info("Test local expression variables");
results = propertyProvider("b", { expressionVars: ["a", "b", "c"] });
test_has_result(results, "b");
Assert.equal(results.matches.has("a"), false);
Assert.equal(results.matches.has("c"), false);
info(
"Test that local expression variables are not included when accessing an object properties"
);
results = propertyProvider("testObject.prop", {
expressionVars: ["propLocal"],
});
Assert.equal(results.matches.has("propLocal"), false);
test_has_result(results, "propA");
results = propertyProvider("testObject['prop", {
expressionVars: ["propLocal"],
});
test_has_result(results, "'propA'");
Assert.equal(results.matches.has("propLocal"), false);
}
/**