From 01a6df1f155b8b068925cc813d663457edc860e8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 31 May 2015 14:04:28 +0100 Subject: Re-write class with inheritence. --- background_scripts/completion_engines.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 9a88d491..48f94965 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -43,15 +43,15 @@ class Google extends GoogleXMLRegexpEngine # A wrapper class for Google completions. This adds prefix terms to the query, and strips those terms from # the resulting suggestions. For example, for Google Maps, we add "map of" as a prefix, then strip "map of" # from the resulting suggestions. -class GoogleWithPrefix +class GoogleWithPrefix extends Google constructor: (prefix, args...) -> - @engine = new Google args... - @prefix = "#{prefix.trim()} " - @queryTerms = @prefix.split /\s+/ - match: (args...) -> @engine.match args... - getUrl: (queryTerms) -> @engine.getUrl [ @queryTerms..., queryTerms... ] + super args... + prefix = prefix.trim() + @prefix = "#{prefix} " + @queryTerms = prefix.split /\s+/ + getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] parse: (xhr) -> - @engine.parse(xhr) + super(xhr) .filter (suggestion) => suggestion.startsWith @prefix .map (suggestion) => suggestion[@prefix.length..].ltrim() -- cgit v1.2.3 From 9e5d030e18f6c07a3a1beec77c7fd8e77a1a5164 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 3 Jun 2015 06:31:26 +0100 Subject: Completion page; make examples data (not comments). For each completion engine, this adds an @example properties (not comments) --- background_scripts/completion_engines.coffee | 30 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 48f94965..987708c2 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -32,9 +32,9 @@ class GoogleXMLRegexpEngine extends RegexpEngine suggestion class Google extends GoogleXMLRegexpEngine - # Example search URL: http://www.google.com/search?q=%s constructor: (regexps = null) -> super regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" + @example = "http://www.google.com/search?q=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @@ -58,22 +58,23 @@ class GoogleWithPrefix extends Google # For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, # then strip "map of" from the resulting suggestions. class GoogleMaps extends GoogleWithPrefix - # Example search URL: https://www.google.com/maps?q=%s - constructor: -> super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" + constructor: -> + super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" + @example = "https://www.google.com/maps?q=%s" class Youtube extends GoogleXMLRegexpEngine - # Example search URL: http://www.youtube.com/results?search_query=%s constructor: -> super "^https?://[a-z]+\\.youtube\\.com/results" + @example = "http://www.youtube.com/results?search_query=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s" class Wikipedia extends RegexpEngine - # Example search URL: http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s constructor: -> super "^https?://[a-z]+\\.wikipedia\\.org/" + @example = "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @@ -84,28 +85,33 @@ class Wikipedia extends RegexpEngine class Bing extends RegexpEngine # Example search URL: https://www.bing.com/search?q=%s - constructor: -> super "^https?://www\\.bing\\.com/search" + constructor: -> + super "^https?://www\\.bing\\.com/search" + @example = "https://www.bing.com/search?q=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://api.bing.com/osjson.aspx?query=%s" parse: (xhr) -> JSON.parse(xhr.responseText)[1] class Amazon extends RegexpEngine - # Example search URL: http://www.amazon.com/s/?field-keywords=%s - constructor: -> super "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" + constructor: -> + super "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" + @example = "http://www.amazon.com/s/?field-keywords=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" parse: (xhr) -> JSON.parse(xhr.responseText)[1] class DuckDuckGo extends RegexpEngine - # Example search URL: https://duckduckgo.com/?q=%s - constructor: -> super "^https?://([a-z]+\\.)?duckduckgo\\.com/" + constructor: -> + super "^https?://([a-z]+\\.)?duckduckgo\\.com/" + @example = "https://duckduckgo.com/?q=%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://duckduckgo.com/ac/?q=%s" parse: (xhr) -> suggestion.phrase for suggestion in JSON.parse xhr.responseText class Webster extends RegexpEngine - # Example search URL: http://www.merriam-webster.com/dictionary/%s - constructor: -> super "^https?://www.merriam-webster.com/dictionary/" + constructor: -> + super "^https?://www.merriam-webster.com/dictionary/" + @example = "http://www.merriam-webster.com/dictionary/%s" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://www.merriam-webster.com/autocomplete?query=%s" parse: (xhr) -> JSON.parse(xhr.responseText).suggestions -- cgit v1.2.3 From 3dfa2776fac56536f85e8ed196db6995468f368c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 3 Jun 2015 06:54:37 +0100 Subject: Completion page; add example DECLARATIONS to page. Instead of just giving an example search URL, we can give the actual declaration (which the user can copy, paste and tweak). --- background_scripts/completion_engines.coffee | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 987708c2..18aa00bb 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -34,7 +34,8 @@ class GoogleXMLRegexpEngine extends RegexpEngine class Google extends GoogleXMLRegexpEngine constructor: (regexps = null) -> super regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" - @example = "http://www.google.com/search?q=%s" + @exampleSearchUrl = "http://www.google.com/search?q=%s" + @exampleKeyword = "m" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @@ -60,12 +61,15 @@ class GoogleWithPrefix extends Google class GoogleMaps extends GoogleWithPrefix constructor: -> super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" - @example = "https://www.google.com/maps?q=%s" + @exampleSearchUrl = "https://www.google.com/maps?q=%s" + @exampleKeyword = "m" + @exampleDescription = "Google maps" class Youtube extends GoogleXMLRegexpEngine constructor: -> super "^https?://[a-z]+\\.youtube\\.com/results" - @example = "http://www.youtube.com/results?search_query=%s" + @exampleSearchUrl = "http://www.youtube.com/results?search_query=%s" + @exampleKeyword = "y" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @@ -74,7 +78,8 @@ class Youtube extends GoogleXMLRegexpEngine class Wikipedia extends RegexpEngine constructor: -> super "^https?://[a-z]+\\.wikipedia\\.org/" - @example = "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" + @exampleSearchUrl = "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" + @exampleKeyword = "y" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @@ -84,17 +89,18 @@ class Wikipedia extends RegexpEngine JSON.parse(xhr.responseText)[1] class Bing extends RegexpEngine - # Example search URL: https://www.bing.com/search?q=%s constructor: -> super "^https?://www\\.bing\\.com/search" - @example = "https://www.bing.com/search?q=%s" + @exampleSearchUrl = "https://www.bing.com/search?q=%s" + @exampleKeyword = "b" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://api.bing.com/osjson.aspx?query=%s" parse: (xhr) -> JSON.parse(xhr.responseText)[1] class Amazon extends RegexpEngine constructor: -> super "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" - @example = "http://www.amazon.com/s/?field-keywords=%s" + @exampleSearchUrl = "http://www.amazon.com/s/?field-keywords=%s" + @exampleKeyword = "a" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" @@ -103,7 +109,8 @@ class Amazon extends RegexpEngine class DuckDuckGo extends RegexpEngine constructor: -> super "^https?://([a-z]+\\.)?duckduckgo\\.com/" - @example = "https://duckduckgo.com/?q=%s" + @exampleSearchUrl = "https://duckduckgo.com/?q=%s" + @exampleKeyword = "d" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://duckduckgo.com/ac/?q=%s" parse: (xhr) -> suggestion.phrase for suggestion in JSON.parse xhr.responseText @@ -111,7 +118,9 @@ class DuckDuckGo extends RegexpEngine class Webster extends RegexpEngine constructor: -> super "^https?://www.merriam-webster.com/dictionary/" - @example = "http://www.merriam-webster.com/dictionary/%s" + @exampleSearchUrl = "http://www.merriam-webster.com/dictionary/%s" + @exampleKeyword = "dw" + @exampleDescription = "Dictionary" getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://www.merriam-webster.com/autocomplete?query=%s" parse: (xhr) -> JSON.parse(xhr.responseText).suggestions -- cgit v1.2.3 From f5db9ea1dc8d23dff732dd3345bf85798d64f1e9 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 6 Jun 2015 06:18:35 +0100 Subject: Re-work completions: initial refactor. The original completion-engine interface was based on three functions. With some experience, it seems there is a pattern involving explicit regular expressions which is used by all actual engine implementations. This is a refactoring to make those regular expressions explicit (and required), and is a first step towards adding additional fucntionality. This also simplifies the completion cache key (use JSON instead of some weird hash). --- background_scripts/completion_engines.coffee | 210 +++++++++++++++------------ background_scripts/completion_search.coffee | 7 +- 2 files changed, 115 insertions(+), 102 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 18aa00bb..afbb2040 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -1,142 +1,160 @@ -# A completion engine provides search suggestions for a search engine. A search engine is identified by a -# "searchUrl", e.g. Settings.get("searchUrl"), or a custom search engine URL. +# A completion engine provides search suggestions for a custom search engine. A custom search engine is +# identified by a "searchUrl". An "engineUrl" is used for fetching suggestions, whereas a "searchUrl" is used +# for the actual search itself. # -# Each completion engine defines three functions: +# Each completion engine defines: # -# 1. "match" - This takes a searchUrl and returns a boolean indicating whether this completion engine can -# perform completion for the given search engine. +# 1. An "engineUrl". This is the URL to use for search completions and is passed as the option "engineUrl" +# to the "BaseEngine" constructor. # -# 2. "getUrl" - This takes a list of query terms (queryTerms) and generates a completion URL, that is, a URL -# which will provide completions for this completion engine. +# 2. One or more regular expressions which define the custom search engine URLs for which the completion +# engine will be used. This is passed as the "regexps" option to the "BaseEngine" constructor. # -# 3. "parse" - This takes a successful XMLHttpRequest object (the request has completed successfully), and -# returns a list of suggestions (a list of strings). This method is always executed within the context -# of a try/catch block, so errors do not propagate. +# 3. A "parse" function. This takes a successful XMLHttpRequest object (the request has completed +# successfully), and returns a list of suggestions (a list of strings). This method is always executed +# within the context of a try/catch block, so errors do not propagate. +# +# 4. For documentation only, each completion engine *must* and example custom search engine. The example +# must include an example "keyword" and and example "searchUrl", and may include and example +# "description". # # Each new completion engine must be added to the list "CompletionEngines" at the bottom of this file. # # The lookup logic which uses these completion engines is in "./completion_search.coffee". # -# A base class for common regexp-based matching engines. -class RegexpEngine - constructor: (args...) -> @regexps = args.map (regexp) -> new RegExp regexp +# A base class for common regexp-based matching engines. "options" must define: +# options.engineUrl: the URL to use for the completion engine. This must be a string. +# options.regexps: one or regular expressions. This may either a single string or a list of strings. +# options.example: an example object containing at least "keyword" and "searchUrl", and optional "description". +class BaseEngine + constructor: (options) -> + extend this, options + @regexps = [ @regexps ] if "string" == typeof @regexps + @regexps = @regexps.map (regexp) -> new RegExp regexp + match: (searchUrl) -> Utils.matchesAnyRegexp @regexps, searchUrl + getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @engineUrl -# Several Google completion engines package XML responses in this way. -class GoogleXMLRegexpEngine extends RegexpEngine +# Several Google completion engines package responses as XML. This parses such XML. +class GoogleXMLBaseEngine extends BaseEngine parse: (xhr) -> for suggestion in xhr.responseXML.getElementsByTagName "suggestion" continue unless suggestion = suggestion.getAttribute "data" suggestion -class Google extends GoogleXMLRegexpEngine +class Google extends GoogleXMLBaseEngine constructor: (regexps = null) -> - super regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" - @exampleSearchUrl = "http://www.google.com/search?q=%s" - @exampleKeyword = "m" - - getUrl: (queryTerms) -> - Utils.createSearchUrl queryTerms, - "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s" - -# A wrapper class for Google completions. This adds prefix terms to the query, and strips those terms from -# the resulting suggestions. For example, for Google Maps, we add "map of" as a prefix, then strip "map of" -# from the resulting suggestions. -class GoogleWithPrefix extends Google - constructor: (prefix, args...) -> - super args... - prefix = prefix.trim() - @prefix = "#{prefix} " - @queryTerms = prefix.split /\s+/ - getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] - parse: (xhr) -> - super(xhr) - .filter (suggestion) => suggestion.startsWith @prefix - .map (suggestion) => suggestion[@prefix.length..].ltrim() - -# For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, -# then strip "map of" from the resulting suggestions. -class GoogleMaps extends GoogleWithPrefix + super + engineUrl: "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s" + regexps: regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" + example: + searchUrl: "http://www.google.com/search?q=%s" + keyword: "g" + +## # A wrapper class for Google completions. This adds prefix terms to the query, and strips those terms from +## # the resulting suggestions. For example, for Google Maps, we add "map of" as a prefix, then strip "map of" +## # from the resulting suggestions. +## class GoogleWithPrefix extends Google +## constructor: (prefix, args...) -> +## super args... +## prefix = prefix.trim() +## @prefix = "#{prefix} " +## @queryTerms = prefix.split /\s+/ +## getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] +## parse: (xhr) -> +## super(xhr) +## .filter (suggestion) => suggestion.startsWith @prefix +## .map (suggestion) => suggestion[@prefix.length..].ltrim() +## +## # For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, +## # then strip "map of" from the resulting suggestions. +## class GoogleMaps extends GoogleWithPrefix +## constructor: -> +## super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" +## @exampleSearchUrl = "https://www.google.com/maps?q=%s" +## @exampleKeyword = "m" +## @exampleDescription = "Google maps" + +class Youtube extends GoogleXMLBaseEngine constructor: -> - super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" - @exampleSearchUrl = "https://www.google.com/maps?q=%s" - @exampleKeyword = "m" - @exampleDescription = "Google maps" - -class Youtube extends GoogleXMLRegexpEngine + super + engineUrl: "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s" + regexps: "^https?://[a-z]+\\.youtube\\.com/results" + example: + searchUrl: "http://www.youtube.com/results?search_query=%s" + keyword: "y" + +class Wikipedia extends BaseEngine constructor: -> - super "^https?://[a-z]+\\.youtube\\.com/results" - @exampleSearchUrl = "http://www.youtube.com/results?search_query=%s" - @exampleKeyword = "y" + super + engineUrl: "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s" + regexps: "^https?://[a-z]+\\.wikipedia\\.org/" + example: + searchUrl: "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" + keyword: "w" - getUrl: (queryTerms) -> - Utils.createSearchUrl queryTerms, - "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s" + parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class Wikipedia extends RegexpEngine +class Bing extends BaseEngine constructor: -> - super "^https?://[a-z]+\\.wikipedia\\.org/" - @exampleSearchUrl = "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" - @exampleKeyword = "y" - - getUrl: (queryTerms) -> - Utils.createSearchUrl queryTerms, - "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s" + super + engineUrl: "http://api.bing.com/osjson.aspx?query=%s" + regexps: "^https?://www\\.bing\\.com/search" + example: + searchUrl: "https://www.bing.com/search?q=%s" + keyword: "b" - parse: (xhr) -> - JSON.parse(xhr.responseText)[1] - -class Bing extends RegexpEngine - constructor: -> - super "^https?://www\\.bing\\.com/search" - @exampleSearchUrl = "https://www.bing.com/search?q=%s" - @exampleKeyword = "b" - getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://api.bing.com/osjson.aspx?query=%s" parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class Amazon extends RegexpEngine +class Amazon extends BaseEngine constructor: -> - super "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" - @exampleSearchUrl = "http://www.amazon.com/s/?field-keywords=%s" - @exampleKeyword = "a" - getUrl: (queryTerms) -> - Utils.createSearchUrl queryTerms, - "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" + super + engineUrl: "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" + regexps: "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" + example: + searchUrl: "http://www.amazon.com/s/?field-keywords=%s" + keyword: "a" + parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class DuckDuckGo extends RegexpEngine +class DuckDuckGo extends BaseEngine constructor: -> - super "^https?://([a-z]+\\.)?duckduckgo\\.com/" - @exampleSearchUrl = "https://duckduckgo.com/?q=%s" - @exampleKeyword = "d" - getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://duckduckgo.com/ac/?q=%s" + super + engineUrl: "https://duckduckgo.com/ac/?q=%s" + regexps: "^https?://([a-z]+\\.)?duckduckgo\\.com/" + example: + searchUrl: "https://duckduckgo.com/?q=%s" + keyword: "d" + parse: (xhr) -> suggestion.phrase for suggestion in JSON.parse xhr.responseText -class Webster extends RegexpEngine +class Webster extends BaseEngine constructor: -> - super "^https?://www.merriam-webster.com/dictionary/" - @exampleSearchUrl = "http://www.merriam-webster.com/dictionary/%s" - @exampleKeyword = "dw" - @exampleDescription = "Dictionary" - getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://www.merriam-webster.com/autocomplete?query=%s" + super + engineUrl: "http://www.merriam-webster.com/autocomplete?query=%s" + regexps: "^https?://www.merriam-webster.com/dictionary/" + example: + searchUrl: "http://www.merriam-webster.com/dictionary/%s" + keyword: "dw" + description: "Dictionary" + parse: (xhr) -> JSON.parse(xhr.responseText).suggestions # A dummy search engine which is guaranteed to match any search URL, but never produces completions. This # allows the rest of the logic to be written knowing that there will always be a completion engine match. -class DummyCompletionEngine - dummy: true - match: -> true - # We return a useless URL which we know will succeed, but which won't generate any network traffic. - getUrl: -> chrome.runtime.getURL "content_scripts/vimium.css" - parse: -> [] +class DummyCompletionEngine extends BaseEngine + constructor: -> + super + regexps: "." + dummy: true # Note: Order matters here. CompletionEngines = [ Youtube - GoogleMaps + # GoogleMaps Google DuckDuckGo Wikipedia diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index d89eb278..cb819025 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -58,12 +58,7 @@ CompletionSearch = return callback [] if 1 == queryTerms.length and Utils.isUrl query return callback [] if Utils.hasJavascriptPrefix query - # Cache completions. However, completions depend upon both the searchUrl and the query terms. So we need - # to generate a key. We mix in some junk generated by pwgen. A key clash might be possible, but - # is vanishingly unlikely. - junk = "//Zi?ei5;o//" - completionCacheKey = searchUrl + junk + queryTerms.map((s) -> s.toLowerCase()).join junk - + completionCacheKey = JSON.stringify [ searchUrl, queryTerms ] if @completionCache.has completionCacheKey console.log "hit", completionCacheKey if @debug return callback @completionCache.get completionCacheKey -- cgit v1.2.3 From 2d5a01c9791a81aa87eaa935a1183f10950bdc84 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 6 Jun 2015 06:53:56 +0100 Subject: Re-work completions: add engine wrapper. --- background_scripts/completion_search.coffee | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'background_scripts') diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index cb819025..b3ae88d4 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -1,4 +1,13 @@ +class EngineWrapper + constructor: (@searchUrl, @engine) -> + + getUrl: (queryTerms) -> + @engine.getUrl queryTerms + + parse: (xhr) -> + @engine.parse xhr + CompletionSearch = debug: false inTransit: {} @@ -93,7 +102,7 @@ CompletionSearch = # Elide duplicate requests. First fetch the suggestions... @inTransit[completionCacheKey] ?= new AsyncDataFetcher (callback) => - engine = @lookupEngine searchUrl + engine = new EngineWrapper searchUrl, @lookupEngine searchUrl url = engine.getUrl queryTerms @get searchUrl, url, (xhr = null) => -- cgit v1.2.3 From 4eda19de339212f86a9b008a4f3142a61d62829e Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 6 Jun 2015 12:01:23 +0100 Subject: Re-work completions: extend engine wrapper to handle prefixes. This commit contains the bulk og the material changes for which the previous commits established the basis. 1) Add a general framework for detecting query prefixes in search URLs, adding them to query sent to the completion engine, and stripping them from the resulting suggestions. This allows the user to have a search engine... j: http://www.google.com/search?q=javascript+%s Javascript and have the prefix "javascript" included (automatically) in queries sent to completion engines, which results in substantially better suggestions. 2) Re-work completion for Google Maps in a simpler form. --- background_scripts/completion.coffee | 13 +++--- background_scripts/completion_engines.coffee | 55 +++++++++++------------ background_scripts/completion_search.coffee | 66 ++++++++++++++++++++-------- 3 files changed, 81 insertions(+), 53 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index c83066a6..6b58f4ea 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -450,7 +450,7 @@ class SearchEngineCompleter { keyword, searchUrl, description } = engine extend request, searchUrl, customSearchMode: true - factor = 0.5 + @previousSuggestions[searchUrl] ?= [] haveCompletionEngine = CompletionSearch.haveCompletionEngine searchUrl # This filter is applied to all of the suggestions from all of the completers, after they have been @@ -466,13 +466,14 @@ class SearchEngineCompleter ) # 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. + # return it for the current query). This allows the user to pick suggestions they've previously seen by + # typing fragments of their text, without regard to whether the completion engine can continue to complete + # the actual text of the query. previousSuggestions = if queryTerms.length == 0 [] else - for url, suggestion of @previousSuggestions + for url, suggestion of @previousSuggestions[searchUrl] 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 @@ -484,6 +485,7 @@ class SearchEngineCompleter type: description url: Utils.createSearchUrl queryTerms, searchUrl title: queryTerms.join " " + searchUrl: searchUrl relevancy: 2.0 autoSelect: true highlightTerms: false @@ -496,11 +498,12 @@ class SearchEngineCompleter count = 0 (suggestion) => url = Utils.createSearchUrl suggestion, searchUrl - @previousSuggestions[url] = new Suggestion + @previousSuggestions[searchUrl][url] = new Suggestion queryTerms: queryTerms type: description url: url title: suggestion + searchUrl: searchUrl insertText: suggestion highlightTerms: false highlightTermsExcludeUrl: true diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index afbb2040..b572375d 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -15,9 +15,9 @@ # successfully), and returns a list of suggestions (a list of strings). This method is always executed # within the context of a try/catch block, so errors do not propagate. # -# 4. For documentation only, each completion engine *must* and example custom search engine. The example -# must include an example "keyword" and and example "searchUrl", and may include and example -# "description". +# 4. Each completion engine *must* include an example custom search engine. The example must include an +# example "keyword" and an example "searchUrl", and may include an example "description" and an +# "explanation". # # Each new completion engine must be added to the list "CompletionEngines" at the bottom of this file. # @@ -45,37 +45,34 @@ class GoogleXMLBaseEngine extends BaseEngine suggestion class Google extends GoogleXMLBaseEngine - constructor: (regexps = null) -> + constructor: () -> super engineUrl: "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s" - regexps: regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" + regexps: "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" example: searchUrl: "http://www.google.com/search?q=%s" keyword: "g" -## # A wrapper class for Google completions. This adds prefix terms to the query, and strips those terms from -## # the resulting suggestions. For example, for Google Maps, we add "map of" as a prefix, then strip "map of" -## # from the resulting suggestions. -## class GoogleWithPrefix extends Google -## constructor: (prefix, args...) -> -## super args... -## prefix = prefix.trim() -## @prefix = "#{prefix} " -## @queryTerms = prefix.split /\s+/ -## getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] -## parse: (xhr) -> -## super(xhr) -## .filter (suggestion) => suggestion.startsWith @prefix -## .map (suggestion) => suggestion[@prefix.length..].ltrim() -## -## # For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, -## # then strip "map of" from the resulting suggestions. -## class GoogleMaps extends GoogleWithPrefix -## constructor: -> -## super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" -## @exampleSearchUrl = "https://www.google.com/maps?q=%s" -## @exampleKeyword = "m" -## @exampleDescription = "Google maps" +class GoogleMaps extends GoogleXMLBaseEngine + prefix: "map of " + constructor: () -> + super + engineUrl: "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=#{@prefix.split(' ').join '+'}%s" + regexps: "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" + example: + searchUrl: "https://www.google.com/maps?q=%s" + keyword: "m" + explanation: + """ + This uses regular Google completion, but prepends the text "map of" to the query. It works + well for places, countries, states, geographical regions and the like, but will not perform address + search. + """ + + parse: (xhr) -> + for suggestion in super xhr + continue unless suggestion.startsWith @prefix + suggestion[@prefix.length..] class Youtube extends GoogleXMLBaseEngine constructor: -> @@ -154,7 +151,7 @@ class DummyCompletionEngine extends BaseEngine # Note: Order matters here. CompletionEngines = [ Youtube - # GoogleMaps + GoogleMaps Google DuckDuckGo Wikipedia diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index b3ae88d4..09261ff6 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -1,12 +1,38 @@ -class EngineWrapper +# This is a wrapper class for completion engines. It handles the case where a custom search engine includes a +# prefix query term (or terms). For example: +# +# http://www.google.com/search?q=javascript+%s +# +# In this case, we get better suggestions if we include the term "javascript" in queries sent to the +# completion engine. This wrapper handles adding such prefixes to completion-engine queries and removing them +# from the resulting suggestions. +class EnginePrefixWrapper constructor: (@searchUrl, @engine) -> getUrl: (queryTerms) -> + # This tests whether @searchUrl contains something of the form "...=abc+def+%s...", from which we extract + # a prefix of the form "abc def ". + if /\=.+\+%s/.test @searchUrl + terms = @searchUrl.replace /\+%s.*/, "" + terms = terms.replace /.*=/, "" + terms = terms.replace /\+/g, " " + + queryTerms = [ terms.split(" ")..., queryTerms... ] + prefix = "#{terms} " + + @postprocessSuggestions = + (suggestions) -> + for suggestion in suggestions + continue unless suggestion.startsWith prefix + suggestion[prefix.length..] + @engine.getUrl queryTerms parse: (xhr) -> - @engine.parse xhr + @postprocessSuggestions @engine.parse xhr + + postprocessSuggestions: (suggestions) -> suggestions CompletionSearch = debug: false @@ -74,22 +100,23 @@ CompletionSearch = # If the user appears to be typing a continuation of the characters of the most recent query, then we can # sometimes re-use the previous suggestions. - if @mostRecentQuery? and @mostRecentSuggestions? - reusePreviousSuggestions = do => - # 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. - # 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, @mostRecentSuggestions.length if @debug - return callback @completionCache.set completionCacheKey, @mostRecentSuggestions + if @mostRecentQuery? and @mostRecentSuggestions? and @mostRecentSearchUrl? + if searchUrl == @mostRecentSearchUrl + reusePreviousSuggestions = do => + # 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. + # 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, @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 # signal that we haven't found a match by returning null. @@ -102,7 +129,7 @@ CompletionSearch = # Elide duplicate requests. First fetch the suggestions... @inTransit[completionCacheKey] ?= new AsyncDataFetcher (callback) => - engine = new EngineWrapper searchUrl, @lookupEngine searchUrl + engine = new EnginePrefixWrapper searchUrl, @lookupEngine searchUrl url = engine.getUrl queryTerms @get searchUrl, url, (xhr = null) => @@ -128,6 +155,7 @@ CompletionSearch = # ... then use the suggestions. @inTransit[completionCacheKey].use (suggestions) => + @mostRecentSearchUrl = searchUrl @mostRecentQuery = query @mostRecentSuggestions = suggestions callback @completionCache.set completionCacheKey, suggestions -- cgit v1.2.3 From cb900a255113b8304d8931f7c6294e20f7f9f36d Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 6 Jun 2015 14:19:45 +0100 Subject: Re-work completions: only offer actual search URL matches. When filter suggestions from other completers, most notably the history completer, we only keep suggestions which match the current searchUrl and completer. Here, we also *replace* the URL of the suggestion. With duplicate elimination, multiple history entries (e.g. those generated with various "Search Tools" settings on Google) will be collapsed to one. This matters because, with custom search engines, we don't show the URL, so the user can't see differences in the URL. Without this, the user can be presented with a list of apparently identical completions. --- background_scripts/completion.coffee | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'background_scripts') diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index 6b58f4ea..fc6263ee 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -456,24 +456,26 @@ class SearchEngineCompleter # This filter is applied to all of the suggestions from all of the completers, after they have been # aggregated by the MultiCompleter. filter = (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 - ) + # 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). + for suggestion in suggestions + if suggestion.isSearchSuggestion or suggestion.isCustomSearch + suggestion + else + terms = Utils.extractQuery searchUrl, suggestion.url + continue unless terms and RankingUtils.matches queryTerms, terms + suggestion.url = Utils.createSearchUrl terms, searchUrl + suggestion # 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 they've previously seen by - # typing fragments of their text, without regard to whether the completion engine can continue to complete - # the actual text of the query. + # return it for the current query). This allows the user to pick suggestions that they've previously seen + # by typing fragments of their text, without regard to whether the completion engine can continue to + # complete the actual text of the query. previousSuggestions = if queryTerms.length == 0 [] else - for url, suggestion of @previousSuggestions[searchUrl] + for _, suggestion of @previousSuggestions[searchUrl] 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 -- cgit v1.2.3