aboutsummaryrefslogtreecommitdiffstats
path: root/lib/completion.js
diff options
context:
space:
mode:
authorNiklas Baumstark2012-01-27 19:21:54 +0100
committerNiklas Baumstark2012-04-10 23:59:54 +0200
commit3449af0461782c24c8577fe4a5938f35f417cbb1 (patch)
treefbf4f352f1679599c4fa8f7d84cafd50e05870e6 /lib/completion.js
parent0ab64a092def03ccd462802f040d83ce2911ea1e (diff)
downloadvimium-3449af0461782c24c8577fe4a5938f35f417cbb1.tar.bz2
move completion logic to background page
This has the following advantages: * searching is done in the background, UI responsiveness is improved * caches are no longer duplicated. This saves RAM and improves performance
Diffstat (limited to 'lib/completion.js')
-rw-r--r--lib/completion.js204
1 files changed, 133 insertions, 71 deletions
diff --git a/lib/completion.js b/lib/completion.js
index 1915a83a..f04eb356 100644
--- a/lib/completion.js
+++ b/lib/completion.js
@@ -195,6 +195,14 @@ var completion = (function() {
return [ open(false), open(true, true), open(true, false) ];
}
+ /** Returns an action that switches to the tab with the given :id. */
+ function createActionSwitchToTab(id) {
+ var open = function() {
+ chrome.extension.sendRequest({ handler: 'selectSpecificTab', id: id });
+ }
+ return [open, open, open];
+ }
+
/** Creates an file-internal representation of a URL match with the given paramters */
function createCompletionHtml(type, str, title) {
title = title || '';
@@ -259,32 +267,17 @@ var completion = (function() {
/** Helper class to construct fuzzy completers for asynchronous data sources like history or bookmark
* matchers. */
- var AsyncCompleter = function() {
- this.completions = null;
+ var AsyncCompletionSource = function() {
this.id = utils.createUniqueId();
- this.readyCallback = this.fallbackReadyCallback = function(results) {
+ this.reset();
+ this.resultsReady = this.fallbackReadyCallback = function(results) {
this.completions = results;
}
}
- AsyncCompleter.prototype = {
- /** Convenience function to remove shared code in the completers. Clears the completion cache, sends
- * a message to an extension port and pipes the returned message through a callback before storing it into
- * the instance's completion cache.
- */
- fetchFromPort: function(name, query, callback) {
- this.completions = null; // reset completions
-
- // asynchronously fetch from a port
- var port = chrome.extension.connect({ name: name }) ;
- var self = this;
- port.onMessage.addListener(function(msg) {
- self.readyCallback(callback(msg));
- });
- port.postMessage(query);
- },
-
- resetCache: function() {
+ AsyncCompletionSource.prototype = {
+ reset: function() {
fuzzyMatcher.invalidateFilterCache(this.id);
+ this.completions = null;
},
/** Convenience function to remove shared code in the completers. Creates an internal representation of
@@ -294,7 +287,7 @@ var completion = (function() {
var url = item.url;
var parts = [type, url, item.title];
var str = parts.join(' ');
- action = action || createActionOpenUrl(url);
+ action = action || {func: 'completion.createActionOpenUrl', args: [url]};
function createLazyCompletion(query) {
return new LazyCompletion(url.length / fuzzyMatcher.calculateRelevancy(query, str), function() {
@@ -333,10 +326,10 @@ var completion = (function() {
handler(this.completions);
} else {
// no: register the handler as a callback
- this.readyCallback = function(results) {
+ this.resultsReady = function(results) {
handler(results);
- self.readyCallback = self.fallbackReadyCallback;
- self.readyCallback(results);
+ self.resultsReady = self.fallbackReadyCallback;
+ self.resultsReady(results);
}
}
},
@@ -346,7 +339,7 @@ var completion = (function() {
/** A simple completer that suggests to open the input string as an URL or to trigger a web search for the
* given term, depending on whether it thinks the input is an URL or not. */
- var SmartCompleter = function(commands) {
+ var SmartCompletionSource = function(commands) {
commands = commands || {};
var commandKeys = Object.keys(commands);
@@ -364,7 +357,7 @@ var completion = (function() {
return new LazyCompletion(-2, function() {
return {
html: createCompletionHtml(desc, term),
- action: createActionOpenUrl(utils.createFullUrl(url)),
+ action: {func: 'completion.createActionOpenUrl', args: [utils.createFullUrl(url)]},
}})
});
}
@@ -379,8 +372,8 @@ var completion = (function() {
return new LazyCompletion(-1, function() {
return {
html: createCompletionHtml(isUrl ? 'goto' : 'search', query),
- action: createActionOpenUrl(isUrl ? utils.createFullUrl(query)
- : utils.createSearchUrl(query)),
+ action: {func: 'completion.createActionOpenUrl', args: isUrl ? [utils.createFullUrl(query)]
+ : [utils.createSearchUrl(query)]},
}});
}
@@ -391,69 +384,134 @@ var completion = (function() {
}
}
- /** A fuzzy history completer */
- var FuzzyHistoryCompleter = function(maxResults) {
- AsyncCompleter.call(this);
- this.maxResults = maxResults || 1000;
+ /** A fuzzy bookmark completer */
+ var FuzzyBookmarkCompletionSource = function() {
+ AsyncCompletionSource.call(this);
}
- utils.extend(AsyncCompleter, FuzzyHistoryCompleter);
+ utils.extend(AsyncCompletionSource, FuzzyBookmarkCompletionSource);
- FuzzyHistoryCompleter.prototype.refresh = function() {
+ FuzzyBookmarkCompletionSource.prototype.traverseTree = function(bookmarks, results) {
var self = this;
- self.resetCache();
- self.fetchFromPort('getHistory', { maxResults: self.maxResults }, function(msg) {
- return msg.history.map(function(item) {
- return self.createInternalMatch('history', item);
- });
+ bookmarks.forEach(function(bookmark) {
+ results.push(bookmark);
+ if (bookmark.children === undefined)
+ return;
+ self.traverseTree(bookmark.children, results);
});
}
- /** A fuzzy bookmark completer */
- var FuzzyBookmarkCompleter = function() {
- AsyncCompleter.call(this);
+ FuzzyBookmarkCompletionSource.prototype.refresh = function() {
+ var self = this; self.reset();
+ chrome.bookmarks.getTree(function(bookmarks) {
+ var results = [];
+ self.traverseTree(bookmarks, results);
+
+ self.resultsReady(results.filter(function(b) { return b.url !== undefined; })
+ .map(function(bookmark) {
+ return self.createInternalMatch('bookmark', bookmark);
+ }));
+ });
+ }
+
+ /** A fuzzy history completer */
+ var FuzzyHistoryCompletionSource = function(maxResults) {
+ AsyncCompletionSource.call(this);
+ this.cachedHistory = null;
+ this.maxResults = maxResults;
}
- utils.extend(AsyncCompleter, FuzzyBookmarkCompleter);
+ utils.extend(AsyncCompletionSource, FuzzyHistoryCompletionSource);
- FuzzyBookmarkCompleter.prototype.refresh = function() {
+ FuzzyHistoryCompletionSource.prototype.useHistory = function(callback) {
var self = this;
- self.resetCache();
- self.fetchFromPort('getAllBookmarks', {}, function(msg) {
- return msg.bookmarks.filter(function(bookmark) { return bookmark.url !== undefined })
- .map(function(bookmark) {
- return self.createInternalMatch('bookmark', bookmark);
+ if (self.cachedHistory !== null)
+ return callback(self.cachedHistory);
+
+ chrome.history.search({
+ text: '',
+ maxResults: 20000,
+ startTime: 0,
+ }, function(history) {
+ // sorting in ascending order, so we can push new items to the end later
+ history.sort(function(a, b) {
+ return (a.lastVisitTime|| 0) - (b.lastVisitTime || 0);
});
+ self.cachedHistory = history;
+ callback(history);
+ });
+
+ chrome.history.onVisited.addListener(function(item) {
+ // only cache newly visited sites
+ if (item.visitCount === 1)
+ self.cachedHistory.push(item);
+ });
+ }
+
+ FuzzyHistoryCompletionSource.prototype.refresh = function() {
+ var self = this;
+ self.reset();
+
+ self.useHistory(function(history) {
+ self.resultsReady(history.slice(-self.maxResults).map(function(item) {
+ return self.createInternalMatch('history', item);
+ }))
});
}
/** A fuzzy tab completer */
- var FuzzyTabCompleter = function() {
- AsyncCompleter.call(this);
+ var FuzzyTabCompletionSource = function() {
+ AsyncCompletionSource.call(this);
}
- utils.extend(AsyncCompleter, FuzzyTabCompleter);
+ utils.extend(AsyncCompletionSource, FuzzyTabCompletionSource);
- FuzzyTabCompleter.prototype.refresh = function() {
+ FuzzyTabCompletionSource.prototype.refresh = function() {
var self = this;
- self.resetCache();
- self.fetchFromPort('getTabsInCurrentWindow', {}, function(msg) {
- return msg.tabs.map(function(tab) {
- var open = function() {
- chrome.extension.sendRequest({ handler: 'selectSpecificTab', id: tab.id });
- }
- return self.createInternalMatch('tab', tab, [open, open, open]);
- });
+ self.reset();
+
+ chrome.tabs.getAllInWindow(null, function(tabs) {
+ self.resultsReady(tabs.map(function(tab) {
+ return self.createInternalMatch('tab', tab,
+ { func: 'completion.createActionSwitchToTab',
+ args: [tab.id] });
+ }));
});
}
+ var BackgroundCompleter = function(name) {
+ this.name = name;
+ this.filterPort = chrome.extension.connect({ name: 'filterCompleter' });
+ }
+ BackgroundCompleter.prototype = {
+ refresh: function() {
+ chrome.extension.sendRequest({ handler: 'refreshCompleter', name: this.name });
+ },
+
+ filter: function(query, maxResults, callback) {
+ var id = utils.createUniqueId();
+ this.filterPort.onMessage.addListener(function(msg) {
+ if (msg.id != id) return;
+ callback(msg.results.map(function(result) {
+ var action = result.action;
+ result.action = eval(action.func).apply(null, action.args);
+ return result;
+ }));
+ });
+ this.filterPort.postMessage({ id: id,
+ name: this.name,
+ query: query,
+ maxResults: maxResults });
+ },
+ }
+
/** A meta-completer that delegates queries and merges and sorts the results of a collection of other
* completer instances given in :sources. The optional argument :queryThreshold determines how long a
- * query has to be to trigger a refresh. */
- var MergingCompleter = function(sources, queryThreshold) {
+ * query has to be to trigger a search. */
+ var MultiCompleter = function(sources, queryThreshold) {
if (queryThreshold === undefined)
queryThreshold = 1; // default
this.sources = sources;
this.queryThreshold = queryThreshold;
}
- MergingCompleter.prototype = {
+ MultiCompleter.prototype = {
refresh: function() {
this.sources.forEach(function(x) { x.refresh(); });
},
@@ -464,6 +522,7 @@ var completion = (function() {
return;
}
+ var self = this;
var all = [];
var counter = this.sources.length;
@@ -484,10 +543,13 @@ var completion = (function() {
// public interface
return {
- FuzzyHistoryCompleter: FuzzyHistoryCompleter,
- FuzzyBookmarkCompleter: FuzzyBookmarkCompleter,
- FuzzyTabCompleter: FuzzyTabCompleter,
- SmartCompleter: SmartCompleter,
- MergingCompleter: MergingCompleter,
+ FuzzyBookmarkCompletionSource: FuzzyBookmarkCompletionSource,
+ FuzzyHistoryCompletionSource: FuzzyHistoryCompletionSource,
+ FuzzyTabCompletionSource: FuzzyTabCompletionSource,
+ SmartCompletionSource: SmartCompletionSource,
+ MultiCompleter: MultiCompleter,
+ BackgroundCompleter: BackgroundCompleter,
+ createActionOpenUrl: createActionOpenUrl,
+ createActionSwitchToTab: createActionSwitchToTab,
};
-})();
+})()