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) { | |
29 var index; | 17 var index; |
30 var stopChars = new Set('=:({;,!+-*/&|^<>`'.split('')); | 18 var stopChars = new Set('=:({;,!+-*/&|^<>`'.split('')); |
31 var whiteSpaceChars = new Set(' \r\n\t'.split('')); | 19 var whiteSpaceChars = new Set(' \r\n\t'.split('')); |
32 var continueChars = new Set('[. \r\n\t'.split('')); | 20 var continueChars = new Set('[. \r\n\t'.split('')); |
33 | 21 |
34 for (index = text.length - 1; index >= 0; index--) { | 22 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. |
35 if (stopChars.has(text.charAt(index))) | 24 if (stopChars.has(text.charAt(index))) |
36 break; | 25 break; |
37 if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charA
t(index - 1))) | 26 if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charA
t(index - 1))) |
38 break; | 27 break; |
39 } | 28 } |
40 var clippedExpression = text.substring(index + 1).trim(); | 29 var clippedExpression = text.substring(index + 1).trim(); |
41 var bracketCount = 0; | 30 var bracketCount = 0; |
42 | 31 |
43 index = clippedExpression.length - 1; | 32 index = clippedExpression.length - 1; |
44 while (index >= 0) { | 33 while (index >= 0) { |
45 var character = clippedExpression.charAt(index); | 34 var character = clippedExpression.charAt(index); |
46 if (character === ']') | 35 if (character === ']') |
47 bracketCount++; | 36 bracketCount++; |
48 // Allow an open bracket at the end for property completion. | 37 // Allow an open bracket at the end for property completion. |
49 if (character === '[' && (index < clippedExpression.length - 1 || !allowEndi
ngBracket)) { | 38 if (character === '[' && index < clippedExpression.length - 1) { |
50 bracketCount--; | 39 bracketCount--; |
51 if (bracketCount < 0) | 40 if (bracketCount < 0) |
52 break; | 41 break; |
53 } | 42 } |
54 index--; | 43 index--; |
55 } | 44 } |
56 return clippedExpression.substring(index + 1).trim(); | 45 clippedExpression = clippedExpression.substring(index + 1).trim(); |
| 46 |
| 47 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre
ssion, query, force); |
57 }; | 48 }; |
58 | 49 |
59 /** | 50 /** |
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().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 /** | |
158 * @param {string} expressionString | 51 * @param {string} expressionString |
159 * @param {string} query | 52 * @param {string} query |
160 * @param {boolean=} force | 53 * @param {boolean=} force |
161 * @return {!Promise<!UI.SuggestBox.Suggestions>} | 54 * @return {!Promise<!UI.SuggestBox.Suggestions>} |
162 */ | 55 */ |
163 Components.JavaScriptAutocomplete.completionsForExpression = function(expression
String, query, force) { | 56 Components.JavaScriptAutocomplete.completionsForExpression = function(expression
String, query, force) { |
164 var executionContext = UI.context.flavor(SDK.ExecutionContext); | 57 var executionContext = UI.context.flavor(SDK.ExecutionContext); |
165 if (!executionContext) | 58 if (!executionContext) |
166 return Promise.resolve([]); | 59 return Promise.resolve([]); |
167 | 60 |
168 var lastIndex = expressionString.length - 1; | 61 var lastIndex = expressionString.length - 1; |
169 | 62 |
170 var dotNotation = (expressionString[lastIndex] === '.'); | 63 var dotNotation = (expressionString[lastIndex] === '.'); |
171 var bracketNotation = (expressionString.length > 1 && expressionString[lastInd
ex] === '['); | 64 var bracketNotation = (expressionString.length > 1 && expressionString[lastInd
ex] === '['); |
172 | 65 |
173 if (dotNotation || bracketNotation) | 66 if (dotNotation || bracketNotation) |
174 expressionString = expressionString.substr(0, lastIndex); | 67 expressionString = expressionString.substr(0, lastIndex); |
175 else | 68 else |
176 expressionString = ''; | 69 expressionString = ''; |
177 | 70 |
178 // User is entering float value, do not suggest anything. | 71 // User is entering float value, do not suggest anything. |
179 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu
ery && !isNaN(query))) | 72 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu
ery && !isNaN(query))) |
180 return Promise.resolve([]); | 73 return Promise.resolve([]); |
181 | 74 |
182 | 75 |
183 if (!query && !expressionString && !force) | 76 if (!query && !expressionString && !force) |
184 return Promise.resolve([]); | 77 return Promise.resolve([]); |
185 | 78 |
186 var fulfill; | 79 var fufill; |
187 var promise = new Promise(x => fulfill = x); | 80 var promise = new Promise(x => fufill = x); |
188 var selectedFrame = executionContext.debuggerModel.selectedCallFrame(); | 81 var selectedFrame = executionContext.debuggerModel.selectedCallFrame(); |
189 if (!expressionString && selectedFrame) | 82 if (!expressionString && selectedFrame) |
190 variableNamesInScopes(selectedFrame, receivedPropertyNames); | 83 variableNamesInScopes(selectedFrame, receivedPropertyNames); |
191 else | 84 else |
192 executionContext.evaluate(expressionString, 'completion', true, true, false,
false, false, evaluated); | 85 executionContext.evaluate(expressionString, 'completion', true, true, false,
false, false, evaluated); |
193 | 86 |
194 return promise; | 87 return promise; |
195 /** | 88 /** |
196 * @param {?SDK.RemoteObject} result | 89 * @param {?SDK.RemoteObject} result |
197 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 90 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
198 */ | 91 */ |
199 function evaluated(result, exceptionDetails) { | 92 function evaluated(result, exceptionDetails) { |
200 if (!result || !!exceptionDetails) { | 93 if (!result || !!exceptionDetails) { |
201 fulfill([]); | 94 fufill([]); |
202 return; | 95 return; |
203 } | 96 } |
204 | 97 |
205 /** | 98 /** |
206 * @param {?SDK.RemoteObject} object | 99 * @param {?SDK.RemoteObject} object |
207 * @return {!Promise<?SDK.RemoteObject>} | 100 * @return {!Promise<?SDK.RemoteObject>} |
208 */ | 101 */ |
209 function extractTarget(object) { | 102 function extractTarget(object) { |
210 if (!object) | 103 if (!object) |
211 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null)); | 104 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null)); |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
319 | 212 |
320 /** | 213 /** |
321 * @param {?SDK.RemoteObject} result | 214 * @param {?SDK.RemoteObject} result |
322 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails | 215 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails |
323 */ | 216 */ |
324 function receivedPropertyNamesFromEval(result, exceptionDetails) { | 217 function receivedPropertyNamesFromEval(result, exceptionDetails) { |
325 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 218 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
326 if (result && !exceptionDetails) | 219 if (result && !exceptionDetails) |
327 receivedPropertyNames(/** @type {!Object} */ (result.value)); | 220 receivedPropertyNames(/** @type {!Object} */ (result.value)); |
328 else | 221 else |
329 fulfill([]); | 222 fufill([]); |
330 } | 223 } |
331 | 224 |
332 /** | 225 /** |
333 * @param {?Object} object | 226 * @param {?Object} object |
334 */ | 227 */ |
335 function receivedPropertyNames(object) { | 228 function receivedPropertyNames(object) { |
336 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); | 229 executionContext.target().runtimeAgent().releaseObjectGroup('completion'); |
337 if (!object) { | 230 if (!object) { |
338 fulfill([]); | 231 fufill([]); |
339 return; | 232 return; |
340 } | 233 } |
341 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co
mpletionGroup>} */ (object); | 234 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co
mpletionGroup>} */ (object); |
342 var includeCommandLineAPI = (!dotNotation && !bracketNotation); | 235 var includeCommandLineAPI = (!dotNotation && !bracketNotation); |
343 if (includeCommandLineAPI) { | 236 if (includeCommandLineAPI) { |
344 const commandLineAPI = [ | 237 const commandLineAPI = [ |
345 'dir', | 238 'dir', |
346 'dirxml', | 239 'dirxml', |
347 'keys', | 240 'keys', |
348 'values', | 241 'values', |
349 'profile', | 242 'profile', |
350 'profileEnd', | 243 'profileEnd', |
351 'monitorEvents', | 244 'monitorEvents', |
352 'unmonitorEvents', | 245 'unmonitorEvents', |
353 'inspect', | 246 'inspect', |
354 'copy', | 247 'copy', |
355 'clear', | 248 'clear', |
356 'getEventListeners', | 249 'getEventListeners', |
357 'debug', | 250 'debug', |
358 'undebug', | 251 'undebug', |
359 'monitor', | 252 'monitor', |
360 'unmonitor', | 253 'unmonitor', |
361 'table', | 254 'table', |
362 '$', | 255 '$', |
363 '$$', | 256 '$$', |
364 '$x' | 257 '$x' |
365 ]; | 258 ]; |
366 propertyGroups.push({items: commandLineAPI}); | 259 propertyGroups.push({items: commandLineAPI}); |
367 } | 260 } |
368 fulfill(Components.JavaScriptAutocomplete._completionsForQuery( | 261 fufill(Components.JavaScriptAutocomplete._completionsForQuery( |
369 dotNotation, bracketNotation, expressionString, query, propertyGroups)); | 262 dotNotation, bracketNotation, expressionString, query, propertyGroups)); |
370 } | 263 } |
371 }; | 264 }; |
372 | 265 |
373 /** | 266 /** |
374 * @param {boolean} dotNotation | 267 * @param {boolean} dotNotation |
375 * @param {boolean} bracketNotation | 268 * @param {boolean} bracketNotation |
376 * @param {string} expressionString | 269 * @param {string} expressionString |
377 * @param {string} query | 270 * @param {string} query |
378 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert
yGroups | 271 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert
yGroups |
(...skipping 13 matching lines...) Expand all Loading... |
392 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
'else', 'finally', | 285 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
'else', 'finally', |
393 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return
', 'switch', 'this', | 286 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return
', 'switch', 'this', |
394 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' | 287 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with' |
395 ]; | 288 ]; |
396 propertyGroups.push({title: Common.UIString('keywords'), items: keywords}); | 289 propertyGroups.push({title: Common.UIString('keywords'), items: keywords}); |
397 } | 290 } |
398 | 291 |
399 var result = []; | 292 var result = []; |
400 var lastGroupTitle; | 293 var lastGroupTitle; |
401 for (var group of propertyGroups) { | 294 for (var group of propertyGroups) { |
402 group.items.sort(itemComparator.bind(null, group.items.length > 1000)); | 295 group.items.sort(itemComparator); |
403 var caseSensitivePrefix = []; | 296 var caseSensitivePrefix = []; |
404 var caseInsensitivePrefix = []; | 297 var caseInsensitivePrefix = []; |
405 var caseSensitiveAnywhere = []; | 298 var caseSensitiveAnywhere = []; |
406 var caseInsensitiveAnywhere = []; | 299 var caseInsensitiveAnywhere = []; |
407 | 300 |
408 for (var property of group.items) { | 301 for (var property of group.items) { |
409 // Assume that all non-ASCII characters are letters and thus can be used a
s part of identifier. | 302 // Assume that all non-ASCII characters are letters and thus can be used a
s part of identifier. |
410 if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFF
F]*$/.test(property)) | 303 if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFF
F]*$/.test(property)) |
411 continue; | 304 continue; |
412 | 305 |
(...skipping 23 matching lines...) Expand all Loading... |
436 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere,
caseInsensitiveAnywhere); | 329 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere,
caseInsensitiveAnywhere); |
437 if (structuredGroup.length && group.title !== lastGroupTitle) { | 330 if (structuredGroup.length && group.title !== lastGroupTitle) { |
438 structuredGroup[0].subtitle = group.title; | 331 structuredGroup[0].subtitle = group.title; |
439 lastGroupTitle = group.title; | 332 lastGroupTitle = group.title; |
440 } | 333 } |
441 result = result.concat(structuredGroup); | 334 result = result.concat(structuredGroup); |
442 } | 335 } |
443 return result; | 336 return result; |
444 | 337 |
445 /** | 338 /** |
446 * @param {boolean} naturalOrder | |
447 * @param {string} a | 339 * @param {string} a |
448 * @param {string} b | 340 * @param {string} b |
449 * @return {number} | 341 * @return {number} |
450 */ | 342 */ |
451 function itemComparator(naturalOrder, a, b) { | 343 function itemComparator(a, b) { |
452 var aStartsWithUnderscore = a.startsWith('_'); | 344 var aStartsWithUnderscore = a.startsWith('_'); |
453 var bStartsWithUnderscore = b.startsWith('_'); | 345 var bStartsWithUnderscore = b.startsWith('_'); |
454 if (aStartsWithUnderscore && !bStartsWithUnderscore) | 346 if (aStartsWithUnderscore && !bStartsWithUnderscore) |
455 return 1; | 347 return 1; |
456 if (bStartsWithUnderscore && !aStartsWithUnderscore) | 348 if (bStartsWithUnderscore && !aStartsWithUnderscore) |
457 return -1; | 349 return -1; |
458 return naturalOrder ? String.naturalOrderComparator(a, b) : a.localeCompare(
b); | 350 return String.naturalOrderComparator(a, b); |
459 } | 351 } |
460 }; | 352 }; |
OLD | NEW |