Skip to content

Commit

Permalink
DevTools: Console: Provide autocompletions for Maps
Browse files Browse the repository at this point in the history
BUG=659152

Review-Url: https://codereview.chromium.org/2639703002
Cr-Original-Commit-Position: refs/heads/master@{#445811}
Committed: https://chromium.googlesource.com/chromium/src/+/a2ab5d688b1b01c2fa805a57762aa9d1e2c4499c
Review-Url: https://codereview.chromium.org/2639703002
Cr-Commit-Position: refs/heads/master@{#445928}
  • Loading branch information
JoelEinbinder authored and Commit bot committed Jan 25, 2017
1 parent 6a0cd76 commit ca89e20
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 14 deletions.
134 changes: 121 additions & 13 deletions front_end/components/JavaScriptAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ Components.JavaScriptAutocomplete.CompletionGroup;
* @return {!Promise<!UI.SuggestBox.Suggestions>}
*/
Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(text, query, force) {
var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text, true);
var mapCompletionsPromise = Components.JavaScriptAutocomplete._mapCompletions(text, query);
return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpression, query, force)
.then(completions => mapCompletionsPromise.then(mapCompletions => mapCompletions.concat(completions)));
};

/**
* @param {string} text
* @param {boolean=} allowEndingBracket
* @return {string}
*/
Components.JavaScriptAutocomplete._clipExpression = function(text, allowEndingBracket) {
var index;
var stopChars = new Set('=:({;,!+-*/&|^<>`'.split(''));
var whiteSpaceChars = new Set(' \r\n\t'.split(''));
var continueChars = new Set('[. \r\n\t'.split(''));

for (index = text.length - 1; index >= 0; index--) {
// Pass less stop characters to rangeOfWord so the range will be a more complete expression.
if (stopChars.has(text.charAt(index)))
break;
if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charAt(index - 1)))
Expand All @@ -35,16 +46,112 @@ Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(
if (character === ']')
bracketCount++;
// Allow an open bracket at the end for property completion.
if (character === '[' && index < clippedExpression.length - 1) {
if (character === '[' && (index < clippedExpression.length - 1 || !allowEndingBracket)) {
bracketCount--;
if (bracketCount < 0)
break;
}
index--;
}
clippedExpression = clippedExpression.substring(index + 1).trim();
return clippedExpression.substring(index + 1).trim();
};

/**
* @param {string} text
* @param {string} query
* @return {!Promise<!UI.SuggestBox.Suggestions>}
*/
Components.JavaScriptAutocomplete._mapCompletions = function(text, query) {
var mapMatch = text.match(/\.\s*(get|set|delete)\s*\(\s*$/);
var executionContext = UI.context.flavor(SDK.ExecutionContext);
if (!executionContext || !mapMatch)
return Promise.resolve([]);

var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text.substring(0, mapMatch.index));
var fulfill;
var promise = new Promise(x => fulfill = x);
executionContext.evaluate(clippedExpression, 'completion', true, true, false, false, false, evaluated);
return promise;

/**
* @param {?SDK.RemoteObject} result
* @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
*/
function evaluated(result, exceptionDetails) {
if (!result || !!exceptionDetails || result.subtype !== 'map') {
fulfill([]);
return;
}
result.getOwnPropertiesPromise(false).then(extractEntriesProperty);
}

/**
* @param {!{properties: ?Array<!SDK.RemoteObjectProperty>, internalProperties: ?Array<!SDK.RemoteObjectProperty>}} properties
*/
function extractEntriesProperty(properties) {
var internalProperties = properties.internalProperties || [];
var entriesProperty = internalProperties.find(property => property.name === '[[Entries]]');
if (!entriesProperty) {
fulfill([]);
return;
}
entriesProperty.value.callFunctionJSONPromise(getEntries).then(keysObj => gotKeys(Object.keys(keysObj)));
}

/**
* @suppressReceiverCheck
* @this {!Array<{key:?, value:?}>}
* @return {!Object}
*/
function getEntries() {
var result = {__proto__: null};
for (var i = 0; i < this.length; i++) {
if (typeof this[i].key === 'string')
result[this[i].key] = true;
}
return result;
}

return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpression, query, force);
/**
* @param {!Array<string>} rawKeys
*/
function gotKeys(rawKeys) {
var caseSensitivePrefix = [];
var caseInsensitivePrefix = [];
var caseSensitiveAnywhere = [];
var caseInsensitiveAnywhere = [];
var quoteChar = '"';
if (query.startsWith('\''))
quoteChar = '\'';
var endChar = ')';
if (mapMatch[0].indexOf('set') !== -1)
endChar = ', ';

var sorter = rawKeys.length < 1000 ? String.naturalOrderComparator : undefined;
var keys = rawKeys.sort(sorter).map(key => quoteChar + key + quoteChar + endChar);

for (var key of keys) {
if (key.length < query.length)
continue;
if (query.length && key.toLowerCase().indexOf(query.toLowerCase()) === -1)
continue;
// Substitute actual newlines with newline characters. @see crbug.com/498421
var title = key.split('\n').join('\\n');

if (key.startsWith(query))
caseSensitivePrefix.push({title: title, priority: 4});
else if (key.toLowerCase().startsWith(query.toLowerCase()))
caseInsensitivePrefix.push({title: title, priority: 3});
else if (key.indexOf(query) !== -1)
caseSensitiveAnywhere.push({title: title, priority: 2});
else
caseInsensitiveAnywhere.push({title: title, priority: 1});
}
var suggestions = caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
if (suggestions.length)
suggestions[0].subtitle = Common.UIString('Keys');
fulfill(suggestions);
}
};

