diff options
| author | Stephen Blott | 2015-05-11 14:55:00 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-11 14:55:00 +0100 |
| commit | 104dc7ff2c88c7df9760c6ca35991d9c160bbb35 (patch) | |
| tree | ecf9af8cfc32756308f39e12d4c10df43304e143 /lib/utils.coffee | |
| parent | 3503b5a510416e04449272a461e6765b7e7cd3f7 (diff) | |
| parent | 4ba12991a277d193969e87706facdba12fdee4d0 (diff) | |
| download | vimium-104dc7ff2c88c7df9760c6ca35991d9c160bbb35.tar.bz2 | |
Merge branch 'search-engine-completion-v5'
Diffstat (limited to 'lib/utils.coffee')
| -rw-r--r-- | lib/utils.coffee | 122 |
1 files changed, 117 insertions, 5 deletions
diff --git a/lib/utils.coffee b/lib/utils.coffee index db63d53a..a56340f5 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -107,11 +107,12 @@ Utils = query = query.split(/\s+/) if typeof(query) == "string" query.map(encodeURIComponent).join "+" - # Creates a search URL from the given :query. - createSearchUrl: (query) -> - # It would be better to pull the default search engine from chrome itself. However, unfortunately chrome - # does not provide an API for doing so. - Settings.get("searchUrl") + @createSearchQuery query + # Create a search URL from the given :query (using either the provided search URL, or the default one). + # It would be better to pull the default search engine from chrome itself. However, chrome does not provide + # an API for doing so. + createSearchUrl: (query, searchUrl = Settings.get("searchUrl")) -> + searchUrl += "%s" unless 0 <= searchUrl.indexOf "%s" + searchUrl.replace /%s/g, @createSearchQuery query # Converts :string into a Google search if it's not already a URL. We don't bother with escaping characters # as Chrome will do that for us. @@ -185,6 +186,29 @@ Utils = delete obj[property] for property in properties obj + # Does string match any of these regexps? + matchesAnyRegexp: (regexps, string) -> + for re in regexps + return true if re.test string + false + + # Calculate the length of the longest shared prefix of a list of strings. + longestCommonPrefix: (strings) -> + return 0 unless 0 < strings.length + strings.sort (a,b) -> a.length - b.length + [ shortest, strings... ] = strings + for ch, index in shortest.split "" + for str in strings + return index if ch != str[index] + return shortest.length + + # Convenience wrapper for setTimeout (with the arguments around the other way). + setTimeout: (ms, func) -> setTimeout func, ms + + # Like Nodejs's nextTick. + nextTick: (func) -> @setTimeout 0, func + + # This creates a new function out of an existing function, where the new function takes fewer arguments. This # allows us to pass around functions instead of functions + a partial list of arguments. Function::curry = -> @@ -195,6 +219,8 @@ Function::curry = -> Array.copy = (array) -> Array.prototype.slice.call(array, 0) String::startsWith = (str) -> @indexOf(str) == 0 +String::ltrim = -> @replace /^\s+/, "" +String::rtrim = -> @replace /\s+$/, "" globalRoot = window ? global globalRoot.extend = (hash1, hash2) -> @@ -202,5 +228,91 @@ globalRoot.extend = (hash1, hash2) -> hash1[key] = hash2[key] hash1 +# A simple cache. Entries used within two expiry periods are retained, otherwise they are discarded. +# At most 2 * @entries entries are retained. +# +# Note. We need to be careful with @timer. If all references to a cache are lost, then eventually its +# contents must be garbage collected, which will not happen if there are active timers. +class SimpleCache + # expiry: expiry time in milliseconds (default, one hour) + # entries: maximum number of entries in @cache (there may be this many entries in @previous, too) + constructor: (@expiry = 60 * 60 * 1000, @entries = 1000) -> + @cache = {} + @previous = {} + @timer = null + + rotate: -> + @previous = @cache + @cache = {} + # We reset the timer every time the cache is rotated (which could be because a previous timer expired, or + # because the number of @entries was exceeded). We only restart the timer if the cache is not empty. + clearTimeout @timer if @timer? + @timer = null + @checkTimer() if 0 < Object.keys(@previous).length + + checkTimer: -> + unless @timer? + @timer = Utils.setTimeout @expiry, => @rotate() + + has: (key) -> + (key of @cache) or key of @previous + + # Set value, and return that value. If value is null, then delete key. + set: (key, value = null) -> + @checkTimer() + if value? + @cache[key] = value + delete @previous[key] + @rotate() if @entries < Object.keys(@cache).length + else + delete @cache[key] + delete @previous[key] + value + + get: (key) -> + if key of @cache + @cache[key] + else if key of @previous + @cache[key] = @previous[key] + else + null + + clear: -> + @rotate() + @rotate() + +# This is a simple class for the common case where we want to use some data value which may be immediately +# available, or for which we may have to wait. It implements a use-immediately-or-wait queue, and calls the +# fetch function to fetch the data asynchronously. +class AsyncDataFetcher + constructor: (fetch) -> + @data = null + @queue = [] + Utils.nextTick => + fetch (@data) => + callback @data for callback in @queue + @queue = null + + use: (callback) -> + if @data? then callback @data else @queue.push callback + +# This takes a list of jobs (functions) and runs them, asynchronously. Functions queued with @onReady() are +# run once all of the jobs have completed. +class JobRunner + constructor: (@jobs) -> + @fetcher = new AsyncDataFetcher (callback) => + for job in @jobs + do (job) => + Utils.nextTick => + job => + @jobs = @jobs.filter (j) -> j != job + callback true if @jobs.length == 0 + + onReady: (callback) -> + @fetcher.use callback + root = exports ? window root.Utils = Utils +root.SimpleCache = SimpleCache +root.AsyncDataFetcher = AsyncDataFetcher +root.JobRunner = JobRunner |
