diff options
| author | Stephen Blott | 2015-05-13 20:02:50 +0100 | 
|---|---|---|
| committer | Stephen Blott | 2015-05-13 21:03:38 +0100 | 
| commit | afb9bec09d3212c56fa3b82ed89ebb34e3a3f562 (patch) | |
| tree | 58beac0528fa44768748e497af3a8936c2fddf98 /background_scripts | |
| parent | 176cac1249e1ee063ef0cadf05d7b090650686fb (diff) | |
| download | vimium-afb9bec09d3212c56fa3b82ed89ebb34e3a3f562.tar.bz2 | |
Search completion; complete rework of UX.
This is a major reworking of how the search-completion systems works.
Previously, the goal had been to emulate roughly how chrome itself
handles completions.  This has turned out to be a bad idea:
   - It creates issues around what it means to hit <Enter> in the
     vomnibar.
   - And the contents of the vomnibar change asynchronously (because we
     fetch completions asynchronously), so it creates the possibility
     that the effect of <Enter> changes depending on how long the user
     waits before typing <Enter>.
All of that is bad.
This commit changes things:
   - In normal omni mode, the vomnibar looks and behaves pretty much
     like it always has, just with some extra completion suggestions
     thrown in.
   - And in custom-search-engine mode it also behaves mostly as it has
     previously, but (again, possibly) with some extra completion
     suggestions thrown in, and with many useless suggestions excluded.