/**
Expand Down Expand Up @@ -76,8 +183,8 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
if (!query && !expressionString && !force)
return Promise.resolve([]);

var fufill;
var promise = new Promise(x => fufill = x);
var fulfill;
var promise = new Promise(x => fulfill = x);
var selectedFrame = executionContext.debuggerModel.selectedCallFrame();
if (!expressionString && selectedFrame)
variableNamesInScopes(selectedFrame, receivedPropertyNames);
Expand All @@ -91,7 +198,7 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
*/
function evaluated(result, exceptionDetails) {
if (!result || !!exceptionDetails) {
fufill([]);
fulfill([]);
return;
}

Expand Down Expand Up @@ -223,7 +330,7 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
if (result && !exceptionDetails)
receivedPropertyNames(/** @type {!Object} */ (result.value));
else
fufill([]);
fulfill([]);
}

/**
Expand All @@ -232,7 +339,7 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
function receivedPropertyNames(object) {
executionContext.target().runtimeAgent().releaseObjectGroup('completion');
if (!object) {
fufill([]);
fulfill([]);
return;
}
var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} */ (object);
Expand Down Expand Up @@ -262,7 +369,7 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
];
propertyGroups.push({items: commandLineAPI});
}
fufill(Components.JavaScriptAutocomplete._completionsForQuery(
fulfill(Components.JavaScriptAutocomplete._completionsForQuery(
dotNotation, bracketNotation, expressionString, query, propertyGroups));
}
};
Expand Down Expand Up @@ -296,7 +403,7 @@ Components.JavaScriptAutocomplete._completionsForQuery = function(
var result = [];
var lastGroupTitle;
for (var group of propertyGroups) {
group.items.sort(itemComparator);
group.items.sort(itemComparator.bind(null, group.items.length > 1000));
var caseSensitivePrefix = [];
var caseInsensitivePrefix = [];
var caseSensitiveAnywhere = [];
Expand Down Expand Up @@ -340,17 +447,18 @@ Components.JavaScriptAutocomplete._completionsForQuery = function(
return result;

/**
* @param {boolean} naturalOrder
* @param {string} a
* @param {string} b
* @return {number}
*/
function itemComparator(a, b) {
function itemComparator(naturalOrder, a, b) {
var aStartsWithUnderscore = a.startsWith('_');
var bStartsWithUnderscore = b.startsWith('_');
if (aStartsWithUnderscore && !bStartsWithUnderscore)
return 1;
if (bStartsWithUnderscore && !aStartsWithUnderscore)
return -1;
return String.naturalOrderComparator(a, b);
return naturalOrder ? String.naturalOrderComparator(a, b) : a.localeCompare(b);
}
};
2 changes: 1 addition & 1 deletion front_end/console/ConsolePrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ Console.ConsolePrompt = class extends UI.Widget {

var excludedTokens = new Set(['js-comment', 'js-string-2', 'js-def']);
var trimmedBefore = before.trim();
if (!trimmedBefore.endsWith('['))
if (!trimmedBefore.endsWith('[') && !trimmedBefore.match(/\.\s*(get|set|delete)\s*\(\s*$/))
excludedTokens.add('js-string');
if (!trimmedBefore.endsWith('.'))
excludedTokens.add('js-property');
Expand Down

0 comments on commit ca89e20

Please sign in to comment.