diff options
| author | Stephen Blott | 2015-05-04 06:35:22 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-04 09:49:27 +0100 |
| commit | 136b132c81ae4f7a3b296109427fd757ca05dacf (patch) | |
| tree | 6e0a60512017799e78c22627ed3763b60ef6bd9c /background_scripts/completion.coffee | |
| parent | 776f617ece5d333fe70df903982a18d65fc2776a (diff) | |
| download | vimium-136b132c81ae4f7a3b296109427fd757ca05dacf.tar.bz2 | |
Search completion; tweak scoring and synchronization.
Diffstat (limited to 'background_scripts/completion.coffee')
| -rw-r--r-- | background_scripts/completion.coffee | 121 |
1 files changed, 71 insertions, 50 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index fee4778a..37e9ea6b 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -335,42 +335,67 @@ class SearchEngineCompleter else (string) -> Utils.createSearchUrl string.split /\s+/ - type = if description? then description else "search" + haveDescription = description? and 0 < description.trim().length + type = if haveDescription then description else "search" searchUrl = if custom then url else Settings.get "searchUrl" - query = queryTerms[(if custom then 1 else 0)..].join " " # For custom search engines, we add an auto-selected suggestion. if custom - title = if description? then query else queryTerms[0] + ": " + query + query = queryTerms[1..].join " " + title = if haveDescription then query else keyword + ": " + query suggestions.push @mkSuggestion false, queryTerms, type, mkUrl(query), title, @computeRelevancy, 1 suggestions[0].autoSelect = true queryTerms = queryTerms[1..] - onComplete suggestions, (onComplete) => + if queryTerms.length == 0 + return onComplete suggestions + + onComplete suggestions, (existingSuggestions, onComplete) => suggestions = [] # For custom search-engine queries, this adds suggestions only if we have a completer. For other queries, # this adds suggestions for the default search engine (if we have a completer for that). - SearchEngines.complete searchUrl, queryTerms, (searchSuggestions = []) => - - # Scoring: - # - The score does not depend upon the actual suggestion (so, it does not depend upon word relevancy). - # We assume that the completion engine has already factored that in. - # - The score is higher if the query is longer. The idea is that search suggestions are more likely - # to be relevant if, after typing quite some number of characters, the user hasn't yet found a - # useful suggestion from another completer. - # - Scores are weighted such that they retain the ordering provided by the completion engine. - characterCount = query.length - queryTerms.length + 1 - score = 0.8 * (Math.min(characterCount, 12.0)/12.0) - - for suggestion in searchSuggestions - suggestions.push @mkSuggestion true, queryTerms, type, mkUrl(suggestion), suggestion, @computeRelevancy, score - score *= 0.9 - if custom - for suggestion in suggestions - suggestion.reinsertPrefix = "#{keyword} " if suggestion.insertText - - onComplete suggestions + # Scoring: + # - The score does not depend upon the actual suggestion (so, it does not depend upon word + # relevancy). We assume that the completion engine has already factored that in. Also, completion + # engines often handle spelling mistakes, in which case we wouldn't find the query terms in the + # suggestion anyway. + # - The score is based on the length of the last query term. The idea is that the user is already + # happy with the earlier terms. + # - The score is higher if the last query term is longer. The idea is that search suggestions are more + # likely to be relevant if, after typing some number of characters, the user hasn't yet found + # a useful suggestion from another completer. + # - Scores are weighted such that they retain the order provided by the completion engine. + characterCount = queryTerms[queryTerms.length - 1].length + score = 0.6 * (Math.min(characterCount, 10.0)/10.0) + + if 0 < existingSuggestions.length + existingSuggestionMinScore = existingSuggestions[existingSuggestions.length-1].relevancy + if score < existingSuggestionMinScore and MultiCompleter.maxResults <= existingSuggestions.length + # No suggestion we propose will have a high enough score to beat the existing suggestions, so bail + # immediately. + return onComplete [] + + # We pause in case the user is still typing. + Utils.setTimeout 250, handler = @mostRecentHandler = => + return onComplete [] if handler != @mostRecentHandler # Bail if another completion has begun. + + SearchEngines.complete searchUrl, queryTerms, (searchSuggestions = []) => + for suggestion in searchSuggestions + suggestions.push @mkSuggestion true, queryTerms, type, mkUrl(suggestion), suggestion, @computeRelevancy, score + score *= 0.9 + + if custom + # For custom search engines, we need to tell the front end to insert the search engine's keyword + # when copying a suggestion into the vomnibar. + suggestion.reinsertPrefix = "#{keyword} " for suggestion in suggestions + + # We keep at least three suggestions (if possible) and at most six. We keep more than three only if + # 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 + onComplete suggestions[...count] mkSuggestion: (insertText, args...) -> suggestion = new Suggestion args... @@ -412,8 +437,10 @@ class SearchEngineCompleter # 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 + constructor: (@completers) -> - @maxResults = 10 + @maxResults = MultiCompleter.maxResults refresh: -> completer.refresh?() for completer in @completers @@ -429,36 +456,30 @@ class MultiCompleter suggestions = [] completersFinished = 0 continuation = null + # Call filter() on every source completer and wait for them all to finish before returning results. + # At most one of the completers (SearchEngineCompleter) may pass a continuation function, which will be + # called after the results of all of the other completers have been posted. Any additional results + # from this continuation will be added to the existing results and posted later. We don't call the + # continuation if another query is already waiting. for completer in @completers - # Call filter() on every source completer and wait for them all to finish before returning results. - # At most one of the completers (SearchEngineCompleter) may pass a continuation function, which will be - # called asynchronously after the results of all of the other completers have been posted. Any - # additional results from this continuation will be added to the existing results and posted. We don't - # call the continuation if another query is already waiting. - completer.filter queryTerms, (newSuggestions, cont = null) => - # Allow completers to execute concurrently. + do (completer) => Utils.nextTick => - suggestions = suggestions.concat newSuggestions - continuation = cont if cont? - completersFinished += 1 - if completersFinished >= @completers.length - onComplete @prepareSuggestions(suggestions), keepAlive: continuation? - onDone = => + completer.filter queryTerms, (newSuggestions, cont = null) => + suggestions = suggestions.concat newSuggestions + continuation = cont if cont? + if @completers.length <= ++completersFinished + shouldRunContinuation = continuation? and not @mostRecentQuery + onComplete @prepareSuggestions(queryTerms, suggestions), keepAlive: shouldRunContinuation + # Allow subsequent queries to begin. @filterInProgress = false - @filter @mostRecentQuery.queryTerms, @mostRecentQuery.onComplete if @mostRecentQuery - # We add a very short delay. It is possible for all of this processing to have been handled - # pretty-much synchronously, which would have prevented any newly-arriving queries from - # registering. - Utils.setTimeout 10, => - if continuation? and not @mostRecentQuery - continuation (newSuggestions) => - onComplete @prepareSuggestions suggestions.concat(newSuggestions) - onDone() + if shouldRunContinuation + continuation suggestions, (newSuggestions) => + onComplete @prepareSuggestions queryTerms, suggestions.concat(newSuggestions) else - onDone() + @filter @mostRecentQuery.queryTerms, @mostRecentQuery.onComplete if @mostRecentQuery - prepareSuggestions: (suggestions) -> - suggestion.computeRelevancy @queryTerms for suggestion in suggestions + prepareSuggestions: (queryTerms, suggestions) -> + suggestion.computeRelevancy queryTerms for suggestion in suggestions suggestions.sort (a, b) -> b.relevancy - a.relevancy suggestions = suggestions[0...@maxResults] suggestion.generateHtml() for suggestion in suggestions |
