| Index: third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js
|
| diff --git a/third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js b/third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js
|
| index b17ac6f8ec5ddd70ff4a37a9879c3e3a3195e138..56aac0ccf049102d266bc8b9ffa432079bde1721 100644
|
| --- a/third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js
|
| +++ b/third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js
|
| @@ -4,6 +4,9 @@
|
|
|
| Components.JavaScriptAutocomplete = {};
|
|
|
| +/** @typedef {{title:(string|undefined), items:Array<string>}} */
|
| +Components.JavaScriptAutocomplete.CompletionGroup;
|
| +
|
| /**
|
| * @param {string} text
|
| * @param {string} query
|
| @@ -39,13 +42,12 @@ Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(
|
| return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpression, query, force);
|
| };
|
|
|
| -
|
| /**
|
| - * @param {string} expressionString
|
| - * @param {string} query
|
| - * @param {boolean=} force
|
| - * @return {!Promise<!UI.SuggestBox.Suggestions>}
|
| - */
|
| + * @param {string} expressionString
|
| + * @param {string} query
|
| + * @param {boolean=} force
|
| + * @return {!Promise<!UI.SuggestBox.Suggestions>}
|
| + */
|
| Components.JavaScriptAutocomplete.completionsForExpression = function(expressionString, query, force) {
|
| var executionContext = UI.context.flavor(SDK.ExecutionContext);
|
| if (!executionContext)
|
| @@ -75,7 +77,7 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| var fufill;
|
| var promise = new Promise(x => fufill = x);
|
| if (!expressionString && executionContext.debuggerModel.selectedCallFrame())
|
| - executionContext.debuggerModel.selectedCallFrame().variableNames(receivedPropertyNames);
|
| + variableNamesInScopes(executionContext.debuggerModel.selectedCallFrame(), receivedPropertyNames);
|
| else
|
| executionContext.evaluate(expressionString, 'completion', true, true, false, false, false, evaluated);
|
|
|
| @@ -129,23 +131,32 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| else
|
| object = this;
|
|
|
| - var resultSet = {__proto__: null};
|
| + var result = [];
|
| try {
|
| for (var o = object; o; o = Object.getPrototypeOf(o)) {
|
| if ((type === 'array' || type === 'typedarray') && o === object && ArrayBuffer.isView(o) && o.length > 9999)
|
| continue;
|
| +
|
| + var group = {items: [], __proto__: null};
|
| + try {
|
| + if (typeof o === 'object' && o.constructor && o.constructor.name)
|
| + group.title = o.constructor.name;
|
| + } catch (ee) {
|
| + // we could break upon cross origin check.
|
| + }
|
| + result[result.length] = group;
|
| var names = Object.getOwnPropertyNames(o);
|
| var isArray = Array.isArray(o);
|
| for (var i = 0; i < names.length; ++i) {
|
| // Skip array elements indexes.
|
| if (isArray && /^[0-9]/.test(names[i]))
|
| continue;
|
| - resultSet[names[i]] = true;
|
| + group.items[group.items.length] = names[i];
|
| }
|
| }
|
| } catch (e) {
|
| }
|
| - return resultSet;
|
| + return result;
|
| }
|
|
|
| /**
|
| @@ -168,6 +179,35 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| }
|
|
|
| /**
|
| + * @param {!SDK.DebuggerModel.CallFrame} callFrame
|
| + * @param {function(!Array<!Components.JavaScriptAutocomplete.CompletionGroup>)} callback
|
| + */
|
| + function variableNamesInScopes(callFrame, callback) {
|
| + var result = [{items: ['this']}];
|
| +
|
| + /**
|
| + * @param {string} name
|
| + * @param {?Array<!SDK.RemoteObjectProperty>} properties
|
| + */
|
| + function propertiesCollected(name, properties) {
|
| + var group = {title: name, items: []};
|
| + result.push(group);
|
| + for (var i = 0; properties && i < properties.length; ++i)
|
| + group.items.push(properties[i].name);
|
| + if (--pendingRequests === 0)
|
| + callback(result);
|
| + }
|
| +
|
| + var scopeChain = callFrame.scopeChain();
|
| + var pendingRequests = scopeChain.length;
|
| + for (var i = 0; i < scopeChain.length; ++i) {
|
| + var scope = scopeChain[i];
|
| + var object = scope.object();
|
| + object.getAllProperties(false, propertiesCollected.bind(null, scope.typeName()));
|
| + }
|
| + }
|
| +
|
| + /**
|
| * @param {?SDK.RemoteObject} result
|
| * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
|
| */
|
| @@ -180,14 +220,15 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| }
|
|
|
| /**
|
| - * @param {?Object} propertyNames
|
| + * @param {?Object} object
|
| */
|
| - function receivedPropertyNames(propertyNames) {
|
| + function receivedPropertyNames(object) {
|
| executionContext.target().runtimeAgent().releaseObjectGroup('completion');
|
| - if (!propertyNames) {
|
| + if (!object) {
|
| fufill([]);
|
| return;
|
| }
|
| + var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} */ (object);
|
| var includeCommandLineAPI = (!dotNotation && !bracketNotation);
|
| if (includeCommandLineAPI) {
|
| const commandLineAPI = [
|
| @@ -212,11 +253,10 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| '$$',
|
| '$x'
|
| ];
|
| - for (var i = 0; i < commandLineAPI.length; ++i)
|
| - propertyNames[commandLineAPI[i]] = true;
|
| + propertyGroups.push({items: commandLineAPI});
|
| }
|
| fufill(Components.JavaScriptAutocomplete._completionsForQuery(
|
| - dotNotation, bracketNotation, expressionString, query, Object.keys(propertyNames)));
|
| + dotNotation, bracketNotation, expressionString, query, propertyGroups));
|
| }
|
| };
|
|
|
| @@ -225,11 +265,11 @@ Components.JavaScriptAutocomplete.completionsForExpression = function(expression
|
| * @param {boolean} bracketNotation
|
| * @param {string} expressionString
|
| * @param {string} query
|
| - * @param {!Array.<string>} properties
|
| + * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propertyGroups
|
| * @return {!UI.SuggestBox.Suggestions}
|
| */
|
| Components.JavaScriptAutocomplete._completionsForQuery = function(
|
| - dotNotation, bracketNotation, expressionString, query, properties) {
|
| + dotNotation, bracketNotation, expressionString, query, propertyGroups) {
|
| if (bracketNotation) {
|
| if (query.length && query[0] === '\'')
|
| var quoteUsed = '\'';
|
| @@ -243,46 +283,52 @@ Components.JavaScriptAutocomplete._completionsForQuery = function(
|
| 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this',
|
| 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with'
|
| ];
|
| - properties = properties.concat(keywords);
|
| + propertyGroups.push({title: Common.UIString('keywords'), items: keywords});
|
| }
|
|
|
| - properties.sort();
|
| + var result = [];
|
| + var lastGroupTitle;
|
| + for (var group of propertyGroups) {
|
| + group.items.sort();
|
| + var caseSensitivePrefix = [];
|
| + var caseInsensitivePrefix = [];
|
| + var caseSensitiveAnywhere = [];
|
| + var caseInsensitiveAnywhere = [];
|
|
|
| - var caseSensitivePrefix = [];
|
| - var caseInsensitivePrefix = [];
|
| - var caseSensitiveAnywhere = [];
|
| - var caseInsensitiveAnywhere = [];
|
| - for (var i = 0; i < properties.length; ++i) {
|
| - var property = properties[i];
|
| + for (var property of group.items) {
|
| + // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
|
| + if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
|
| + continue;
|
|
|
| - // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
|
| - if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
|
| - continue;
|
| + if (bracketNotation) {
|
| + if (!/^[0-9]+$/.test(property))
|
| + property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + quoteUsed;
|
| + property += ']';
|
| + }
|
|
|
| - if (bracketNotation) {
|
| - if (!/^[0-9]+$/.test(property))
|
| - property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + quoteUsed;
|
| - property += ']';
|
| - }
|
| + if (property.length < query.length)
|
| + continue;
|
| + if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) === -1)
|
| + continue;
|
| + // Substitute actual newlines with newline characters. @see crbug.com/498421
|
| + var prop = property.split('\n').join('\\n');
|
|
|
| - if (property.length < query.length)
|
| - continue;
|
| - if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) === -1)
|
| - continue;
|
| - // Substitute actual newlines with newline characters. @see crbug.com/498421
|
| - var prop = property.split('\n').join('\\n');
|
| -
|
| - if (property.startsWith(query))
|
| - caseSensitivePrefix.push(prop);
|
| - else if (property.toLowerCase().startsWith(query.toLowerCase()))
|
| - caseInsensitivePrefix.push(prop);
|
| - else if (property.indexOf(query) !== -1)
|
| - caseSensitiveAnywhere.push(prop);
|
| - else
|
| - caseInsensitiveAnywhere.push(prop);
|
| + if (property.startsWith(query))
|
| + caseSensitivePrefix.push({title: prop, priority: 4});
|
| + else if (property.toLowerCase().startsWith(query.toLowerCase()))
|
| + caseInsensitivePrefix.push({title: prop, priority: 3});
|
| + else if (property.indexOf(query) !== -1)
|
| + caseSensitiveAnywhere.push({title: prop, priority: 2});
|
| + else
|
| + caseInsensitiveAnywhere.push({title: prop, priority: 1});
|
| + }
|
| + var structuredGroup =
|
| + caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
|
| + if (structuredGroup.length && group.title !== lastGroupTitle) {
|
| + structuredGroup[0].subtitle = group.title;
|
| + lastGroupTitle = group.title;
|
| + }
|
| + result = result.concat(structuredGroup);
|
| }
|
| - return caseSensitivePrefix.concat(caseInsensitivePrefix)
|
| - .concat(caseSensitiveAnywhere)
|
| - .concat(caseInsensitiveAnywhere)
|
| - .map(completion => ({title: completion}));
|
| + return result;
|
| };
|
|
|