Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 Components.JavaScriptAutocomplete = {}; | 5 Components.JavaScriptAutocomplete = {}; |
| 6 | 6 |
| 7 /** @typedef {{title:(string|undefined), items:Array<string>}} */ | |
| 8 Components.JavaScriptAutocomplete.CompletionGroup; | |
| 9 | |
| 7 /** | 10 /** |
| 8 * @param {string} text | 11 * @param {string} text |
| 9 * @param {string} query | 12 * @param {string} query |
| 10 * @param {boolean=} force | 13 * @param {boolean=} force |
| 11 * @return {!Promise<!UI.SuggestBox.Suggestions>} | 14 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
| 12 */ | 15 */ |
| 13 Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function( text, query, force) { | 16 Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function( text, query, force) { |
| 14 var index; | 17 var index; |
| 15 var stopChars = new Set(' =:({;,!+-*/&|^<>`'.split('')); | 18 var stopChars = new Set(' =:({;,!+-*/&|^<>`'.split('')); |
| 16 for (index = text.length - 1; index >= 0; index--) { | 19 for (index = text.length - 1; index >= 0; index--) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 32 if (bracketCount < 0) | 35 if (bracketCount < 0) |
| 33 break; | 36 break; |
| 34 } | 37 } |
| 35 index--; | 38 index--; |
| 36 } | 39 } |
| 37 clippedExpression = clippedExpression.substring(index + 1); | 40 clippedExpression = clippedExpression.substring(index + 1); |
| 38 | 41 |
| 39 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre ssion, query, force); | 42 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre ssion, query, force); |
| 40 }; | 43 }; |
| 41 | 44 |
| 42 | |
| 43 /** | 45 /** |
| 44 * @param {string} expressionString | 46 * @param {string} expressionString |
| 45 * @param {string} query | 47 * @param {string} query |
| 46 * @param {boolean=} force | 48 * @param {boolean=} force |
| 47 * @return {!Promise<!UI.SuggestBox.Suggestions>} | 49 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
| 48 */ | 50 */ |
| 49 Components.JavaScriptAutocomplete.completionsForExpression = function(expression String, query, force) { | 51 Components.JavaScriptAutocomplete.completionsForExpression = function(expression String, query, force) { |
| 50 var executionContext = UI.context.flavor(SDK.ExecutionContext); | 52 var executionContext = UI.context.flavor(SDK.ExecutionContext); |
| 51 if (!executionContext) | 53 if (!executionContext) |
| 52 return Promise.resolve([]); | 54 return Promise.resolve([]); |
| 53 | 55 |
| 54 var lastIndex = expressionString.length - 1; | 56 var lastIndex = expressionString.length - 1; |
| 55 | 57 |
| 56 var dotNotation = (expressionString[lastIndex] === '.'); | 58 var dotNotation = (expressionString[lastIndex] === '.'); |
| 57 var bracketNotation = (expressionString[lastIndex] === '['); | 59 var bracketNotation = (expressionString[lastIndex] === '['); |
| 58 | 60 |
| 59 if (dotNotation || bracketNotation) | 61 if (dotNotation || bracketNotation) |
| 60 expressionString = expressionString.substr(0, lastIndex); | 62 expressionString = expressionString.substr(0, lastIndex); |
| 61 else | 63 else |
| 62 expressionString = ''; | 64 expressionString = ''; |
| 63 | 65 |
| 64 // User is entering float value, do not suggest anything. | 66 // User is entering float value, do not suggest anything. |
| 65 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu ery && !isNaN(query))) | 67 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu ery && !isNaN(query))) |
| 66 return Promise.resolve([]); | 68 return Promise.resolve([]); |
| 67 | 69 |
| 68 if (!query && !expressionString && !force) | 70 if (!query && !expressionString && !force) |
| 69 return Promise.resolve([]); | 71 return Promise.resolve([]); |
| 70 | 72 |
| 71 var fufill; | 73 var fufill; |
| 72 var promise = new Promise(x => fufill = x); | 74 var promise = new Promise(x => fufill = x); |
| 73 if (!expressionString && executionContext.debuggerModel.selectedCallFrame()) | 75 if (!expressionString && executionContext.debuggerModel.selectedCallFrame()) |
| 74 executionContext.debuggerModel.selectedCallFrame().variableNames(receivedPro pertyNames); | 76 variableNamesInScopes(executionContext.debuggerModel.selectedCallFrame(), re ceivedPropertyNames); |
| 75 else | 77 else |
| 76 executionContext.evaluate(expressionString, 'completion', true, true, false, false, false, evaluated); | 78 executionContext.evaluate(expressionString, 'completion', true, true, false, false, false, evaluated); |
| 77 | 79 |
| 78 return promise; | 80 return promise; |
| 79 /** | 81 /** |
| 80 * @param {?SDK.RemoteObject} result | 82 * @param {?SDK.RemoteObject} result |
| 81 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 83 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
| 82 */ | 84 */ |
| 83 function evaluated(result, exceptionDetails) { | 85 function evaluated(result, exceptionDetails) { |
| 84 if (!result || !!exceptionDetails) { | 86 if (!result || !!exceptionDetails) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 118 var object; | 120 var object; |
| 119 if (type === 'string') | 121 if (type === 'string') |
| 120 object = new String(''); | 122 object = new String(''); |
| 121 else if (type === 'number') | 123 else if (type === 'number') |
| 122 object = new Number(0); | 124 object = new Number(0); |
| 123 else if (type === 'boolean') | 125 else if (type === 'boolean') |
| 124 object = new Boolean(false); | 126 object = new Boolean(false); |
| 125 else | 127 else |
| 126 object = this; | 128 object = this; |
| 127 | 129 |
| 128 var resultSet = {__proto__: null}; | 130 var result = []; |
| 129 try { | 131 try { |
| 130 for (var o = object; o; o = Object.getPrototypeOf(o)) { | 132 for (var o = object; o; o = Object.getPrototypeOf(o)) { |
| 131 if ((type === 'array' || type === 'typedarray') && o === object && Arr ayBuffer.isView(o) && o.length > 9999) | 133 if ((type === 'array' || type === 'typedarray') && o === object && Arr ayBuffer.isView(o) && o.length > 9999) |
| 132 continue; | 134 continue; |
| 135 | |
| 136 var group = {items: [], __proto__: null}; | |
| 137 try { | |
| 138 if (typeof o === 'object' && o.constructor && o.constructor.name) | |
|
einbinder
2016/12/07 20:10:50
//TODO (einbinder) namespaces?
| |
| 139 group.title = o.constructor.name; | |
| 140 } catch (ee) { | |
| 141 // we could break upon cross origin check. | |
| 142 } | |
| 143 result[result.length] = group; | |
| 133 var names = Object.getOwnPropertyNames(o); | 144 var names = Object.getOwnPropertyNames(o); |
| 134 var isArray = Array.isArray(o); | 145 var isArray = Array.isArray(o); |
| 135 for (var i = 0; i < names.length; ++i) { | 146 for (var i = 0; i < names.length; ++i) { |
| 136 // Skip array elements indexes. | 147 // Skip array elements indexes. |
| 137 if (isArray && /^[0-9]/.test(names[i])) | 148 if (isArray && /^[0-9]/.test(names[i])) |
| 138 continue; | 149 continue; |
| 139 resultSet[names[i]] = true; | 150 group.items[group.items.length] = names[i]; |
| 140 } | 151 } |
| 141 } | 152 } |
| 142 } catch (e) { | 153 } catch (e) { |
| 143 } | 154 } |
| 144 return resultSet; | 155 return result; |
| 145 } | 156 } |
| 146 | 157 |
| 147 /** | 158 /** |
| 148 * @param {?SDK.RemoteObject} object | 159 * @param {?SDK.RemoteObject} object |
| 149 */ | 160 */ |
| 150 function completionsForObject(object) { | 161 function completionsForObject(object) { |
| 151 if (!object) { | 162 if (!object) { |
| 152 receivedPropertyNames(null); | 163 receivedPropertyNames(null); |
| 153 } else if (object.type === 'object' || object.type === 'function') { | 164 } else if (object.type === 'object' || object.type === 'function') { |
| 154 object.callFunctionJSON( | 165 object.callFunctionJSON( |
| 155 getCompletions, [SDK.RemoteObject.toCallArgument(object.subtype)], r eceivedPropertyNames); | 166 getCompletions, [SDK.RemoteObject.toCallArgument(object.subtype)], r eceivedPropertyNames); |
| 156 } else if (object.type === 'string' || object.type === 'number' || object. type === 'boolean') { | 167 } else if (object.type === 'string' || object.type === 'number' || object. type === 'boolean') { |
| 157 executionContext.evaluate( | 168 executionContext.evaluate( |
| 158 '(' + getCompletions + ')("' + result.type + '")', 'completion', fal se, true, true, false, false, | 169 '(' + getCompletions + ')("' + result.type + '")', 'completion', fal se, true, true, false, false, |
| 159 receivedPropertyNamesFromEval); | 170 receivedPropertyNamesFromEval); |
| 160 } | 171 } |
| 161 } | 172 } |
| 162 | 173 |
| 163 extractTarget(result).then(completionsForObject); | 174 extractTarget(result).then(completionsForObject); |
| 164 } | 175 } |
| 165 | 176 |
| 166 /** | 177 /** |
| 178 * @param {!SDK.DebuggerModel.CallFrame} callFrame | |
| 179 * @param {function(!Array<!Components.JavaScriptAutocomplete.CompletionGroup> )} callback | |
| 180 */ | |
| 181 function variableNamesInScopes(callFrame, callback) { | |
| 182 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
| |
| 183 | |
| 184 /** | |
| 185 * @param {string} name | |
| 186 * @param {?Array<!SDK.RemoteObjectProperty>} properties | |
| 187 */ | |
| 188 function propertiesCollected(name, properties) { | |
| 189 var group = {title: name, items: []}; | |
| 190 result.push(group); | |
| 191 for (var i = 0; properties && i < properties.length; ++i) | |
| 192 group.items.push(properties[i].name); | |
| 193 if (--pendingRequests === 0) | |
| 194 callback(result); | |
| 195 } | |
| 196 | |
| 197 var scopeChain = callFrame.scopeChain(); | |
| 198 var pendingRequests = scopeChain.length; | |
| 199 for (var i = 0; i < scopeChain.length; ++i) { | |
| 200 var scope = scopeChain[i]; | |
| 201 var object = scope.object(); | |
| 202 object.getAllProperties(false, propertiesCollected.bind(null, scope.typeNa me())); | |
|
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
| |
| 203 } | |
| 204 } | |
| 205 | |
| 206 /** | |
| 167 * @param {?SDK.RemoteObject} result | 207 * @param {?SDK.RemoteObject} result |
| 168 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 208 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
| 169 */ | 209 */ |
| 170 function receivedPropertyNamesFromEval(result, exceptionDetails) { | 210 function receivedPropertyNamesFromEval(result, exceptionDetails) { |
| 171 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 211 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
| 172 if (result && !exceptionDetails) | 212 if (result && !exceptionDetails) |
| 173 receivedPropertyNames(/** @type {!Object} */ (result.value)); | 213 receivedPropertyNames(/** @type {!Object} */ (result.value)); |
| 174 else | 214 else |
| 175 fufill([]); | 215 fufill([]); |
| 176 } | 216 } |
| 177 | 217 |
| 178 /** | 218 /** |
| 179 * @param {?Object} propertyNames | 219 * @param {?Object} object |
| 180 */ | 220 */ |
| 181 function receivedPropertyNames(propertyNames) { | 221 function receivedPropertyNames(object) { |
| 182 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 222 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
| 183 if (!propertyNames) { | 223 if (!object) { |
| 184 fufill([]); | 224 fufill([]); |
| 185 return; | 225 return; |
| 186 } | 226 } |
| 227 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co mpletionGroup>} */ (object); | |
| 187 var includeCommandLineAPI = (!dotNotation && !bracketNotation); | 228 var includeCommandLineAPI = (!dotNotation && !bracketNotation); |
| 188 if (includeCommandLineAPI) { | 229 if (includeCommandLineAPI) { |
| 189 const commandLineAPI = [ | 230 const commandLineAPI = [ |
| 190 'dir', | 231 'dir', |
| 191 'dirxml', | 232 'dirxml', |
| 192 'keys', | 233 'keys', |
| 193 'values', | 234 'values', |
| 194 'profile', | 235 'profile', |
| 195 'profileEnd', | 236 'profileEnd', |
| 196 'monitorEvents', | 237 'monitorEvents', |
| 197 'unmonitorEvents', | 238 'unmonitorEvents', |
| 198 'inspect', | 239 'inspect', |
| 199 'copy', | 240 'copy', |
| 200 'clear', | 241 'clear', |
| 201 'getEventListeners', | 242 'getEventListeners', |
| 202 'debug', | 243 'debug', |
| 203 'undebug', | 244 'undebug', |
| 204 'monitor', | 245 'monitor', |
| 205 'unmonitor', | 246 'unmonitor', |
| 206 'table', | 247 'table', |
| 207 '$', | 248 '$', |
| 208 '$$', | 249 '$$', |
| 209 '$x' | 250 '$x' |
| 210 ]; | 251 ]; |
| 211 for (var i = 0; i < commandLineAPI.length; ++i) | 252 propertyGroups.push({items: commandLineAPI}); |
| 212 propertyNames[commandLineAPI[i]] = true; | |
| 213 } | 253 } |
| 214 fufill(Components.JavaScriptAutocomplete._completionsForQuery( | 254 fufill(Components.JavaScriptAutocomplete._completionsForQuery( |
| 215 dotNotation, bracketNotation, expressionString, query, Object.keys(prope rtyNames))); | 255 dotNotation, bracketNotation, expressionString, query, propertyGroups)); |
| 216 } | 256 } |
| 217 }; | 257 }; |
| 218 | 258 |
| 219 /** | 259 /** |
| 220 * @param {boolean} dotNotation | 260 * @param {boolean} dotNotation |
| 221 * @param {boolean} bracketNotation | 261 * @param {boolean} bracketNotation |
| 222 * @param {string} expressionString | 262 * @param {string} expressionString |
| 223 * @param {string} query | 263 * @param {string} query |
| 224 * @param {!Array.<string>} properties | 264 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert yGroups |
| 225 * @return {!UI.SuggestBox.Suggestions} | 265 * @return {!UI.SuggestBox.Suggestions} |
| 226 */ | 266 */ |
| 227 Components.JavaScriptAutocomplete._completionsForQuery = function( | 267 Components.JavaScriptAutocomplete._completionsForQuery = function( |
| 228 dotNotation, bracketNotation, expressionString, query, properties) { | 268 dotNotation, bracketNotation, expressionString, query, propertyGroups) { |
| 229 if (bracketNotation) { | 269 if (bracketNotation) { |
| 230 if (query.length && query[0] === '\'') | 270 if (query.length && query[0] === '\'') |
| 231 var quoteUsed = '\''; | 271 var quoteUsed = '\''; |
| 232 else | 272 else |
| 233 var quoteUsed = '"'; | 273 var quoteUsed = '"'; |
| 234 } | 274 } |
| 235 | 275 |
| 236 if (!expressionString) { | 276 if (!expressionString) { |
| 237 const keywords = [ | 277 const keywords = [ |
| 238 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', 'else', 'finally', | 278 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', 'else', 'finally', |
| 239 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return ', 'switch', 'this', | 279 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return ', 'switch', 'this', |
| 240 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' | 280 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' |
| 241 ]; | 281 ]; |
| 242 properties = properties.concat(keywords); | 282 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.
| |
| 243 } | 283 } |
| 244 | 284 |
| 245 properties.sort(); | 285 var result = []; |
| 286 for (var group of propertyGroups) { | |
| 287 group.items.sort(); | |
| 288 var caseSensitivePrefix = []; | |
| 289 var caseInsensitivePrefix = []; | |
| 290 var caseSensitiveAnywhere = []; | |
| 291 var caseInsensitiveAnywhere = []; | |
| 246 | 292 |
| 247 var caseSensitivePrefix = []; | 293 for (var property of group.items) { |
| 248 var caseInsensitivePrefix = []; | 294 // Assume that all non-ASCII characters are letters and thus can be used a s part of identifier. |
| 249 var caseSensitiveAnywhere = []; | 295 if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/ .test(property)) |
| 250 var caseInsensitiveAnywhere = []; | 296 continue; |
| 251 for (var i = 0; i < properties.length; ++i) { | |
| 252 var property = properties[i]; | |
| 253 | 297 |
| 254 // Assume that all non-ASCII characters are letters and thus can be used as part of identifier. | 298 if (bracketNotation) { |
| 255 if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.t est(property)) | 299 if (!/^[0-9]+$/.test(property)) |
| 256 continue; | 300 property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + q uoteUsed; |
| 301 property += ']'; | |
| 302 } | |
| 257 | 303 |
| 258 if (bracketNotation) { | 304 if (property.length < query.length) |
| 259 if (!/^[0-9]+$/.test(property)) | 305 continue; |
| 260 property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + quo teUsed; | 306 if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) == = -1) |
| 261 property += ']'; | 307 continue; |
| 308 // Substitute actual newlines with newline characters. @see crbug.com/4984 21 | |
| 309 var prop = property.split('\n').join('\\n'); | |
| 310 | |
| 311 if (property.startsWith(query)) | |
| 312 caseSensitivePrefix.push(prop); | |
| 313 else if (property.toLowerCase().startsWith(query.toLowerCase())) | |
| 314 caseInsensitivePrefix.push(prop); | |
| 315 else if (property.indexOf(query) !== -1) | |
| 316 caseSensitiveAnywhere.push(prop); | |
| 317 else | |
| 318 caseInsensitiveAnywhere.push(prop); | |
| 262 } | 319 } |
| 263 | 320 var structuredGroup = |
| 264 if (property.length < query.length) | 321 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere) |
| 265 continue; | 322 .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.
| |
| 266 if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) === -1) | 323 if (structuredGroup.length) |
| 267 continue; | 324 structuredGroup[0].subtitle = group.title; |
| 268 // Substitute actual newlines with newline characters. @see crbug.com/498421 | 325 result = result.concat(structuredGroup); |
| 269 var prop = property.split('\n').join('\\n'); | |
| 270 | |
| 271 if (property.startsWith(query)) | |
| 272 caseSensitivePrefix.push(prop); | |
| 273 else if (property.toLowerCase().startsWith(query.toLowerCase())) | |
| 274 caseInsensitivePrefix.push(prop); | |
| 275 else if (property.indexOf(query) !== -1) | |
| 276 caseSensitiveAnywhere.push(prop); | |
| 277 else | |
| 278 caseInsensitiveAnywhere.push(prop); | |
| 279 } | 326 } |
| 280 return caseSensitivePrefix.concat(caseInsensitivePrefix) | 327 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
| |
| 281 .concat(caseSensitiveAnywhere) | |
| 282 .concat(caseInsensitiveAnywhere) | |
| 283 .map(completion => ({title: completion})); | |
| 284 }; | 328 }; |
| OLD | NEW |