Chromium Code Reviews| 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 6a71a77674a418f342dfa1d774c2ceabf54d0f0f..2b7da7b2b798c25507a9845328ae743c51f49b6b 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) |
| @@ -71,7 +73,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); |
| @@ -125,23 +127,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) |
|
einbinder
2016/12/07 20:10:50
//TODO (einbinder) namespaces?
|
| + 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; |
| } |
| /** |
| @@ -164,6 +175,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']}]; |
|
einbinder
2016/12/07 20:10:50
Should this be added to the local scope? Also, it
pfeldman
2016/12/08 00:29:06
I'd want 'this' to be high in the list in the debu
|
| + |
| + /** |
| + * @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())); |
|
einbinder
2016/12/07 20:10:51
Nested deep in some code, you might see many Closu
pfeldman
2016/12/08 00:29:06
Don't do deep nesting?
einbinder
2016/12/08 18:51:22
Will the order of the stacks always go Local, Clos
pfeldman
2016/12/08 21:26:18
You can have multiple closures, why not? The order
|
| + } |
| + } |
| + |
| + /** |
| * @param {?SDK.RemoteObject} result |
| * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
| */ |
| @@ -176,14 +216,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 = [ |
| @@ -208,11 +249,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)); |
| } |
| }; |
| @@ -221,11 +261,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 = '\''; |
| @@ -239,46 +279,50 @@ 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({items: keywords}); |
|
einbinder
2016/12/07 20:10:51
Should keywords get a title?
pfeldman
2016/12/08 00:29:06
Sure, done.
|
| } |
| - properties.sort(); |
| + var result = []; |
| + 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 (dotNotation && !/^[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 (dotNotation && !/^[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(prop); |
| + else if (property.toLowerCase().startsWith(query.toLowerCase())) |
| + caseInsensitivePrefix.push(prop); |
| + else if (property.indexOf(query) !== -1) |
| + caseSensitiveAnywhere.push(prop); |
| + else |
| + caseInsensitiveAnywhere.push(prop); |
| + } |
| + var structuredGroup = |
| + caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere) |
| + .map(item => ({title: item})); |
|
einbinder
2016/12/07 20:10:51
Add higher priority to the case sensitive and pref
pfeldman
2016/12/08 00:29:06
Wouldn't that break the grouping?
einbinder
2016/12/08 18:51:22
No, it preserves the order of the suggestions. It
pfeldman
2016/12/08 21:26:18
Done.
|
| + if (structuredGroup.length) |
| + structuredGroup[0].subtitle = group.title; |
| + result = result.concat(structuredGroup); |
| } |
| - return caseSensitivePrefix.concat(caseInsensitivePrefix) |
| - .concat(caseSensitiveAnywhere) |
| - .concat(caseInsensitiveAnywhere) |
| - .map(completion => ({title: completion})); |
| + return result; |
|
einbinder
2016/12/07 20:10:50
You will have duplicate suggestions if the prototy
einbinder
2016/12/07 20:10:51
You will have duplicate suggestions if the prototy
pfeldman
2016/12/08 00:29:06
Maybe that is ok?
pfeldman
2016/12/08 00:29:06
Maybe that is ok?
einbinder
2016/12/08 18:51:22
While it does get used for introspection, the main
pfeldman
2016/12/08 21:26:18
My worry is that with this new structure, not show
|
| }; |