diff options
| author | Stephen Blott | 2015-05-08 16:47:24 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-08 16:47:24 +0100 |
| commit | 82d25b5df76c8526d4ccb5352c0905cc28371199 (patch) | |
| tree | b6f695701b4f0801e2022a3699454d5e60e73711 | |
| parent | 5e6fa4ccfc103750b84df02a35f42a6acef78fa1 (diff) | |
| download | vimium-82d25b5df76c8526d4ccb5352c0905cc28371199.tar.bz2 | |
Search completion; search keyword on SPACE.
| -rw-r--r-- | background_scripts/completion.coffee | 22 | ||||
| -rw-r--r-- | lib/utils.coffee | 1 | ||||
| -rw-r--r-- | pages/vomnibar.coffee | 64 |
3 files changed, 53 insertions, 34 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index 9d249198..ba19970f 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -131,7 +131,8 @@ class BookmarkCompleter # These bookmarks are loaded asynchronously when refresh() is called. bookmarks: null - filter: (@queryTerms, @onComplete) -> + filter: (queryTerms, @onComplete) -> + @queryTerms = queryTerms.filter (t) -> 0 < t.length @currentSearch = { queryTerms: @queryTerms, onComplete: @onComplete } @performSearch() if @bookmarks @@ -193,6 +194,7 @@ class BookmarkCompleter class HistoryCompleter filter: (queryTerms, onComplete) -> + queryTerms = queryTerms.filter (t) -> 0 < t.length @currentSearch = { queryTerms: @queryTerms, onComplete: @onComplete } results = [] HistoryCache.use (history) => @@ -227,6 +229,7 @@ class DomainCompleter domains: null filter: (queryTerms, onComplete) -> + queryTerms = queryTerms.filter (t) -> 0 < t.length return onComplete([]) unless queryTerms.length == 1 if @domains @performSearch(queryTerms, onComplete) @@ -329,6 +332,7 @@ tabRecency = new TabRecency() # Searches through all open tabs, matching on title and URL. class TabCompleter filter: (queryTerms, onComplete) -> + queryTerms = queryTerms.filter (t) -> 0 < t.length # NOTE(philc): We search all tabs, not just those in the current window. I'm not sure if this is the # correct UX. chrome.tabs.query {}, (tabs) => @@ -366,9 +370,7 @@ class SearchEngineCompleter queryTerms = queryTerms[1..] if custom query = queryTerms.join " " - - if queryTerms.length == 0 - return onComplete [] + return onComplete [] if queryTerms.length == 0 # For custom search engines, we add an auto-selected suggestion. if custom @@ -385,6 +387,10 @@ class SearchEngineCompleter # Suppress the "w" from "w query terms" in the vomnibar input. suppressLeadingQueryTerm: true + # We filter out the empty strings late so that we can distinguish between, for example, "w" and "w ". + queryTerms = queryTerms.filter (t) -> 0 < t.length + return onComplete suggestions if queryTerms.length == 0 + onComplete suggestions, exclusive: if custom and CompletionEngines.haveCompletionEngine searchUrl then description else null continuation: (existingSuggestions, onComplete) => @@ -404,6 +410,7 @@ class SearchEngineCompleter characterCount = query.length - queryTerms.length + 1 relavancy = 0.6 * (Math.min(characterCount, 10.0)/10.0) + queryTerms = queryTerms.filter (t) -> 0 < t.length if 0 < existingSuggestions.length existingSuggestionsMinScore = existingSuggestions[existingSuggestions.length-1].relevancy if relavancy < existingSuggestionsMinScore and MultiCompleter.maxResults <= existingSuggestions.length @@ -477,10 +484,11 @@ class MultiCompleter # 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. + # continuation if another query is already waiting. This is for slow tasks which should be done + # asynchronously (e.g. HTTP GET). continuation: null - # If truthy, completions from other completers should be discarded. The truthy value should be the type - # of the completer (e.g. "custom search"). + # If truthy, completions from other completers should be suppressed. The truthy value should be the + # type of the completer (e.g. "custom search"). All other completion types are suppressed. exclusive: false (queryTerms, onComplete) -> diff --git a/lib/utils.coffee b/lib/utils.coffee index 1c24a40f..033fdd2b 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -201,6 +201,7 @@ Function::curry = -> Array.copy = (array) -> Array.prototype.slice.call(array, 0) String::startsWith = (str) -> @indexOf(str) == 0 +String::ltrim = () -> @replace /^\s+/, "" globalRoot = window ? global globalRoot.extend = (hash1, hash2) -> diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee index 1188c411..b228a59b 100644 --- a/pages/vomnibar.coffee +++ b/pages/vomnibar.coffee @@ -61,11 +61,10 @@ class VomnibarUI @postHideCallback = null reset: -> + @clearUpdateTimer() @completionList.style.display = "" @input.value = "" @completions = [] - window.clearTimeout @updateTimer if @updateTimer? - @updateTimer = null @previousAutoSelect = null @previousInputValue = null @suppressedLeadingQueryTerm = null @@ -84,10 +83,7 @@ class VomnibarUI # For custom search engines, we suppress the leading term (e.g. the "w" of "w query terms") within the # vomnibar input. if @suppressedLeadingQueryTerm? - # If we have a suppressed term and the input is empty, then reinstate it. - if @input.value.trim().split(/\s+/).join("").length == 0 - @input.value = @getInputValue() - @suppressedLeadingQueryTerm = null + @restoreSuppressedQueryTerm() else if @completions[0]?.suppressLeadingQueryTerm # We've been asked to suppress the leading query term, and it's not already suppressed. So suppress it. queryTerms = @input.value.trim().split /\s+/ @@ -107,8 +103,15 @@ class VomnibarUI for i in [0...@completionList.children.length] @completionList.children[i].className = (if i == @selection then "vomnibarSelected" else "") + restoreSuppressedQueryTerm: -> + if @suppressedLeadingQueryTerm? + # If we have a suppressed term and the input is empty, then reinstate it. + if @input.value.length == 0 + @input.value = @suppressedLeadingQueryTerm + @suppressedLeadingQueryTerm = null + # - # Returns the user's action ("up", "down", "enter", "dismiss" or null) based on their keypress. + # Returns the user's action ("up", "down", "enter", "dismiss", "delete" or null) based on their keypress. # We support the arrow keys and other shortcuts for moving, so this method hides that complexity. # actionFromKeyEvent: (event) -> @@ -125,6 +128,9 @@ class VomnibarUI return "down" else if (event.keyCode == keyCodes.enter) return "enter" + else if event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey + return "delete" + null onKeydown: (event) => action = @actionFromKeyEvent(event) @@ -157,6 +163,13 @@ class VomnibarUI else completion = @completions[@selection] @hide -> completion.performAction openInNewTab + else if action == "delete" + if @input.value.length == 0 + @restoreSuppressedQueryTerm() + @updateCompletions() + else + # Don't suppress the Delete. We want it to happen. + return true # It seems like we have to manually suppress the event here and still return true. event.stopImmediatePropagation() @@ -167,6 +180,7 @@ class VomnibarUI (if @suppressedLeadingQueryTerm? then @suppressedLeadingQueryTerm + " " else "") + @input.value updateCompletions: (callback = null) -> + @clearUpdateTimer() @completer.filter @getInputValue(), (@completions) => @populateUiWithCompletions @completions callback?() @@ -188,19 +202,18 @@ class VomnibarUI @selection = -1 @update false + clearUpdateTimer: -> + if @updateTimer? + window.clearTimeout @updateTimer + @updateTimer = null + update: (updateSynchronously = false, callback = null) => if updateSynchronously - # Cancel any scheduled update. - if @updateTimer? - window.clearTimeout @updateTimer - @updateTimer = null @updateCompletions callback else if not @updateTimer? # Update asynchronously for better user experience and to take some load off the CPU (not every # keystroke will cause a dedicated update) - @updateTimer = Utils.setTimeout @refreshInterval, => - @updateTimer = null - @updateCompletions callback + @updateTimer = Utils.setTimeout @refreshInterval, => @updateCompletions callback @input.focus() @@ -257,23 +270,21 @@ class BackgroundCompleter @mostRecentCallback msg.results filter: (query, @mostRecentCallback) -> - queryTerms = query.trim().split(/\s+/).filter (term) -> 0 < term.length + # We retain trailing whitespace so that we can tell the difference between "w" and "w " (for custom search + # engines). + queryTerms = query.ltrim().split(/\s+/) query = queryTerms.join " " if @cache.has query console.log "cache hit:", query if @debug @mostRecentCallback @cache.get query else - # Silently drop identical consecutive queries. This can happen, for example, if the user adds - # whitespace to the query. - unless @mostRecentQuery? and query == @mostRecentQuery - @mostRecentQuery = query - @messageId = Utils.createUniqueId() - @port.postMessage - name: @name - handler: "filter" - id: @messageId - query: query - queryTerms: queryTerms + @messageId = Utils.createUniqueId() + @port.postMessage + name: @name + handler: "filter" + id: @messageId + query: query + queryTerms: queryTerms refresh: -> @reset() @@ -283,7 +294,6 @@ class BackgroundCompleter reset: -> # We only cache results for the duration of a single vomnibar activation, so clear the cache now. @cache.clear() - @mostRecentQuery = null cancel: -> # Inform the background completer that it may (should it choose to do so) abandon any pending query |
