diff options
| author | Stephen Blott | 2015-05-29 06:43:19 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-29 06:43:19 +0100 |
| commit | 42fa6e9506d95073e0ca6924b1b9a082d23bd483 (patch) | |
| tree | eb9e526f6f57ab4e49ff9e0579d26a9c648df521 | |
| parent | 67e012f2c9de903deeaa5ade730161665a0bf430 (diff) | |
| parent | 45157bc460494503ca2b90caa762e72d224a1ef3 (diff) | |
| download | vimium-42fa6e9506d95073e0ca6924b1b9a082d23bd483.tar.bz2 | |
Merge branch 'completion-on-custom-search-only' into completion-on-custom-search-only-merge
Conflicts:
background_scripts/completion.coffee
| -rw-r--r-- | background_scripts/completion.coffee | 166 | ||||
| -rw-r--r-- | background_scripts/settings.coffee | 1 | ||||
| -rw-r--r-- | pages/options.html | 3 | ||||
| -rw-r--r-- | pages/vomnibar.coffee | 14 | ||||
| -rw-r--r-- | pages/vomnibar.css | 1 | ||||
| -rw-r--r-- | tests/unit_tests/completion_test.coffee | 8 |
6 files changed, 91 insertions, 102 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index 02c5478a..bae73b8d 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -43,23 +43,33 @@ class Suggestion # or @relevancyFunction. @relevancy ?= @relevancyFunction this - generateHtml: -> + generateHtml: (request) -> return @html if @html relevancyHtml = if @showRelevancy then "<span class='relevancy'>#{@computeRelevancy()}</span>" else "" insertTextClass = if @insertText then "vomnibarInsertText" else "vomnibarNoInsertText" insertTextIndicator = "↪" # A right hooked arrow. + @title = @insertText if @insertText and request.isCustomSearch # NOTE(philc): We're using these vimium-specific class names so we don't collide with the page's CSS. @html = - """ - <div class="vimiumReset vomnibarTopHalf"> - <span class="vimiumReset vomnibarSource #{insertTextClass}">#{insertTextIndicator}</span><span class="vimiumReset vomnibarSource">#{@type}</span> - <span class="vimiumReset vomnibarTitle">#{@highlightQueryTerms Utils.escapeHtml @title}</span> - </div> - <div class="vimiumReset vomnibarBottomHalf"> - <span class="vimiumReset vomnibarSource vomnibarNoInsertText">#{insertTextIndicator}</span><span class="vimiumReset vomnibarUrl">#{@highlightUrlTerms Utils.escapeHtml @shortenUrl()}</span> - #{relevancyHtml} - </div> - """ + if request.isCustomSearch + """ + <div class="vimiumReset vomnibarTopHalf"> + <span class="vimiumReset vomnibarSource #{insertTextClass}">#{insertTextIndicator}</span><span class="vimiumReset vomnibarSource">#{@type}</span> + <span class="vimiumReset vomnibarTitle">#{@highlightQueryTerms Utils.escapeHtml @title}</span> + #{relevancyHtml} + </div> + """ + else + """ + <div class="vimiumReset vomnibarTopHalf"> + <span class="vimiumReset vomnibarSource #{insertTextClass}">#{insertTextIndicator}</span><span class="vimiumReset vomnibarSource">#{@type}</span> + <span class="vimiumReset vomnibarTitle">#{@highlightQueryTerms Utils.escapeHtml @title}</span> + </div> + <div class="vimiumReset vomnibarBottomHalf"> + <span class="vimiumReset vomnibarSource vomnibarNoInsertText">#{insertTextIndicator}</span><span class="vimiumReset vomnibarUrl">#{@highlightUrlTerms Utils.escapeHtml @shortenUrl()}</span> + #{relevancyHtml} + </div> + """ # Use neat trick to snatch a domain (http://stackoverflow.com/a/8498668). getUrlRoot: (url) -> @@ -293,7 +303,7 @@ class DomainCompleter queryTerms: queryTerms type: "domain" url: domains[0]?[0] ? "" # This is the URL or an empty string, but not null. - relevancy: 1 + relevancy: 2.0 ].filter (s) -> 0 < s.url.length # Returns a list of domains of the form: [ [domain, relevancy], ... ] @@ -414,7 +424,7 @@ class SearchEngineCompleter preprocessRequest: (request) -> @searchEngines.use (engines) => { queryTerms, query } = request - request.searchEngines = engines + extend request, searchEngines: engines, keywords: key for own key of engines keyword = queryTerms[0] # Note. For a keyword "w", we match "w search terms" and "w ", but not "w" on its own. if keyword and engines[keyword] and (1 < queryTerms.length or /\S\s/.test query) @@ -422,6 +432,7 @@ class SearchEngineCompleter queryTerms: queryTerms[1..] keyword: keyword engine: engines[keyword] + isCustomSearch: true refresh: (port) -> @previousSuggestions = {} @@ -454,92 +465,77 @@ class SearchEngineCompleter filter: (request, onComplete) -> { queryTerms, query, engine } = request + return onComplete [] unless engine - { custom, searchUrl, description } = - if engine - { keyword, searchUrl, description } = engine - extend request, { searchUrl, customSearchMode: true } - custom: true - searchUrl: searchUrl - description: description - else - custom: false - searchUrl: Settings.get "searchUrl" - description: "search" - - return onComplete [] unless custom or 0 < queryTerms.length + { keyword, searchUrl, description } = engine + extend request, searchUrl, customSearchMode: true - factor = Math.max 0.0, Math.min 1.0, Settings.get "omniSearchWeight" - haveCompletionEngine = (0.0 < factor or custom) and CompletionSearch.haveCompletionEngine searchUrl + factor = 0.5 + haveCompletionEngine = CompletionSearch.haveCompletionEngine searchUrl # This filter is applied to all of the suggestions from all of the completers, after they have been # aggregated by the MultiCompleter. filter = (suggestions) -> - if custom and haveCompletionEngine - # We only accept suggestions: - # - from this completer, or - # - 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. - (suggestion.url.startsWith(engine.searchUrlPrefix) and - # And the URL suffix (which must contain the query part) matches the current query. - RankingUtils.matches queryTerms, suggestion.url[engine.searchUrlPrefix.length..]) - - else if not custom - # Filter out any suggestion which is just what the user would get if they hit <Enter> anyway. For - # example, don't offer "https://www.google.com/search?q=vimium" if the query is "vimium". - defaultUrl = Utils.createSearchUrl queryTerms, searchUrl - defaultQuery = queryTerms.join " " - suggestions.filter (suggestion) -> Utils.extractQuery(searchUrl, suggestion.url) != defaultQuery - else - suggestions + suggestions.filter (suggestion) -> + # We only keep suggestions which either *were* generated by this search engine, or *could have + # been* generated by this search engine (and match the current query). + suggestion.isSearchSuggestion or suggestion.isCustomSearch or + ( + terms = Utils.extractQuery searchUrl, suggestion.url + terms and RankingUtils.matches queryTerms, terms + ) # If a previous suggestion still matches the query, then we keep it (even if the completion engine may not # return it for the current query). This allows the user to pick suggestions by typing fragments of their # text, without regard to whether the completion engine can complete the actual text of the query. previousSuggestions = - for url, suggestion of @previousSuggestions - continue unless RankingUtils.matches queryTerms, suggestion.title - # Reset various fields, they may not be correct wrt. the current query. - extend suggestion, relevancy: null, html: null, queryTerms: queryTerms - suggestion.relevancy = null - suggestion + if queryTerms.length == 0 + [] + else + for url, suggestion of @previousSuggestions + continue unless RankingUtils.matches queryTerms, suggestion.title + # Reset various fields, they may not be correct wrt. the current query. + extend suggestion, relevancy: null, html: null, queryTerms: queryTerms + suggestion.relevancy = null + suggestion primarySuggestion = new Suggestion queryTerms: queryTerms type: description url: Utils.createSearchUrl queryTerms, searchUrl title: queryTerms.join " " - relevancy: 1 - autoSelect: custom - highlightTerms: not haveCompletionEngine + relevancy: 2.0 + autoSelect: true + highlightTerms: false isSearchSuggestion: true + isPrimarySuggestion: true - mkSuggestion = (suggestion) => - url = Utils.createSearchUrl suggestion, searchUrl - @previousSuggestions[url] = new Suggestion - queryTerms: queryTerms - type: description - url: url - title: suggestion - insertText: suggestion - highlightTerms: false - highlightTermsExcludeUrl: true - isCustomSearch: custom - relevancyFunction: @computeRelevancy - relevancyData: factor + return onComplete [ primarySuggestion ], { filter } if queryTerms.length == 0 + + mkSuggestion = do => + count = 0 + (suggestion) => + url = Utils.createSearchUrl suggestion, searchUrl + @previousSuggestions[url] = new Suggestion + queryTerms: queryTerms + type: description + url: url + title: suggestion + insertText: suggestion + highlightTerms: false + highlightTermsExcludeUrl: true + isCustomSearch: true + relevancy: if ++count == 1 then 1.0 else null + relevancyFunction: @computeRelevancy cachedSuggestions = if haveCompletionEngine then CompletionSearch.complete searchUrl, queryTerms else null suggestions = previousSuggestions - suggestions.push primarySuggestion if custom - suggestions.push cachedSuggestions.map(mkSuggestion)... if custom and cachedSuggestions? + suggestions.push primarySuggestion if queryTerms.length == 0 or cachedSuggestions? or not haveCompletionEngine - # There is no prospect of adding further completions. + # There is no prospect of adding further completions, so we're done. suggestions.push cachedSuggestions.map(mkSuggestion)... if cachedSuggestions? onComplete suggestions, { filter, continuation: null } else @@ -547,20 +543,7 @@ class SearchEngineCompleter # continuation. onComplete suggestions, filter: filter - continuation: (suggestions, onComplete) => - - # We can skip querying the completion engine if any new suggestions we propose will not score highly - # enough to make the list anyway. We construct a suggestion which perfectly matches the query, and - # ask the relevancy function what score it would get. If that score is less than the score of the - # lowest-ranked suggestion from another completer (and there are already 10 suggestions), then - # there's no need to query the completion engine. - perfectRelevancyScore = @computeRelevancy new Suggestion - queryTerms: queryTerms, title: queryTerms.join(" "), relevancyData: factor - - if 10 <= suggestions.length and perfectRelevancyScore < suggestions[suggestions.length-1].relevancy - console.log "skip (cannot make the grade):", suggestions.length, query if SearchEngineCompleter.debug - return onComplete [] - + continuation: (onComplete) => CompletionSearch.complete searchUrl, queryTerms, (suggestions = []) => console.log "fetched suggestions:", suggestions.length, query if SearchEngineCompleter.debug onComplete suggestions.map mkSuggestion @@ -571,7 +554,7 @@ class SearchEngineCompleter # scores here, and those provided by other completers. # - Relevancy depends only on the title (which is the search terms), and not on the URL. Suggestion.boostRelevancyScore 0.5, - relevancyData * RankingUtils.wordRelevancy queryTerms, title, title + 0.7 * RankingUtils.wordRelevancy queryTerms, title, title postProcessSuggestions: (request, suggestions) -> return unless request.searchEngines @@ -586,9 +569,6 @@ class SearchEngineCompleter # suggestion, then custom search-engine mode should be activated. suggestion.customSearchMode = engine.keyword suggestion.title ||= suggestion.insertText - # NOTE(smblott) The following is disabled: experimentation with UI. - # suggestion.highlightTermsExcludeUrl = true - # suggestion.type = engine.description ? "custom search history" break # A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top @@ -643,7 +623,7 @@ class MultiCompleter if shouldRunContinuations jobs = new JobRunner continuations.map (continuation) -> (callback) -> - continuation suggestions, (newSuggestions) -> + continuation (newSuggestions) -> suggestions.push newSuggestions... callback() @@ -676,7 +656,7 @@ class MultiCompleter completer.postProcessSuggestions? request, suggestions for completer in @completers # Generate HTML for the remaining suggestions and return them. - suggestion.generateHtml() for suggestion in suggestions + suggestion.generateHtml request for suggestion in suggestions suggestions # Utilities which help us compute a relevancy score for a given item. diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee index 269b4a2c..d23649ee 100644 --- a/background_scripts/settings.coffee +++ b/background_scripts/settings.coffee @@ -43,7 +43,6 @@ root.Settings = Settings = # or strings defaults: scrollStepSize: 60 - omniSearchWeight: 0.4 smoothScroll: true keyMappings: "# Insert your preferred key mappings here." linkHintCharacters: "sadfjklewcmpgh" diff --git a/pages/options.html b/pages/options.html index 8535823d..0fa5b18d 100644 --- a/pages/options.html +++ b/pages/options.html @@ -238,6 +238,8 @@ b: http://b.com/?q=%s description </tr> <!-- Vimium Labs --> + <!-- + Disabled. But we leave this code here as a template for the next time we need to introduce "Vimium Labs". <tr> <td colspan="2"><header>Vimium Labs</header></td> </tr> @@ -263,6 +265,7 @@ b: http://b.com/?q=%s description <input id="omniSearchWeight" type="number" min="0.0" max="1.0" step="0.05" />(0 to 1) </td> </tr> + --> </tbody> </table> </div> diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee index 423ffa59..71f22900 100644 --- a/pages/vomnibar.coffee +++ b/pages/vomnibar.coffee @@ -76,7 +76,7 @@ class VomnibarUI updateSelection: -> # For custom search engines, we suppress the leading term (e.g. the "w" of "w query terms") within the # vomnibar input. - if @lastReponse.customSearchMode and not @customSearchMode? + if @lastReponse.isCustomSearch and not @customSearchMode? queryTerms = @input.value.trim().split /\s+/ @customSearchMode = queryTerms[0] @input.value = queryTerms[1..].join " " @@ -141,15 +141,23 @@ class VomnibarUI @selection = @completions.length - 1 if @selection < @initialSelectionValue @updateSelection() else if (action == "enter") - if @selection == -1 + isCustomSearchPrimarySuggestion = @completions[@selection]?.isPrimarySuggestion and @lastReponse.engine?.searchUrl? + if @selection == -1 or isCustomSearchPrimarySuggestion query = @input.value.trim() # <Enter> on an empty query is a no-op. return unless 0 < query.length + # First case (@selection == -1). # If the user types something and hits enter without selecting a completion from the list, then: # - If a search URL has been provided, then use it. This is custom search engine request. # - Otherwise, send the query to the background page, which will open it as a URL or create a # default search, as appropriate. - query = Utils.createSearchUrl query, @lastReponse.searchUrl if @lastReponse.searchUrl? + # + # Second case (isCustomSearchPrimarySuggestion). + # Alternatively, the selected completion could be the primary selection for a custom search engine. + # Because the the suggestions are updated asynchronously in omni mode, the user may have typed more + # text than that which is included in the URL associated with the primary suggestion. Therefore, to + # avoid a race condition, we construct the query from the actual contents of the input (query). + query = Utils.createSearchUrl query, @lastReponse.engine.searchUrl if isCustomSearchPrimarySuggestion @hide -> chrome.runtime.sendMessage handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab" diff --git a/pages/vomnibar.css b/pages/vomnibar.css index b1ed0252..1b19daad 100644 --- a/pages/vomnibar.css +++ b/pages/vomnibar.css @@ -126,7 +126,6 @@ #vomnibar li em { font-style: italic; } #vomnibar li em .vomnibarMatch, #vomnibar li .vomnibarTitle .vomnibarMatch { color: #333; - text-decoration: underline; } #vomnibar li.vomnibarSelected { diff --git a/tests/unit_tests/completion_test.coffee b/tests/unit_tests/completion_test.coffee index 88df0a43..4a0cf746 100644 --- a/tests/unit_tests/completion_test.coffee +++ b/tests/unit_tests/completion_test.coffee @@ -244,7 +244,7 @@ context "suggestions", url: "url" title: "title <span>" relevancyFunction: returns 1 - assert.isTrue suggestion.generateHtml().indexOf("title <span>") >= 0 + assert.isTrue suggestion.generateHtml({}).indexOf("title <span>") >= 0 should "highlight query words", -> suggestion = new Suggestion @@ -254,7 +254,7 @@ context "suggestions", title: "ninjawords" relevancyFunction: returns 1 expected = "<span class='vomnibarMatch'>ninj</span>a<span class='vomnibarMatch'>words</span>" - assert.isTrue suggestion.generateHtml().indexOf(expected) >= 0 + assert.isTrue suggestion.generateHtml({}).indexOf(expected) >= 0 should "highlight query words correctly when whey they overlap", -> suggestion = new Suggestion @@ -264,7 +264,7 @@ context "suggestions", title: "ninjawords" relevancyFunction: returns 1 expected = "<span class='vomnibarMatch'>ninjaword</span>s" - assert.isTrue suggestion.generateHtml().indexOf(expected) >= 0 + assert.isTrue suggestion.generateHtml({}).indexOf(expected) >= 0 should "shorten urls", -> suggestion = new Suggestion @@ -273,7 +273,7 @@ context "suggestions", url: "http://ninjawords.com" title: "ninjawords" relevancyFunction: returns 1 - assert.equal -1, suggestion.generateHtml().indexOf("http://ninjawords.com") + assert.equal -1, suggestion.generateHtml({}).indexOf("http://ninjawords.com") context "RankingUtils.wordRelevancy", should "score higher in shorter URLs", -> |
