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

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

Issue 2557763003: DevTools: sort completions by prototype. (Closed)
Patch Set: same w/ cross-origin check Created 4 years 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>}} */
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698