From d848f50fb1c199de581bf63e18495b7f4d0c4faf Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 2 May 2015 08:30:15 +0100 Subject: Search completion; refactor to separate file. --- lib/utils.coffee | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index fba03b61..338e535d 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -194,5 +194,43 @@ globalRoot.extend = (hash1, hash2) -> hash1[key] = hash2[key] hash1 +# A simple cache. Entries used within an expiry period are retained (for one more expiry period), otherwise +# they are discarded. +class SimpleCache + # expiry: expiry time in milliseconds (default, one hour) + # entries: maximum number of entries + constructor: (@expiry = 60 * 60 * 1000, @entries = 1000) -> + @cache = {} + @previous = {} + setInterval (=> @rotate()), @expiry + + rotate: -> + @previous = @cache + @cache = {} + + has: (key) -> + (key of @cache) or key of @previous + + get: (key) -> + console.log "get", key + if key of @cache + @cache[key] + else if key of @previous + @cache[key] = @previous[key] + else + null + + # Set value, and return that value. If value is null, then delete key. + set: (key, value = null) -> + if value? + @cache[key] = value + delete @previous[key] + @rotate() if @entries < Object.keys(@cache).length + else + delete @cache[key] + delete @previous[key] + value + root = exports ? window root.Utils = Utils +root.SimpleCache = SimpleCache -- cgit v1.2.3 From 41495d11e6608767dde299223f10c8a606d4a8fb Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 2 May 2015 12:14:16 +0100 Subject: Search completion; minor tweaks. Including: - Make completers classes. That way, we may be able to get better code reuse. --- lib/utils.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 338e535d..88fe9e2c 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -202,7 +202,8 @@ class SimpleCache constructor: (@expiry = 60 * 60 * 1000, @entries = 1000) -> @cache = {} @previous = {} - setInterval (=> @rotate()), @expiry + rotate = => @rotate() + setInterval rotate, @expiry rotate: -> @previous = @cache @@ -212,7 +213,6 @@ class SimpleCache (key of @cache) or key of @previous get: (key) -> - console.log "get", key if key of @cache @cache[key] else if key of @previous -- cgit v1.2.3 From ba4e8018e3d8cd80e0fa9ac541e37e7eee37028f Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 2 May 2015 17:32:28 +0100 Subject: Search completion; tweaks and refactoring. --- lib/utils.coffee | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 88fe9e2c..5d9696e1 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -177,6 +177,13 @@ 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 + + # 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 = -> -- cgit v1.2.3 From 776f617ece5d333fe70df903982a18d65fc2776a Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 3 May 2015 17:22:20 +0100 Subject: Search completion; make completion lookup asynchronous. --- lib/utils.coffee | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 5d9696e1..07528714 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -183,6 +183,12 @@ Utils = return true if re.test string false + # 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. -- cgit v1.2.3 From 43bdd2787f2bffc4fc9c3397937a0ce9a183beda Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 5 May 2015 16:25:27 +0100 Subject: Search completion; better SimpleCache. SimpleCache should reset the timer very time it's rotated (including when the allowed number of entries is exceeded. --- lib/utils.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 07528714..1b2a7a3f 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -211,16 +211,18 @@ globalRoot.extend = (hash1, hash2) -> # they are discarded. class SimpleCache # expiry: expiry time in milliseconds (default, one hour) - # entries: maximum number of entries + # 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 = {} - rotate = => @rotate() - setInterval rotate, @expiry + @rotate() # Force starts the rotation timer. 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. + clearTimeout @timer if @timer? + @timer = Utils.setTimeout @expiry, => @rotate() has: (key) -> (key of @cache) or key of @previous -- cgit v1.2.3 From 28807bd25b27e5404228a638f2ab5e6c00f606cc Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 5 May 2015 16:43:48 +0100 Subject: Search completion; misc. --- lib/utils.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 1b2a7a3f..e97872f0 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -207,14 +207,14 @@ globalRoot.extend = (hash1, hash2) -> hash1[key] = hash2[key] hash1 -# A simple cache. Entries used within an expiry period are retained (for one more expiry period), otherwise -# they are discarded. +# A simple cache. Entries used within two expiry periods are retained, otherwise they are discarded. +# At most 2 * @entries entries are retained. 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 = {} - @rotate() # Force starts the rotation timer. + @rotate() # Force start the rotation timer. rotate: -> @previous = @cache -- cgit v1.2.3 From 5752c0ead0a65fc2329515509f66e00bd6ee2f60 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 6 May 2015 07:40:46 +0100 Subject: Search completion; more tweaks. --- lib/utils.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index e97872f0..354d82f6 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -96,11 +96,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. -- cgit v1.2.3 From 44378220093ee5bd873b553f5be556921c778663 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 8 May 2015 11:31:05 +0100 Subject: Search completion; clear (not replace) the cache. --- lib/utils.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 354d82f6..1c24a40f 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -236,6 +236,17 @@ class SimpleCache else null + clear: -> + @rotate() + @rotate() + + # Because of the timer, we can't just let these caches go out of scope and have the garbage collector + # harvest them. Whenever they may fall out of use, we need to remove the timer. @rotate() can be used to + # restart the cache. + suspend: -> + clearTimeout @timer if @timer? + @timer = null + # Set value, and return that value. If value is null, then delete key. set: (key, value = null) -> if value? -- cgit v1.2.3 From 82d25b5df76c8526d4ccb5352c0905cc28371199 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 8 May 2015 16:47:24 +0100 Subject: Search completion; search keyword on SPACE. --- lib/utils.coffee | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') 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) -> -- cgit v1.2.3 From 6fd0e15b96325222abf1a19886bd5e0fb48fcdbb Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 9 May 2015 08:30:29 +0100 Subject: Search completion; refactor SearchCompleter activation. --- lib/utils.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 033fdd2b..4c2a7a14 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -201,7 +201,8 @@ Function::curry = -> Array.copy = (array) -> Array.prototype.slice.call(array, 0) String::startsWith = (str) -> @indexOf(str) == 0 -String::ltrim = () -> @replace /^\s+/, "" +String::ltrim = -> @replace /^\s+/, "" +String::rtrim = -> @replace /\s+$/, "" globalRoot = window ? global globalRoot.extend = (hash1, hash2) -> -- cgit v1.2.3 From 313a1f96d666f23c2bc75ef340f0f828319e127c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 10 May 2015 05:35:50 +0100 Subject: Search completion; refactor searchEngineCompleter. This revamps how search-engine configuration is handled, and revises some rather strange legacy code. --- lib/utils.coffee | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 4c2a7a14..51b16351 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -260,6 +260,22 @@ class SimpleCache delete @previous[key] value +# This is a simple class for the common case where we want to use some data value which may be immediately +# available, or we may have to wait. It implements the use-immediately-or-wait queue, and calls the function +# to fetch the data asynchronously. +class AsyncDataFetcher + constructor: (fetch) -> + @data = null + @queue = [] + fetch (@data) => + Utils.nextTick => + callback @data for callback in @queue + @queue = null + + use: (callback) -> + if @data? then callback @data else @queue.push callback + root = exports ? window root.Utils = Utils root.SimpleCache = SimpleCache +root.AsyncDataFetcher = AsyncDataFetcher -- cgit v1.2.3 From 5fdbb8e579c068a54e9a397097d87063a3d8a146 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 10 May 2015 05:57:47 +0100 Subject: Search completion; rework SimpleCache. --- lib/utils.coffee | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 51b16351..b0abfd8f 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -212,24 +212,45 @@ globalRoot.extend = (hash1, hash2) -> # 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 = {} - @rotate() # Force start the rotation timer. + @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. + # because the number of @entries was exceeded). We only restart the timer if the cache is not empty. clearTimeout @timer if @timer? - @timer = Utils.setTimeout @expiry, => @rotate() + @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] @@ -242,27 +263,9 @@ class SimpleCache @rotate() @rotate() - # Because of the timer, we can't just let these caches go out of scope and have the garbage collector - # harvest them. Whenever they may fall out of use, we need to remove the timer. @rotate() can be used to - # restart the cache. - suspend: -> - clearTimeout @timer if @timer? - @timer = null - - # Set value, and return that value. If value is null, then delete key. - set: (key, value = null) -> - if value? - @cache[key] = value - delete @previous[key] - @rotate() if @entries < Object.keys(@cache).length - else - delete @cache[key] - delete @previous[key] - value - # This is a simple class for the common case where we want to use some data value which may be immediately -# available, or we may have to wait. It implements the use-immediately-or-wait queue, and calls the function -# to fetch the data asynchronously. +# available, or for which we may have to wait. It implements the use-immediately-or-wait queue, and calls the +# function to fetch the data asynchronously. class AsyncDataFetcher constructor: (fetch) -> @data = null -- cgit v1.2.3 From 198dd8fa89148f3389c289bf71c40b9ab1a5681f Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 10 May 2015 06:45:37 +0100 Subject: Search completion; refactor job-running logic. --- lib/utils.coffee | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index b0abfd8f..16adc305 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -278,7 +278,23 @@ class AsyncDataFetcher 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 -- cgit v1.2.3 From 1a337a261a6dd6deffa836cbd949bb036e103f36 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 10 May 2015 08:59:14 +0100 Subject: Search completion; reuse previous query. --- lib/utils.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index 16adc305..a1ed23c2 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -184,6 +184,16 @@ Utils = 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 -- cgit v1.2.3 From 9bc1c215e3857d109fca2a073fd50799e0021cc8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 10 May 2015 12:07:48 +0100 Subject: Search completion; fix synchronisation issue. --- lib/utils.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/utils.coffee b/lib/utils.coffee index a1ed23c2..1ff33300 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -274,14 +274,14 @@ class SimpleCache @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 the use-immediately-or-wait queue, and calls the -# function to fetch the data asynchronously. +# 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 = [] - fetch (@data) => - Utils.nextTick => + Utils.nextTick => + fetch (@data) => callback @data for callback in @queue @queue = null -- cgit v1.2.3