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>}} */ | 7 /** @typedef {{title:(string|undefined), items:Array<string>}} */ |
8 Components.JavaScriptAutocomplete.CompletionGroup; | 8 Components.JavaScriptAutocomplete.CompletionGroup; |
9 | 9 |
10 /** | 10 /** |
11 * @param {string} text | 11 * @param {string} text |
12 * @param {string} query | 12 * @param {string} query |
13 * @param {boolean=} force | 13 * @param {boolean=} force |
14 * @return {!Promise<!UI.SuggestBox.Suggestions>} | 14 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
15 */ | 15 */ |
16 Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(
text, query, force) { | 16 Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(
text, query, force) { |
| 17 var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text
, true); |
| 18 var mapCompletionsPromise = Components.JavaScriptAutocomplete._mapCompletions(
text, query); |
| 19 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre
ssion, query, force) |
| 20 .then(completions => mapCompletionsPromise.then(mapCompletions => mapCompl
etions.concat(completions))); |
| 21 }; |
| 22 |
| 23 /** |
| 24 * @param {string} text |
| 25 * @param {boolean=} allowEndingBracket |
| 26 * @return {string} |
| 27 */ |
| 28 Components.JavaScriptAutocomplete._clipExpression = function(text, allowEndingBr
acket) { |
17 var index; | 29 var index; |
18 var stopChars = new Set('=:({;,!+-*/&|^<>`'.split('')); | 30 var stopChars = new Set('=:({;,!+-*/&|^<>`'.split('')); |
19 var whiteSpaceChars = new Set(' \r\n\t'.split('')); | 31 var whiteSpaceChars = new Set(' \r\n\t'.split('')); |
20 var continueChars = new Set('[. \r\n\t'.split('')); | 32 var continueChars = new Set('[. \r\n\t'.split('')); |
21 | 33 |
22 for (index = text.length - 1; index >= 0; index--) { | 34 for (index = text.length - 1; index >= 0; index--) { |
23 // Pass less stop characters to rangeOfWord so the range will be a more comp
lete expression. | |
24 if (stopChars.has(text.charAt(index))) | 35 if (stopChars.has(text.charAt(index))) |
25 break; | 36 break; |
26 if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charA
t(index - 1))) | 37 if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charA
t(index - 1))) |
27 break; | 38 break; |
28 } | 39 } |
29 var clippedExpression = text.substring(index + 1).trim(); | 40 var clippedExpression = text.substring(index + 1).trim(); |
30 var bracketCount = 0; | 41 var bracketCount = 0; |
31 | 42 |
32 index = clippedExpression.length - 1; | 43 index = clippedExpression.length - 1; |
33 while (index >= 0) { | 44 while (index >= 0) { |
34 var character = clippedExpression.charAt(index); | 45 var character = clippedExpression.charAt(index); |
35 if (character === ']') | 46 if (character === ']') |
36 bracketCount++; | 47 bracketCount++; |
37 // Allow an open bracket at the end for property completion. | 48 // Allow an open bracket at the end for property completion. |
38 if (character === '[' && index < clippedExpression.length - 1) { | 49 if (character === '[' && (index < clippedExpression.length - 1 || !allowEndi
ngBracket)) { |
39 bracketCount--; | 50 bracketCount--; |
40 if (bracketCount < 0) | 51 if (bracketCount < 0) |
41 break; | 52 break; |
42 } | 53 } |
43 index--; | 54 index--; |
44 } | 55 } |
45 clippedExpression = clippedExpression.substring(index + 1).trim(); | 56 return clippedExpression.substring(index + 1).trim(); |
46 | |
47 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre
ssion, query, force); | |
48 }; | 57 }; |
49 | 58 |
50 /** | 59 /** |
| 60 * @param {string} text |
| 61 * @param {string} query |
| 62 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
| 63 */ |
| 64 Components.JavaScriptAutocomplete._mapCompletions = function(text, query) { |
| 65 var mapMatch = text.match(/\.\s*(get|set|delete)\s*\(\s*$/); |
| 66 var executionContext = UI.context.flavor(SDK.ExecutionContext); |
| 67 if (!executionContext || !mapMatch) |
| 68 return Promise.resolve([]); |
| 69 |
| 70 var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text
.substring(0, mapMatch.index)); |
| 71 var fulfill; |
| 72 var promise = new Promise(x => fulfill = x); |
| 73 executionContext.evaluate(clippedExpression, 'completion', true, true, false,
false, false, evaluated); |
| 74 return promise; |
| 75 |
| 76 /** |
| 77 * @param {?SDK.RemoteObject} result |
| 78 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
| 79 */ |
| 80 function evaluated(result, exceptionDetails) { |
| 81 if (!result || !!exceptionDetails || result.subtype !== 'map') { |
| 82 fulfill([]); |
| 83 return; |
| 84 } |
| 85 result.getOwnPropertiesPromise(false).then(extractEntriesProperty); |
| 86 } |
| 87 |
| 88 /** |
| 89 * @param {!{properties: ?Array<!SDK.RemoteObjectProperty>, internalProperties
: ?Array<!SDK.RemoteObjectProperty>}} properties |
| 90 */ |
| 91 function extractEntriesProperty(properties) { |
| 92 var internalProperties = properties.internalProperties || []; |
| 93 var entriesProperty = internalProperties.find(property => property.name ===
'[[Entries]]'); |
| 94 if (!entriesProperty) { |
| 95 fulfill([]); |
| 96 return; |
| 97 } |
| 98 entriesProperty.value.callFunctionJSONPromise(getEntries).then(keysObj => go
tKeys(Object.keys(keysObj))); |
| 99 } |
| 100 |
| 101 /** |
| 102 * @suppressReceiverCheck |
| 103 * @this {!Array<{key:?, value:?}>} |
| 104 * @return {!Object} |
| 105 */ |
| 106 function getEntries() { |
| 107 var result = {__proto__: null}; |
| 108 for (var i = 0; i < this.length; i++) { |
| 109 if (typeof this[i].key === 'string') |
| 110 result[this[i].key] = true; |
| 111 } |
| 112 return result; |
| 113 } |
| 114 |
| 115 /** |
| 116 * @param {!Array<string>} rawKeys |
| 117 */ |
| 118 function gotKeys(rawKeys) { |
| 119 var caseSensitivePrefix = []; |
| 120 var caseInsensitivePrefix = []; |
| 121 var caseSensitiveAnywhere = []; |
| 122 var caseInsensitiveAnywhere = []; |
| 123 var quoteChar = '"'; |
| 124 if (query.startsWith('\'')) |
| 125 quoteChar = '\''; |
| 126 var endChar = ')'; |
| 127 if (mapMatch[0].indexOf('set') !== -1) |
| 128 endChar = ', '; |
| 129 |
| 130 var sorter = rawKeys.length < 1000 ? String.naturalOrderComparator : undefin
ed; |
| 131 var keys = rawKeys.sort(sorter).map(key => quoteChar + key + quoteChar + end
Char); |
| 132 |
| 133 for (var key of keys) { |
| 134 if (key.length < query.length) |
| 135 continue; |
| 136 if (query.length && key.toLowerCase().indexOf(query.toLowerCase()) === -1) |
| 137 continue; |
| 138 // Substitute actual newlines with newline characters. @see crbug.com/4984
21 |
| 139 var title = key.split('\n').join('\\n'); |
| 140 |
| 141 if (key.startsWith(query)) |
| 142 caseSensitivePrefix.push({title: title, priority: 4}); |
| 143 else if (key.toLowerCase().startsWith(query.toLowerCase())) |
| 144 caseInsensitivePrefix.push({title: title, priority: 3}); |
| 145 else if (key.indexOf(query) !== -1) |
| 146 caseSensitiveAnywhere.push({title: title, priority: 2}); |
| 147 else |
| 148 caseInsensitiveAnywhere.push({title: title, priority: 1}); |
| 149 } |
| 150 var suggestions = caseSensitivePrefix.concat(caseInsensitivePrefix, caseSens
itiveAnywhere, caseInsensitiveAnywhere); |
| 151 if (suggestions.length) |
| 152 suggestions[0].subtitle = Common.UIString('Keys'); |
| 153 fulfill(suggestions); |
| 154 } |
| 155 }; |
| 156 |
| 157 /** |
51 * @param {string} expressionString | 158 * @param {string} expressionString |
52 * @param {string} query | 159 * @param {string} query |
53 * @param {boolean=} force | 160 * @param {boolean=} force |
54 * @return {!Promise<!UI.SuggestBox.Suggestions>} | 161 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
55 */ | 162 */ |
56 Components.JavaScriptAutocomplete.completionsForExpression = function(expression
String, query, force) { | 163 Components.JavaScriptAutocomplete.completionsForExpression = function(expression
String, query, force) { |
57 var executionContext = UI.context.flavor(SDK.ExecutionContext); | 164 var executionContext = UI.context.flavor(SDK.ExecutionContext); |
58 if (!executionContext) | 165 if (!executionContext) |
59 return Promise.resolve([]); | 166 return Promise.resolve([]); |
60 | 167 |
61 var lastIndex = expressionString.length - 1; | 168 var lastIndex = expressionString.length - 1; |
62 | 169 |
63 var dotNotation = (expressionString[lastIndex] === '.'); | 170 var dotNotation = (expressionString[lastIndex] === '.'); |
64 var bracketNotation = (expressionString.length > 1 && expressionString[lastInd
ex] === '['); | 171 var bracketNotation = (expressionString.length > 1 && expressionString[lastInd
ex] === '['); |
65 | 172 |
66 if (dotNotation || bracketNotation) | 173 if (dotNotation || bracketNotation) |
67 expressionString = expressionString.substr(0, lastIndex); | 174 expressionString = expressionString.substr(0, lastIndex); |
68 else | 175 else |
69 expressionString = ''; | 176 expressionString = ''; |
70 | 177 |
71 // User is entering float value, do not suggest anything. | 178 // User is entering float value, do not suggest anything. |
72 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu
ery && !isNaN(query))) | 179 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu
ery && !isNaN(query))) |
73 return Promise.resolve([]); | 180 return Promise.resolve([]); |
74 | 181 |
75 | 182 |
76 if (!query && !expressionString && !force) | 183 if (!query && !expressionString && !force) |
77 return Promise.resolve([]); | 184 return Promise.resolve([]); |
78 | 185 |
79 var fufill; | 186 var fulfill; |
80 var promise = new Promise(x => fufill = x); | 187 var promise = new Promise(x => fulfill = x); |
81 var selectedFrame = executionContext.debuggerModel.selectedCallFrame(); | 188 var selectedFrame = executionContext.debuggerModel.selectedCallFrame(); |
82 if (!expressionString && selectedFrame) | 189 if (!expressionString && selectedFrame) |
83 variableNamesInScopes(selectedFrame, receivedPropertyNames); | 190 variableNamesInScopes(selectedFrame, receivedPropertyNames); |
84 else | 191 else |
85 executionContext.evaluate(expressionString, 'completion', true, true, false,
false, false, evaluated); | 192 executionContext.evaluate(expressionString, 'completion', true, true, false,
false, false, evaluated); |
86 | 193 |
87 return promise; | 194 return promise; |
88 /** | 195 /** |
89 * @param {?SDK.RemoteObject} result | 196 * @param {?SDK.RemoteObject} result |
90 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 197 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
91 */ | 198 */ |
92 function evaluated(result, exceptionDetails) { | 199 function evaluated(result, exceptionDetails) { |
93 if (!result || !!exceptionDetails) { | 200 if (!result || !!exceptionDetails) { |
94 fufill([]); | 201 fulfill([]); |
95 return; | 202 return; |
96 } | 203 } |
97 | 204 |
98 /** | 205 /** |
99 * @param {?SDK.RemoteObject} object | 206 * @param {?SDK.RemoteObject} object |
100 * @return {!Promise<?SDK.RemoteObject>} | 207 * @return {!Promise<?SDK.RemoteObject>} |
101 */ | 208 */ |
102 function extractTarget(object) { | 209 function extractTarget(object) { |
103 if (!object) | 210 if (!object) |
104 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null)); | 211 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null)); |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
216 | 323 |
217 /** | 324 /** |
218 * @param {?SDK.RemoteObject} result | 325 * @param {?SDK.RemoteObject} result |
219 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 326 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
220 */ | 327 */ |
221 function receivedPropertyNamesFromEval(result, exceptionDetails) { | 328 function receivedPropertyNamesFromEval(result, exceptionDetails) { |
222 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 329 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
223 if (result && !exceptionDetails) | 330 if (result && !exceptionDetails) |
224 receivedPropertyNames(/** @type {!Object} */ (result.value)); | 331 receivedPropertyNames(/** @type {!Object} */ (result.value)); |
225 else | 332 else |
226 fufill([]); | 333 fulfill([]); |
227 } | 334 } |
228 | 335 |
229 /** | 336 /** |
230 * @param {?Object} object | 337 * @param {?Object} object |
231 */ | 338 */ |
232 function receivedPropertyNames(object) { | 339 function receivedPropertyNames(object) { |
233 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 340 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
234 if (!object) { | 341 if (!object) { |
235 fufill([]); | 342 fulfill([]); |
236 return; | 343 return; |
237 } | 344 } |
238 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co
mpletionGroup>} */ (object); | 345 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co
mpletionGroup>} */ (object); |
239 var includeCommandLineAPI = (!dotNotation && !bracketNotation); | 346 var includeCommandLineAPI = (!dotNotation && !bracketNotation); |
240 if (includeCommandLineAPI) { | 347 if (includeCommandLineAPI) { |
241 const commandLineAPI = [ | 348 const commandLineAPI = [ |
242 'dir', | 349 'dir', |
243 'dirxml', | 350 'dirxml', |
244 'keys', | 351 'keys', |
245 'values', | 352 'values', |
246 'profile', | 353 'profile', |
247 'profileEnd', | 354 'profileEnd', |
248 'monitorEvents', | 355 'monitorEvents', |
249 'unmonitorEvents', | 356 'unmonitorEvents', |
250 'inspect', | 357 'inspect', |
251 'copy', | 358 'copy', |
252 'clear', | 359 'clear', |
253 'getEventListeners', | 360 'getEventListeners', |
254 'debug', | 361 'debug', |
255 'undebug', | 362 'undebug', |
256 'monitor', | 363 'monitor', |
257 'unmonitor', | 364 'unmonitor', |
258 'table', | 365 'table', |
259 '$', | 366 '$', |
260 '$$', | 367 '$$', |
261 '$x' | 368 '$x' |
262 ]; | 369 ]; |
263 propertyGroups.push({items: commandLineAPI}); | 370 propertyGroups.push({items: commandLineAPI}); |
264 } | 371 } |
265 fufill(Components.JavaScriptAutocomplete._completionsForQuery( | 372 fulfill(Components.JavaScriptAutocomplete._completionsForQuery( |
266 dotNotation, bracketNotation, expressionString, query, propertyGroups)); | 373 dotNotation, bracketNotation, expressionString, query, propertyGroups)); |
267 } | 374 } |
268 }; | 375 }; |
269 | 376 |
270 /** | 377 /** |
271 * @param {boolean} dotNotation | 378 * @param {boolean} dotNotation |
272 * @param {boolean} bracketNotation | 379 * @param {boolean} bracketNotation |
273 * @param {string} expressionString | 380 * @param {string} expressionString |
274 * @param {string} query | 381 * @param {string} query |
275 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert
yGroups | 382 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert
yGroups |
(...skipping 13 matching lines...) Expand all Loading... |
289 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
'else', 'finally', | 396 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
'else', 'finally', |
290 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return
', 'switch', 'this', | 397 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return
', 'switch', 'this', |
291 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' | 398 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' |
292 ]; | 399 ]; |
293 propertyGroups.push({title: Common.UIString('keywords'), items: keywords}); | 400 propertyGroups.push({title: Common.UIString('keywords'), items: keywords}); |
294 } | 401 } |
295 | 402 |
296 var result = []; | 403 var result = []; |
297 var lastGroupTitle; | 404 var lastGroupTitle; |
298 for (var group of propertyGroups) { | 405 for (var group of propertyGroups) { |
299 group.items.sort(itemComparator); | 406 group.items.sort(itemComparator.bind(null, group.items.length > 1000)); |
300 var caseSensitivePrefix = []; | 407 var caseSensitivePrefix = []; |
301 var caseInsensitivePrefix = []; | 408 var caseInsensitivePrefix = []; |
302 var caseSensitiveAnywhere = []; | 409 var caseSensitiveAnywhere = []; |
303 var caseInsensitiveAnywhere = []; | 410 var caseInsensitiveAnywhere = []; |
304 | 411 |
305 for (var property of group.items) { | 412 for (var property of group.items) { |
306 // Assume that all non-ASCII characters are letters and thus can be used a
s part of identifier. | 413 // Assume that all non-ASCII characters are letters and thus can be used a
s part of identifier. |
307 if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFF
F]*$/.test(property)) | 414 if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFF
F]*$/.test(property)) |
308 continue; | 415 continue; |
309 | 416 |
(...skipping 23 matching lines...) Expand all Loading... |
333 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere,
caseInsensitiveAnywhere); | 440 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere,
caseInsensitiveAnywhere); |
334 if (structuredGroup.length && group.title !== lastGroupTitle) { | 441 if (structuredGroup.length && group.title !== lastGroupTitle) { |
335 structuredGroup[0].subtitle = group.title; | 442 structuredGroup[0].subtitle = group.title; |
336 lastGroupTitle = group.title; | 443 lastGroupTitle = group.title; |
337 } | 444 } |
338 result = result.concat(structuredGroup); | 445 result = result.concat(structuredGroup); |
339 } | 446 } |
340 return result; | 447 return result; |
341 | 448 |
342 /** | 449 /** |
| 450 * @param {boolean} naturalOrder |
343 * @param {string} a | 451 * @param {string} a |
344 * @param {string} b | 452 * @param {string} b |
345 * @return {number} | 453 * @return {number} |
346 */ | 454 */ |
347 function itemComparator(a, b) { | 455 function itemComparator(naturalOrder, a, b) { |
348 var aStartsWithUnderscore = a.startsWith('_'); | 456 var aStartsWithUnderscore = a.startsWith('_'); |
349 var bStartsWithUnderscore = b.startsWith('_'); | 457 var bStartsWithUnderscore = b.startsWith('_'); |
350 if (aStartsWithUnderscore && !bStartsWithUnderscore) | 458 if (aStartsWithUnderscore && !bStartsWithUnderscore) |
351 return 1; | 459 return 1; |
352 if (bStartsWithUnderscore && !aStartsWithUnderscore) | 460 if (bStartsWithUnderscore && !aStartsWithUnderscore) |
353 return -1; | 461 return -1; |
354 return String.naturalOrderComparator(a, b); | 462 return naturalOrder ? String.naturalOrderComparator(a, b) : a.localeCompare(
b); |
355 } | 463 } |
356 }; | 464 }; |
OLD | NEW |