diff options
| author | Stephen Blott | 2015-05-10 13:03:32 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-10 14:14:02 +0100 |
| commit | 09b2aad039c7894c6023100d03c9941649843b77 (patch) | |
| tree | 52a4f2f1f1fbdab2cb93e0f7e5c95b490498d10c | |
| parent | 9bc1c215e3857d109fca2a073fd50799e0021cc8 (diff) | |
| download | vimium-09b2aad039c7894c6023100d03c9941649843b77.tar.bz2 | |
Search completion; more minor tweaks.
| -rw-r--r-- | background_scripts/completion.coffee | 27 | ||||
| -rw-r--r-- | background_scripts/completion_engines.coffee | 17 | ||||
| -rw-r--r-- | pages/vomnibar.coffee | 45 |
3 files changed, 58 insertions, 31 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index 4f0401f0..4fa91b9d 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -384,7 +384,7 @@ class SearchEngineCompleter handler: "keywords" keywords: key for own key of engines - filter: ({ queryTerms, query }, onComplete) -> + filter: ({ queryTerms, query, maxResults }, onComplete) -> return onComplete [] if queryTerms.length == 0 @searchEngines.use (engines) => @@ -421,11 +421,11 @@ class SearchEngineCompleter # This distinguishes two very different kinds of vomnibar baviours, the newer bahviour (true) and the # legacy behavior (false). We retain the latter for the default search engine, and for custom search # engines for which we do not have a completion engine. - version2 = custom and haveCompletionEngine + useExclusiveVomnibar = custom and haveCompletionEngine # If this is a custom search engine and we have a completer, then we exclude results from other # completers. - filter = if version2 then (suggestion) -> suggestion.type == description else null + filter = if useExclusiveVomnibar then (suggestion) -> suggestion.type == description else null suggestions = [] @@ -439,17 +439,17 @@ class SearchEngineCompleter url: Utils.createSearchUrl queryTerms, searchUrl title: query relevancy: 1 - insertText: if version2 then query else null + insertText: if useExclusiveVomnibar then query else null # We suppress the leading keyword, for example "w something" becomes "something" in the vomnibar. suppressLeadingKeyword: true # Should we highlight (via the selection) the longest continuation of the current query which is # contained in all completions? - completeSuggestions: version2 + completeSuggestions: useExclusiveVomnibar # Toggles for the legacy behaviour. - autoSelect: not version2 - forceAutoSelect: not version2 - highlightTerms: not version2 - # Do not use this entry for vomnibar completion. + autoSelect: not useExclusiveVomnibar + forceAutoSelect: not useExclusiveVomnibar + highlightTerms: not useExclusiveVomnibar + # Do not use this entry for vomnibar completion (highlighting the common text of the suggestions). highlightCommonMatches: false mkSuggestion = do -> @@ -487,9 +487,10 @@ class SearchEngineCompleter if 0 < existingSuggestions.length existingSuggestionsMinScore = existingSuggestions[existingSuggestions.length-1].relevancy - if relavancy < existingSuggestionsMinScore and MultiCompleter.maxResults <= existingSuggestions.length + if relavancy < existingSuggestionsMinScore and maxResults <= existingSuggestions.length # No suggestion we propose will have a high enough relavancy to beat the existing suggestions, so bail # immediately. + console.log "skip: cannot add completions" if @debug return onComplete [] CompletionEngines.complete searchUrl, queryTerms, (completionSuggestions = []) => @@ -509,16 +510,15 @@ class SearchEngineCompleter # there are enough slots. The idea is that these suggestions shouldn't wholly displace suggestions # from other completers. That would potentially be a problem because there is no relationship # between the relevancy scores produced here and those produced by other completers. - count = Math.min 6, Math.max 3, MultiCompleter.maxResults - existingSuggestions.length + count = Math.min 6, Math.max 3, maxResults - existingSuggestions.length onComplete suggestions[...count] # A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top # 10. Queries from the vomnibar frontend script come through a multi completer. class MultiCompleter - @maxResults: 10 + maxResults: 10 constructor: (@completers) -> - @maxResults = MultiCompleter.maxResults refresh: (port) -> completer.refresh? port for completer in @completers @@ -533,6 +533,7 @@ class MultiCompleter RegexpCache.clear() { queryTerms } = request + request.maxResults = @maxResults @mostRecentQuery = null @filterInProgress = true diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 51799971..ac5c86aa 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -126,7 +126,8 @@ CompletionEngines = # The amount of time to wait for new requests before launching the HTTP request. The intention is to cut # down on the number of HTTP requests we issue. - delay: 100 + # delay: 100 + delay: 0 get: (searchUrl, url, callback) -> xhr = new XMLHttpRequest() @@ -190,20 +191,22 @@ CompletionEngines = else # We add a short delay, even for a cache hit. This avoids an ugly flicker when the additional # suggestions are posted. - Utils.setTimeout 75, => + Utils.setTimeout 50, => console.log "hit", completionCacheKey if @debug callback @completionCache.get completionCacheKey return if @mostRecentQuery? and @mostRecentSuggestions? - # If the user appears to be typing a continuation of the characters of the most recent query, and those - # characters are also common to all of the most recent suggestions, then we can re-use the previous - # suggestions. + # If the user appears to be typing a continuation of the characters of the most recent query, then we + # can re-use the previous suggestions. reusePreviousSuggestions = do (query) => query = queryTerms.join(" ").toLowerCase() + # Verify that the previous query is a prefix of the current query. return false unless 0 == query.indexOf @mostRecentQuery.toLowerCase() - previousSuggestions = @mostRecentSuggestions.map (s) -> s.toLowerCase() - return false unless query.length <= Utils.longestCommonPrefix previousSuggestions + # Ensure that every previous suggestion contains the text of the new query. + for suggestion in (@mostRecentSuggestions.map (s) -> s.toLowerCase()) + return false unless 0 <= suggestion.indexOf query + # Ok. Re-use the suggestion. true if reusePreviousSuggestions diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee index 8c655b07..85930aff 100644 --- a/pages/vomnibar.coffee +++ b/pages/vomnibar.coffee @@ -18,7 +18,7 @@ Vomnibar = newTab: false selectFirst: false extend options, userOptions - extend options, refreshInterval: if options.completer == "omni" then 100 else 0 + extend options, refreshInterval: if options.completer == "omni" then 150 else 0 completer = @getCompleter options.completer @vomnibarUI ?= new VomnibarUI() @@ -136,26 +136,40 @@ class VomnibarUI completions = @completions.filter (completion) -> completion.highlightCommonMatches? and completion.highlightCommonMatches - # Bail if these aren't any completions. - return unless 0 < completions.length - # Fetch the query and the suggestion texts. query = @input.value.ltrim().split(/\s+/).join(" ").toLowerCase() suggestions = completions.map (completion) -> completion.title - # Ensure that the query is a prefix of all of the suggestions. + # Some completion engines add text at the start of the suggestion; for example, Bing takes "they might be" + # and suggests "Ana Ng They Might be Giants". In such cases, we should still be able to complete + # "giants". So, if the query string is present in the suggestion but there is an extra prefix, we strip + # the prefix. + suggestions = + for suggestion in suggestions + index = Math.max 0, suggestion.toLowerCase().indexOf query + suggestion[index..] + + # Strip suggestions which aren't longer than the query (they can't help). + suggestions = suggestions.filter (suggestion) -> query.length < suggestion.length + + # Ensure that the query is a prefix of all remaining suggestions. for suggestion in suggestions return unless 0 == suggestion.toLowerCase().indexOf query - # Calculate the length of the shotest suggestion. + # Bail if these aren't any remaining completions. + return unless 0 < completions.length + + # Calculate the length of the shortest suggestion. length = suggestions[0].length length = Math.min length, suggestion.length for suggestion in suggestions # Find the the length of the longest common continuation. - length = do -> + length = do (suggestions) -> + suggestions = suggestions.map (s) -> s.toLowerCase() + [ first, suggestions... ] = suggestions for index in [query.length...length] for suggestion in suggestions - return index if suggestions[0][index].toLowerCase() != suggestion[index].toLowerCase() + return index if first[index] != suggestion[index] length # Bail if there's nothing to complete. @@ -164,8 +178,13 @@ class VomnibarUI # Don't highlight only whitespace (that is, the entire common text consists only of whitespace). return if /^\s+$/.test suggestions[0].slice query.length, length + completion = suggestions[0].slice query.length, length + + # If the typed text is all lower case, then make the completion lower case too. + completion = completion.toLowerCase() unless /[A-Z]/.test @input.value + # Highlight match. - @input.value = suggestions[0].slice 0, length + @input.value = @input.value + completion @input.setSelectionRange query.length, length # @@ -205,6 +224,7 @@ class VomnibarUI if @inputContainsASelectionRange() # The first tab collapses the selection to the end. window.getSelection()?.collapseToEnd() + @updateOnInput() else # Subsequent tabs behave the same as "down". action = "down" @@ -258,10 +278,13 @@ class VomnibarUI onKeypress: (event) => if @inputContainsASelectionRange() - if @input.value[@input.selectionStart][0] == String.fromCharCode event.charCode + # As the user types characters which match a highlighted completion suggestion (in the text input), we + # suppress the keyboard event and "simulate" it by advancing the start of the highlighted selection. We + # do this so that the selection doesn't flicker as the user types. + if @input.value[@input.selectionStart][0].toLowerCase() == (String.fromCharCode event.charCode).toLowerCase() console.log "extend selection:", @getInputWithoutSelectionRange() @input.setSelectionRange @input.selectionStart + 1, @input.selectionEnd - @update() + @updateOnInput() event.stopImmediatePropagation() event.preventDefault() true |
