diff options
| author | Stephen Blott | 2015-06-06 06:18:35 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-06-06 06:46:32 +0100 |
| commit | f5db9ea1dc8d23dff732dd3345bf85798d64f1e9 (patch) | |
| tree | f8aff705dfe0a666f3ba53190a2c35799c689f0c /background_scripts/completion_engines.coffee | |
| parent | 97e5da6cbdcf1b5cf4f80cf550fc26c1ce194a3e (diff) | |
| download | vimium-f5db9ea1dc8d23dff732dd3345bf85798d64f1e9.tar.bz2 | |
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).
Diffstat (limited to 'background_scripts/completion_engines.coffee')
| -rw-r--r-- | background_scripts/completion_engines.coffee | 210 |
1 files changed, 114 insertions, 96 deletions
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 |
