aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Crosby2012-06-03 16:02:28 -0700
committerPhil Crosby2012-06-03 16:56:39 -0700
commit98e9e8ca04918de3fae369e1f3b0285ba17d4188 (patch)
tree76c9f0669990f368c3c660f995a853a74dc229a5
parent39885cd326737534e2afc976f2a8ce086c76fc66 (diff)
downloadvimium-98e9e8ca04918de3fae369e1f3b0285ba17d4188.tar.bz2
Add a domain completer
-rw-r--r--background_page.html8
-rw-r--r--background_scripts/completion.coffee50
-rw-r--r--tests/completion_test.coffee40
3 files changed, 89 insertions, 9 deletions
diff --git a/background_page.html b/background_page.html
index 709a6633..8e1d3a6b 100644
--- a/background_page.html
+++ b/background_page.html
@@ -54,11 +54,15 @@
var completionSources = {
bookmarks: new BookmarkCompleter(),
- history: new HistoryCompleter()
+ history: new HistoryCompleter(),
+ domains: new DomainCompleter()
};
var completers = {
- omni: new MultiCompleter([completionSources.bookmarks, completionSources.history])
+ omni: new MultiCompleter([
+ completionSources.bookmarks,
+ completionSources.history,
+ completionSources.domains])
};
chrome.extension.onConnect.addListener(function(port, name) {
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee
index c9b4de78..ca0c6c45 100644
--- a/background_scripts/completion.coffee
+++ b/background_scripts/completion.coffee
@@ -110,11 +110,57 @@ class HistoryCompleter
refresh: ->
+# The domain completer is designed to match a single-word query which looks like it is a domain. This supports
+# the user experience where they quickly type a partial domain, hit tab -> enter, and expect to arrive there.
+class DomainCompleter
+ domains: null # A map of domain -> history
+
+ filter: (queryTerms, onComplete) ->
+ return onComplete([]) if queryTerms.length > 1
+ if @domains
+ @performSearch(queryTerms, onComplete)
+ else
+ @populateDomains => @performSearch(queryTerms, onComplete)
+
+ performSearch: (queryTerms, onComplete) ->
+ query = queryTerms[0]
+ domainCandidates = (domain for domain of @domains when domain.indexOf(query) >= 0)
+ domains = @sortDomainsByRelevancy(queryTerms, domainCandidates)
+ return onComplete([]) if domains.length == 0
+ topDomain = domains[0][0]
+ onComplete([new Suggestion(queryTerms, "domain", topDomain, null, @computeRelevancy)])
+
+ # Returns a list of domains of the form: [ [domain, relevancy], ... ]
+ sortDomainsByRelevancy: (queryTerms, domainCandidates) ->
+ results = []
+ for domain in domainCandidates
+ recencyScore = RankingUtils.recencyScore(@domains[domain].lastVisitTime || 0)
+ wordRelevancy = RankingUtils.wordRelevancy(queryTerms, domain, null)
+ score = wordRelevancy + Math.max(recencyScore, wordRelevancy) / 2
+ results.push([domain, score])
+ results.sort (a, b) -> b[1] - a[1]
+ results
+
+ populateDomains: (onComplete) ->
+ HistoryCache.use (history) =>
+ @domains = {}
+ history.forEach (entry) =>
+ # We want each key in our domains hash to point to the most recent History entry for that domain.
+ # Thankfully, the domains in HistoryCache are sorted from oldest to most recent.
+ domain = @parseDomain(entry.url)
+ @domains[domain] = entry if domain
+ onComplete()
+
+ parseDomain: (url) -> url.split("/")[2] || ""
+
+ # Suggestions from the Domain completer have the maximum relevancy. They should be shown first in the list.
+ computeRelevancy: -> 1
+
class MultiCompleter
constructor: (@completers) ->
@maxResults = 10 # TODO(philc): Should this be configurable?
- refresh: -> completer.refresh() for completer in @completers
+ refresh: -> completer.refresh() for completer in @completers when completer.refresh
filter: (queryTerms, onComplete) ->
suggestions = []
@@ -208,3 +254,5 @@ root.Suggestion = Suggestion
root.BookmarkCompleter = BookmarkCompleter
root.MultiCompleter = MultiCompleter
root.HistoryCompleter = HistoryCompleter
+root.DomainCompleter = DomainCompleter
+root.HistoryCache = HistoryCache
diff --git a/tests/completion_test.coffee b/tests/completion_test.coffee
index 813d3af3..dadf5860 100644
--- a/tests/completion_test.coffee
+++ b/tests/completion_test.coffee
@@ -35,15 +35,37 @@ context "history completer",
@completer = new HistoryCompleter()
should "return matching history entries when searching", ->
- @completer.filter(["story1"], (@results) =>)
- assert.arrayEqual [@history1.url], @results.map (entry) -> entry.url
+ assert.arrayEqual [@history1.url], filterCompleter(@completer, ["story1"]).map (entry) -> entry.url
should "rank recent results higher than nonrecent results", ->
stub(Date, "now", returns(hours(24)))
- @completer.filter(["hist"], (@results) =>)
- @results.forEach (result) -> result.computeRelevancy()
- @results.sort (a, b) -> b.relevancy - a.relevancy
- assert.arrayEqual [@history2.url, @history1.url], @results.map (result) -> result.url
+ results = filterCompleter(@completer, ["hist"])
+ results.forEach (result) -> result.computeRelevancy()
+ results.sort (a, b) -> b.relevancy - a.relevancy
+ assert.arrayEqual [@history2.url, @history1.url], results.map (result) -> result.url
+
+context "domain completer",
+ setup ->
+ @history1 = { title: "history1", url: "http://history1.com", lastVisitTime: hours(1) }
+ @history2 = { title: "history2", url: "http://history2.com", lastVisitTime: hours(1) }
+
+ stub(HistoryCache, "use", (onComplete) => onComplete([@history1, @history2]))
+ stub(Date, "now", returns(hours(24)))
+
+ @completer = new DomainCompleter()
+
+ should "return only a single matching domain", ->
+ results = filterCompleter(@completer, ["story"])
+ assert.arrayEqual ["history1.com"], results.map (result) -> result.url
+
+ should "pick domains which are more recent", ->
+ # This domains are the same except for their last visited time.
+ assert.equal "history1.com", filterCompleter(@completer, ["story"])[0].url
+ @history2.lastVisitTime = hours(3)
+ assert.equal "history2.com", filterCompleter(@completer, ["story"])[0].url
+
+ should "returns no results when there's more than one query term, because clearly it's not a domain", ->
+ assert.arrayEqual [], filterCompleter(@completer, ["his", "tory"])
context "suggestions",
should "escape html in page titles", ->
@@ -58,6 +80,12 @@ context "suggestions",
suggestion = new Suggestion(["queryterm"], "tab", "http://ninjawords.com", "ninjawords", returns(1))
assert.equal -1, suggestion.generateHtml().indexOf("http://ninjawords.com")
+# A convenience wrapper around completer.filter() so it can be called synchronously in tests.
+filterCompleter = (completer, queryTerms) ->
+ results = []
+ completer.filter(queryTerms, (completionResults) -> results = completionResults)
+ results
+
hours = (n) -> 1000 * 60 * 60 * n
Tests.run() \ No newline at end of file