OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this | 3 * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this |
4 * source code is governed by a BSD-style license that can be found in the | 4 * source code is governed by a BSD-style license that can be found in the |
5 * LICENSE file. | 5 * LICENSE file. |
6 --> | 6 --> |
7 <html> | 7 <html> |
8 <head> | 8 <head> |
9 </head> | 9 </head> |
10 <body> | 10 <body> |
(...skipping 24 matching lines...) Expand all Loading... |
35 * Adds an entry to the index. | 35 * Adds an entry to the index. |
36 * @param {String} name Name of the function (e.g. chrome.tabs.get). | 36 * @param {String} name Name of the function (e.g. chrome.tabs.get). |
37 * @param {String} url Url to the documentation. | 37 * @param {String} url Url to the documentation. |
38 * @param {String} desc Description (optional). | 38 * @param {String} desc Description (optional). |
39 * @param {String} type The type of entry (e.g. method, event). | 39 * @param {String} type The type of entry (e.g. method, event). |
40 */ | 40 */ |
41 APISearchCorpus.prototype.addEntry = function(name, url, desc, type) { | 41 APISearchCorpus.prototype.addEntry = function(name, url, desc, type) { |
42 this.corpus_.push({ | 42 this.corpus_.push({ |
43 'name' : name, | 43 'name' : name, |
44 'url' : url, | 44 'url' : url, |
45 'ranges' : [], | 45 'style' : name, |
46 'description' : desc, | 46 'description' : desc, |
47 'type' : type | 47 'type' : type |
48 }); | 48 }); |
49 }; | 49 }; |
50 | 50 |
51 /** | 51 /** |
52 * Locates a match from the supplied keywords against text. | 52 * Locates a match from the supplied keywords against text. |
53 * | 53 * |
54 * Keywords are matched in the order supplied, and a non-overlapping | 54 * Keywords are matched in the order supplied, and a non-overlapping |
55 * search is used. The ranges are returned in a way that is easily | 55 * search is used. The matches are returned in a styled string that |
56 * converted to the style array required by the omnibox API. | 56 * can be passed directly to the omnibox API. |
57 * | 57 * |
58 * @param {Array.<String>} keywords A list of keywords to check. | 58 * @param {Array.<String>} keywords A list of keywords to check. |
59 * @param {String} name The name to search against. | 59 * @param {String} name The name to search against. |
60 * @returns {Array.<Array.<number>>|null} A list of indexes corresponding | 60 * @returns {String|null} A string containing <match> markup |
61 * to matches, or null if no match was found. | 61 * corresponding to the matched text, or null if no match was found. |
62 */ | 62 */ |
63 APISearchCorpus.prototype.findMatch_ = function(keywords, name) { | 63 APISearchCorpus.prototype.findMatch_ = function(keywords, name) { |
64 var ranges = []; | 64 var style = []; |
65 var indexFrom = 0; | 65 var indexFrom = 0; |
| 66 var lowerName = name.toLowerCase(); |
66 for (var i = 0; i < keywords.length; i++) { | 67 for (var i = 0; i < keywords.length; i++) { |
67 var keyword = keywords[i].toLowerCase(); | 68 var keyword = keywords[i].toLowerCase(); |
68 var start = name.indexOf(keyword, indexFrom); | 69 var start = lowerName.indexOf(keyword, indexFrom); |
69 if (start == -1) { | 70 if (start == -1) { |
70 return null; | 71 return null; |
71 } | 72 } |
72 var end = start + keyword.length; | 73 var end = start + keyword.length + 1; |
73 ranges.push([start, end]); | 74 |
74 indexFrom = end + 1; | 75 style.push(name.substring(indexFrom, start)) |
| 76 style.push('<match>'); |
| 77 style.push(name.substring(start, end)); |
| 78 style.push('</match>'); |
| 79 |
| 80 indexFrom = end; |
75 } | 81 } |
76 return ranges; | 82 style.push(name.substring(indexFrom)); |
| 83 return style.join(''); |
77 }; | 84 }; |
78 | 85 |
79 /** | 86 /** |
80 * Searches this corpus for the supplied text. | 87 * Searches this corpus for the supplied text. |
81 * @param {String} text Query text. | 88 * @param {String} text Query text. |
82 * @param {Number} limit Max results to return. | 89 * @param {Number} limit Max results to return. |
83 * @returns {Array.<Object>} A list of entries corresponding with | 90 * @returns {Array.<Object>} A list of entries corresponding with |
84 * matches (@see APISearchCorpus.findMatch_ for keyword search | 91 * matches (@see APISearchCorpus.findMatch_ for keyword search |
85 * algorithm. Results are returned in a sorted order, first by | 92 * algorithm. Results are returned in a sorted order, first by |
86 * length, then alphabetically by name. An exact match will be | 93 * length, then alphabetically by name. An exact match will be |
87 * returned first. | 94 * returned first. |
88 */ | 95 */ |
89 APISearchCorpus.prototype.search = function(text, limit) { | 96 APISearchCorpus.prototype.search = function(text, limit) { |
90 var results = []; | 97 var results = []; |
91 var match = null; | 98 var match = null; |
92 if (!text || text.length == 0) { | 99 if (!text || text.length == 0) { |
93 return this.corpus_.slice(0, limit); // No text, start listing APIs. | 100 return this.corpus_.slice(0, limit); // No text, start listing APIs. |
94 } | 101 } |
95 var searchText = text.toLowerCase(); | 102 var searchText = text.toLowerCase(); |
96 var keywords = searchText.split(' '); | 103 var keywords = searchText.split(' '); |
97 for (var i = 0; i < this.corpus_.length; i++) { | 104 for (var i = 0; i < this.corpus_.length; i++) { |
98 var name = this.corpus_[i]['name'].toLowerCase(); | 105 var name = this.corpus_[i]['name']; |
99 if (results.length < limit) { | 106 if (results.length < limit) { |
100 var result = this.findMatch_(keywords, name); | 107 var result = this.findMatch_(keywords, name); |
101 if (result) { | 108 if (result) { |
102 this.corpus_[i]['ranges'] = result; | 109 this.corpus_[i]['style'] = result; |
103 results.push(this.corpus_[i]); | 110 results.push(this.corpus_[i]); |
104 } | 111 } |
105 } | 112 } |
106 if (!match && searchText == name) { | 113 if (!match && searchText == name) { |
107 match = this.corpus_[i]; // An exact match. | 114 match = this.corpus_[i]; // An exact match. |
108 } | 115 } |
109 if (match && results.length >= limit) { | 116 if (match && results.length >= limit) { |
110 break; // Have an exact match and have reached the search limit. | 117 break; // Have an exact match and have reached the search limit. |
111 } | 118 } |
112 } | 119 } |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 this.onChanged_.bind(this)); | 312 this.onChanged_.bind(this)); |
306 chrome.omnibox.onInputEntered.addListener( | 313 chrome.omnibox.onInputEntered.addListener( |
307 this.onEntered_.bind(this)); | 314 this.onEntered_.bind(this)); |
308 }; | 315 }; |
309 | 316 |
310 /** | 317 /** |
311 * Converts a corpus match to an object suitable for the omnibox API. | 318 * Converts a corpus match to an object suitable for the omnibox API. |
312 * @param {Object} match The match to convert. | 319 * @param {Object} match The match to convert. |
313 * @returns {Object} A suggestion object formatted for the omnibox API. | 320 * @returns {Object} A suggestion object formatted for the omnibox API. |
314 */ | 321 */ |
315 OmniboxManager.prototype.matchToSuggestion_ = function(match) { | 322 OmniboxManager.prototype.convertMatchToSuggestion_ = function(match) { |
316 var styles = [ ]; | 323 var suggestion = [ match['style'] ]; |
317 var ranges = match['ranges']; | |
318 var desc = match['name']; | |
319 var name = match['name']; | |
320 var lastIndex = 0; | |
321 for (var i = 0; i < ranges.length; i++) { | |
322 styles.push(chrome.omnibox.styleMatch(ranges[i][0])); | |
323 styles.push(chrome.omnibox.styleNone(ranges[i][1])); | |
324 lastIndex = ranges[i][1]; | |
325 } | |
326 | |
327 if (match['type']) { | 324 if (match['type']) { |
328 // Abusing the URL style a little, but want this to stand out. | 325 // Abusing the URL style a little, but want this to stand out. |
329 desc = this.pushStyle_(styles, 'Url', desc, match['type']); | 326 suggestion.push(['<url>', match['type'], '</url>'].join('')); |
330 lastIndex = desc.length; | |
331 } | 327 } |
332 if (match['description']) { | 328 if (match['description']) { |
333 desc = this.pushStyle_(styles, 'Dim', desc, match['description']); | 329 suggestion.push(['<dim>', match['description'], '</dim>'].join('')); |
334 lastIndex = desc.length; | |
335 } | 330 } |
336 | |
337 if (lastIndex == desc.length) styles.pop(); | |
338 | |
339 return { | 331 return { |
340 'content' : name, | 332 'content' : match['name'], |
341 'description' : desc, | 333 'description' : suggestion.join(' - ') |
342 'descriptionStyles' : styles | 334 } |
343 }; | |
344 }; | 335 }; |
345 | 336 |
346 /** | 337 /** |
347 * Suggests a list of possible matches when omnibox text changes. | 338 * Suggests a list of possible matches when omnibox text changes. |
348 * @param {String} text Text input from the omnibox. | 339 * @param {String} text Text input from the omnibox. |
349 * @param {Function} suggest Callback to execute with a list of | 340 * @param {Function} suggest Callback to execute with a list of |
350 * suggestion objects, if any matches were found. | 341 * suggestion objects, if any matches were found. |
351 */ | 342 */ |
352 OmniboxManager.prototype.onChanged_ = function(text, suggest) { | 343 OmniboxManager.prototype.onChanged_ = function(text, suggest) { |
353 var matches = this.corpus_.search(text, 10); | 344 var matches = this.corpus_.search(text, 10); |
354 var suggestions = []; | 345 var suggestions = []; |
355 for (var i = 0; i < matches.length; i++) { | 346 for (var i = 0; i < matches.length; i++) { |
356 var suggestion = this.matchToSuggestion_(matches[i]); | 347 var suggestion = this.convertMatchToSuggestion_(matches[i]); |
357 suggestions.push(suggestion); | 348 suggestions.push(suggestion); |
358 } | 349 } |
359 suggest(suggestions); | 350 suggest(suggestions); |
360 }; | 351 }; |
361 | 352 |
362 /** | 353 /** |
363 * Opens the most appropriate URL when enter is pressed in the omnibox. | 354 * Opens the most appropriate URL when enter is pressed in the omnibox. |
364 * | 355 * |
365 * Note that the entered text does not have to be exact - the first | 356 * Note that the entered text does not have to be exact - the first |
366 * search result is automatically opened when enter is pressed. | 357 * search result is automatically opened when enter is pressed. |
367 * | 358 * |
368 * @param {String} text The text entered. | 359 * @param {String} text The text entered. |
369 */ | 360 */ |
370 OmniboxManager.prototype.onEntered_ = function(text) { | 361 OmniboxManager.prototype.onEntered_ = function(text) { |
371 var matches = this.corpus_.search(text, 1); | 362 var matches = this.corpus_.search(text, 1); |
372 if (matches.length > 0) { | 363 if (matches.length > 0) { |
373 this.tabManager_.open(matches[0]['url']); | 364 this.tabManager_.open(matches[0]['url']); |
374 } | 365 } |
375 }; | 366 }; |
376 | 367 |
377 /** | |
378 * Helper function for constructing a suggestion style list. | |
379 * | |
380 * Adds a separator and text to a description, and modifies an array | |
381 * of styles so that the separator is not styled and the additional text | |
382 * obtains the requested style type. | |
383 * | |
384 * This method expects the list of styles to end with a styleNone style. | |
385 * It will modify the list so that the last element is a styleNone style. | |
386 * The last element will always be at the end of the returned string, | |
387 * which will throw an error unless it is removed before being passed | |
388 * to the API (this method is intended to be called a few times in a row). | |
389 * @see OmniboxManager.matchToSuggestion_ for code that removes this last | |
390 * entry. | |
391 * | |
392 * @param {Array.<Object>} styles An array of styles, in the format | |
393 * expected by the omnibox API. | |
394 * @param {String} type The style type to apply - must be one of | |
395 * "Dim", "Match", "None", and "Url". | |
396 * @param {String} desc The description text to append to and style. | |
397 * @param {String} text The text to append to the description. | |
398 * @returns {String} The description plus a separator and the supplied | |
399 * text, intended to overwrite the variable which was passed into | |
400 * this function as "desc". | |
401 */ | |
402 OmniboxManager.prototype.pushStyle_ = function(styles, type, desc, text) { | |
403 desc += this.SEPARATOR; | |
404 styles.push(chrome.omnibox['style' + type](desc.length)); | |
405 desc += text; | |
406 styles.push(chrome.omnibox.styleNone(desc.length)); | |
407 return desc; | |
408 }; | |
409 | |
410 ////////////////////////////////////////////////////////////////////////// | 368 ////////////////////////////////////////////////////////////////////////// |
411 | 369 |
412 /** | 370 /** |
413 * Manages opening urls in tabs. | 371 * Manages opening urls in tabs. |
414 * @constructor | 372 * @constructor |
415 */ | 373 */ |
416 function TabManager() { | 374 function TabManager() { |
417 this.tab_ = null; | 375 this.tab_ = null; |
418 chrome.tabs.onRemoved.addListener(this.onRemoved_.bind(this)); | 376 chrome.tabs.onRemoved.addListener(this.onRemoved_.bind(this)); |
419 }; | 377 }; |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 ////////////////////////////////////////////////////////////////////////// | 415 ////////////////////////////////////////////////////////////////////////// |
458 | 416 |
459 var corpus = new APISearchCorpus(); | 417 var corpus = new APISearchCorpus(); |
460 var docsManager = new DocsManager(corpus); | 418 var docsManager = new DocsManager(corpus); |
461 docsManager.fetch(); | 419 docsManager.fetch(); |
462 var tabManager = new TabManager(); | 420 var tabManager = new TabManager(); |
463 var omnibox = new OmniboxManager(corpus, tabManager); | 421 var omnibox = new OmniboxManager(corpus, tabManager); |
464 </script> | 422 </script> |
465 </body> | 423 </body> |
466 </html> | 424 </html> |
OLD | NEW |