Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(439)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js

Issue 2639703002: DevTools: Console: Provide autocompletions for Maps (Closed)
Patch Set: New RemoteObject api Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698