aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2015-05-08 16:47:24 +0100
committerStephen Blott2015-05-08 16:47:24 +0100
commit82d25b5df76c8526d4ccb5352c0905cc28371199 (patch)
treeb6f695701b4f0801e2022a3699454d5e60e73711
parent5e6fa4ccfc103750b84df02a35f42a6acef78fa1 (diff)
downloadvimium-82d25b5df76c8526d4ccb5352c0905cc28371199.tar.bz2
Search completion; search keyword on SPACE.
-rw-r--r--background_scripts/completion.coffee22
-rw-r--r--lib/utils.coffee1
-rw-r--r--pages/vomnibar.coffee64
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