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