This is all far more Vimium-like than Chrome-like.
Diffstat (limited to 'background_scripts')
| -rw-r--r-- | background_scripts/completion.coffee | 65 | ||||
| -rw-r--r-- | background_scripts/completion_search.coffee | 14 | 
2 files changed, 32 insertions, 47 deletions
| diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index ebf56dde..68edad99 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -10,7 +10,7 @@  #  - refresh(): (optional) refreshes the completer's data source (e.g. refetches the list of bookmarks).  #  - cancel(): (optional) cancels any pending, cancelable action.  class Suggestion -  showRelevancy: false # Set this to true to render relevancy when debugging the ranking scores. +  showRelevancy: true # Set this to true to render relevancy when debugging the ranking scores.    constructor: (@options) ->      # Required options. @@ -397,20 +397,19 @@ class SearchEngineCompleter        callback engines        # Let the front-end vomnibar know the search-engine keywords.  It needs to know them so that, when the -      # query goes from "w" to "w ", the vomnibar synchronously launches the next filter() request (all of which avoids -      # an ugly delay). +      # query goes from "w" to "w ", the vomnibar can synchronously launch the next filter() request (which +      # avoids an ugly delay/flicker).        port.postMessage          handler: "keywords"          keywords: key for own key of engines    filter: (request, onComplete) ->      { queryTerms, query, engine } = request -    [ primarySuggestion, removePrimarySuggestion ] = [ null, false ]      { custom, searchUrl, description } =        if engine          { keyword, searchUrl, description } = engine -        extend request, { searchUrl, suppressLeadingKeyword: keyword } +        extend request, { searchUrl, customSearchMode: true }          custom: true          searchUrl: searchUrl          description: description @@ -421,35 +420,32 @@ class SearchEngineCompleter      return onComplete [] unless custom or 0 < queryTerms.length -    factor = Math.max 0, Math.min 1, Settings.get "omniSearchWeight" +    factor = Math.max 0.0, Math.min 1.0, Settings.get "omniSearchWeight"      haveCompletionEngine = (0.0 < factor or custom) and CompletionSearch.haveCompletionEngine searchUrl      # Relevancy:      #   - Relevancy does not depend upon the actual suggestion (so, it does not depend upon word      #     relevancy, say).  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. +    #     completion engines sometimes handle spelling mistakes, in which case we wouldn't find the query +    #     terms in the suggestion anyway.      #   - Scores are weighted such that they retain the order provided by the completion engine.      #   - The relavancy is higher if the 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.      #      characterCount = query.length - queryTerms.length + 1 -    relevancy = (if custom then 0.9 else factor) * (Math.min(characterCount, 12.0)/12.0) +    relevancy = (if custom then 0.5 else factor) * 12.0 / Math.max 12.0, characterCount +    console.log factor, relevancy -    # This filter is applied to all of the suggestions from all of the completers. +    # This filter is applied to all of the suggestions from all of the completers, after they have been +    # aggregated by the MultiCompleter.      filter = (suggestions) ->        return suggestions unless custom and haveCompletionEngine -      # The primary suggestion was just a guess.  If we've managed fetch actual completions (asynchronously), -      # then we now remove it. -      if removePrimarySuggestion -        suggestions = suggestions.filter (suggestion) -> suggestion != primarySuggestion -        # We only accept suggestions:        #   - from this completer, or -      #   - from other completers, but then only if their URL matches this search engine and this query -      #     (that is only if their URL could have been generated by this search engine). +      #   - from other completers, but then only if their URL matches this search engine and matches this +      #   query (that is only if their URL could have been generated by this search engine).        suggestions.filter (suggestion) ->          suggestion.type == description or            # This is a suggestion for the same search engine. @@ -466,7 +462,6 @@ class SearchEngineCompleter        autoSelect: custom        forceAutoSelect: custom        highlightTerms: not haveCompletionEngine -      searchSuggestionType: "primary"      mkSuggestion = (suggestion) ->        new Suggestion @@ -477,43 +472,27 @@ class SearchEngineCompleter          relevancy: relevancy *= 0.9          insertText: suggestion          highlightTerms: false -        searchSuggestionType: "completion" - -    deliverCompletions = (onComplete, completions, args...) -> -      # Make the first suggestion float to the top of the vomnibar (except if we would be competing with the -      # domain completer, which also assigns a relevancy of 1). -      if 0 < completions.length -        if custom # or (1 < queryTerms.length or /\S\s/.test query) -          extend completions[0], -            relevancy: 1 -            autoSelect: custom -            forceAutoSelect: custom -            insertText: null -      onComplete completions, args...      cachedSuggestions =        if haveCompletionEngine then CompletionSearch.complete searchUrl, queryTerms else null -    suggestions = -      if cachedSuggestions? and 0 < cachedSuggestions.length -        cachedSuggestions.map mkSuggestion -      else if custom -        [ primarySuggestion ] -      else -        [] +    suggestions = [] +    suggestions.push primarySuggestion if custom +    suggestions.push cachedSuggestions.map(mkSuggestion)... if custom and cachedSuggestions?      if queryTerms.length == 0 or cachedSuggestions? or not haveCompletionEngine        # There is no prospect of adding further completions. -      deliverCompletions onComplete, suggestions, { filter, continuation: null } +      suggestions.push cachedSuggestions.map(mkSuggestion)... if cachedSuggestions? +      onComplete suggestions, { filter, continuation: null }      else -      # Post the initial suggestions, then deliver further completions asynchronously, as a continuation. -      deliverCompletions onComplete, suggestions, +      # Post the initial suggestions, but then deliver any further completions asynchronously, as a +      # continuation. +      onComplete suggestions,          filter: filter          continuation: (onComplete) =>            CompletionSearch.complete searchUrl, queryTerms, (suggestions = []) =>              console.log "fetched suggestions:", suggestions.length, query if SearchEngineCompleter.debug -            removePrimarySuggestion = 0 < suggestions.length -            deliverCompletions onComplete, suggestions.map mkSuggestion +            onComplete suggestions.map mkSuggestion  # A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top  # 10. All queries from the vomnibar come through a multi completer. diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index a9521a3d..c6824594 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -1,6 +1,6 @@  CompletionSearch = -  debug: false +  debug: true    inTransit: {}    completionCache: new SimpleCache 2 * 60 * 60 * 1000, 5000 # Two hours, 5000 entries.    engineCache:new SimpleCache 1000 * 60 * 60 * 1000 # 1000 hours. @@ -75,13 +75,16 @@ CompletionSearch =          # Verify that the previous query is a prefix of the current query.          return false unless 0 == query.indexOf @mostRecentQuery.toLowerCase()          # Verify that every previous suggestion contains the text of the new query. -        for suggestion in (@mostRecentSuggestions.map (s) -> s.toLowerCase()) +        # Note: @mostRecentSuggestions may also be empty, in which case we drop though. The effect is that +        # previous queries with no suggestions suppress subsequent no-hope HTTP requests as the user continues +        # to type. +        for suggestion in @mostRecentSuggestions            return false unless 0 <= suggestion.indexOf query          # Ok. Re-use the suggestion.          true        if reusePreviousSuggestions -        console.log "reuse previous query:", @mostRecentQuery if @debug +        console.log "reuse previous query:", @mostRecentQuery, @mostRecentSuggestions.length if @debug          return callback @completionCache.set completionCacheKey, @mostRecentSuggestions      # That's all of the caches we can try.  Bail if the caller is only requesting synchronous results.  We @@ -104,8 +107,11 @@ CompletionSearch =              # incorrect or out-of-date completion engines.              try                suggestions = engine.parse xhr +              # Make all suggestions lower case.  It looks odd when suggestions from one completion engine are +              # upper case, and those from another are lower case. +              suggestions = (suggestion.toLowerCase() for suggestion in suggestions)                # Filter out the query itself. It's not adding anything. -              suggestions = (suggestion for suggestion in suggestions when suggestion.toLowerCase() != query) +              suggestions = (suggestion for suggestion in suggestions when suggestion != query)                console.log "GET", url if @debug              catch                suggestions = [] | 
