summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorKris Maglione <kris@vimperator.org>2010-08-31 21:09:13 -0400
committerKris Maglione <kris@vimperator.org>2010-08-31 21:09:13 -0400
commit8b0d9586b23eb166fafb064e75c4956021d73ca1 (patch)
treecc3a8bdda4e19dc18eadd5ed0edc4aa2131b43e2 /common
parent5632e14721897b9e7e23d493f95358bb7df73314 (diff)
downloadpentadactyl-8b0d9586b23eb166fafb064e75c4956021d73ca1.tar.gz
Merge testing.
--HG-- rename : common/content/base.js => common/modules/base.jsm rename : common/content/services.js => common/modules/services.jsm rename : common/content/style.js => common/modules/styles.jsm rename : common/content/template.js => common/modules/template.jsm rename : common/content/util.js => common/modules/util.jsm
Diffstat (limited to 'common')
-rwxr-xr-xcommon/content/autocommands.js14
-rw-r--r--common/content/bookmarks.js272
-rw-r--r--common/content/buffer.js88
-rw-r--r--common/content/commandline.js99
-rw-r--r--common/content/commands.js234
-rwxr-xr-xcommon/content/completion.js15
-rw-r--r--common/content/configbase.js16
-rw-r--r--common/content/dactyl-overlay.js82
-rw-r--r--common/content/dactyl.js120
-rw-r--r--common/content/editor.js14
-rw-r--r--common/content/events.js13
-rw-r--r--common/content/finder.js84
-rw-r--r--common/content/hints.js2
-rw-r--r--common/content/history.js4
-rwxr-xr-xcommon/content/io.js366
-rw-r--r--common/content/javascript.js17
-rw-r--r--common/content/mappings.js20
-rw-r--r--common/content/marks.js20
-rw-r--r--common/content/modes.js4
-rw-r--r--common/content/modules.js90
-rw-r--r--common/content/options.js59
-rw-r--r--common/content/quickmarks.js2
-rw-r--r--common/content/sanitizer.js13
-rw-r--r--common/content/style.js832
-rw-r--r--common/content/tabs.js11
-rw-r--r--common/modules/base.jsm (renamed from common/content/base.js)234
-rw-r--r--common/modules/bookmarkcache.jsm162
-rw-r--r--common/modules/highlight.jsm241
-rw-r--r--common/modules/services.jsm (renamed from common/content/services.js)30
-rw-r--r--common/modules/storage.jsm535
-rw-r--r--common/modules/styles.jsm373
-rw-r--r--common/modules/template.jsm (renamed from common/content/template.js)94
-rw-r--r--common/modules/util.jsm (renamed from common/content/util.js)164
33 files changed, 2144 insertions, 2180 deletions
diff --git a/common/content/autocommands.js b/common/content/autocommands.js
index 899019f7..97232f9b 100755
--- a/common/content/autocommands.js
+++ b/common/content/autocommands.js
@@ -14,8 +14,6 @@ const AutoCommand = Struct("event", "pattern", "command");
* @instance autocommands
*/
const AutoCommands = Module("autocommands", {
- requires: ["config"],
-
init: function () {
this._store = [];
},
@@ -83,7 +81,7 @@ const AutoCommands = Module("autocommands", {
}
});
- let list = template.commandOutput(commandline.command,
+ commandline.commandOutput(
<table>
<tr highlight="Title">
<td colspan="2">----- Auto Commands -----</td>
@@ -101,8 +99,6 @@ const AutoCommands = Module("autocommands", {
</tr>))
}
</table>);
-
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
/**
@@ -137,7 +133,6 @@ const AutoCommands = Module("autocommands", {
autoCmd.command.call(autoCmd, args);
}
catch (e) {
- dactyl.reportError(e);
dactyl.echoerr(e);
}
}
@@ -203,7 +198,12 @@ const AutoCommands = Module("autocommands", {
return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
},
literal: 2,
- options: [[["-javascript", "-js"], commands.OPTION_NOARG]]
+ options: [
+ {
+ names: ["-javascript", "-js"],
+ description: "Interperate the action as JavaScript code rather than an ex command",
+ }
+ ]
});
[
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
index fff84480..095b4c21 100644
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -10,193 +10,13 @@ const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
// also includes methods for dealing with keywords and search engines
const Bookmarks = Module("bookmarks", {
- requires: ["autocommands", "config", "dactyl", "storage", "services"],
-
init: function () {
- const faviconService = services.get("favicon");
- const bookmarksService = services.get("bookmarks");
- const historyService = services.get("history");
- const tagging = PlacesUtils.tagging;
-
- this.getFavicon = getFavicon;
- function getFavicon(uri) {
- try {
- return faviconService.getFaviconImageForPage(util.newURI(uri)).spec;
- }
- catch (e) {
- return "";
- }
- }
-
- // Fix for strange Firefox bug:
- // Error: [Exception... "Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIObserverService.addObserver]"
- // nsresult: "0x8000ffff (NS_ERROR_UNEXPECTED)"
- // location: "JS frame :: file://~firefox/components/nsTaggingService.js :: anonymous :: line 89"
- // data: no]
- // Source file: file://~firefox/components/nsTaggingService.js
- tagging.getTagsForURI(window.makeURI("http://mysterious.bug"), {});
-
- const Bookmark = Struct("url", "title", "icon", "keyword", "tags", "id");
- const Keyword = Struct("keyword", "title", "icon", "url");
- Bookmark.defaultValue("icon", function () getFavicon(this.url));
- Bookmark.prototype.__defineGetter__("extra", function () [
- ["keyword", this.keyword, "Keyword"],
- ["tags", this.tags.join(", "), "Tag"]
- ].filter(function (item) item[1]));
-
- const storage = modules.storage;
- function Cache(name, store) {
- const rootFolders = [bookmarksService.toolbarFolder, bookmarksService.bookmarksMenuFolder, bookmarksService.unfiledBookmarksFolder];
- const sleep = dactyl.sleep; // Storage objects are global to all windows, 'dactyl' isn't.
-
- let bookmarks = [];
- let self = this;
-
- this.__defineGetter__("name", function () name);
- this.__defineGetter__("store", function () store);
- this.__defineGetter__("bookmarks", function () this.load());
-
- this.__defineGetter__("keywords",
- function () [Keyword(k.keyword, k.title, k.icon, k.url) for ([, k] in Iterator(self.bookmarks)) if (k.keyword)]);
-
- this.__iterator__ = function () (val for ([, val] in Iterator(self.bookmarks)));
-
- function loadBookmark(node) {
- if (node.uri == null) // How does this happen?
- return false;
- let uri = util.newURI(node.uri);
- let keyword = bookmarksService.getKeywordForBookmark(node.itemId);
- let tags = tagging.getTagsForURI(uri, {}) || [];
- let bmark = Bookmark(node.uri, node.title, node.icon && node.icon.spec, keyword, tags, node.itemId);
-
- bookmarks.push(bmark);
- return bmark;
- }
-
- function readBookmark(id) {
- return {
- itemId: id,
- uri: bookmarksService.getBookmarkURI(id).spec,
- title: bookmarksService.getItemTitle(id)
- };
- }
-
- function deleteBookmark(id) {
- let length = bookmarks.length;
- bookmarks = bookmarks.filter(function (item) item.id != id);
- return bookmarks.length < length;
- }
-
- this.findRoot = function findRoot(id) {
- do {
- var root = id;
- id = bookmarksService.getFolderIdForItem(id);
- } while (id != bookmarksService.placesRoot && id != root);
- return root;
- };
-
- this.isBookmark = function (id) rootFolders.indexOf(self.findRoot(id)) >= 0;
-
- this.isRegularBookmark = function findRoot(id) {
- do {
- var root = id;
- if (services.get("livemark") && services.get("livemark").isLivemark(id))
- return false;
- id = bookmarksService.getFolderIdForItem(id);
- } while (id != bookmarksService.placesRoot && id != root);
- return rootFolders.indexOf(root) >= 0;
- };
-
- // since we don't use a threaded bookmark loading (by set preload)
- // anymore, is this loading synchronization still needed? --mst
- let loading = false;
- this.load = function load() {
- if (loading) {
- while (loading)
- sleep(10);
- return bookmarks;
- }
-
- // update our bookmark cache
- bookmarks = [];
- loading = true;
-
- let folders = rootFolders.slice();
- let query = historyService.getNewQuery();
- let options = historyService.getNewQueryOptions();
- while (folders.length > 0) {
- query.setFolders(folders, 1);
- folders.shift();
- let result = historyService.executeQuery(query, options);
- let folder = result.root;
- folder.containerOpen = true;
-
- // iterate over the immediate children of this folder
- for (let i = 0; i < folder.childCount; i++) {
- let node = folder.getChild(i);
- if (node.type == node.RESULT_TYPE_FOLDER) // folder
- folders.push(node.itemId);
- else if (node.type == node.RESULT_TYPE_URI) // bookmark
- loadBookmark(node);
- }
-
- // close a container after using it!
- folder.containerOpen = false;
- }
- this.__defineGetter__("bookmarks", function () bookmarks);
- loading = false;
- return bookmarks;
- };
-
- var observer = {
- onBeginUpdateBatch: function onBeginUpdateBatch() {},
- onEndUpdateBatch: function onEndUpdateBatch() {},
- onItemVisited: function onItemVisited() {},
- onItemMoved: function onItemMoved() {},
- onItemAdded: function onItemAdded(itemId, folder, index) {
- // dactyl.dump("onItemAdded(" + itemId + ", " + folder + ", " + index + ")\n");
- if (bookmarksService.getItemType(itemId) == bookmarksService.TYPE_BOOKMARK) {
- if (self.isBookmark(itemId)) {
- let bmark = loadBookmark(readBookmark(itemId));
- storage.fireEvent(name, "add", bmark);
- }
- }
- },
- onItemRemoved: function onItemRemoved(itemId, folder, index) {
- // dactyl.dump("onItemRemoved(" + itemId + ", " + folder + ", " + index + ")\n");
- if (deleteBookmark(itemId))
- storage.fireEvent(name, "remove", itemId);
- },
- onItemChanged: function onItemChanged(itemId, property, isAnnotation, value) {
- if (isAnnotation)
- return;
- // dactyl.dump("onItemChanged(" + itemId + ", " + property + ", " + value + ")\n");
- let bookmark = bookmarks.filter(function (item) item.id == itemId)[0];
- if (bookmark) {
- if (property == "tags")
- value = tagging.getTagsForURI(util.newURI(bookmark.url), {});
- if (property in bookmark)
- bookmark[property] = value;
- storage.fireEvent(name, "change", itemId);
- }
- },
- QueryInterface: function QueryInterface(iid) {
- if (iid.equals(Ci.nsINavBookmarkObserver) || iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- };
-
- bookmarksService.addObserver(observer, false);
- }
-
let bookmarkObserver = function (key, event, arg) {
if (event == "add")
autocommands.trigger("BookmarkAdd", arg);
statusline.updateUrl();
};
- this._cache = storage.newObject("bookmark-cache", Cache, { store: false });
storage.addObserver("bookmark-cache", bookmarkObserver, window);
},
@@ -217,7 +37,7 @@ const Bookmarks = Module("bookmarks", {
try {
let uri = util.createURI(url);
if (!force) {
- for (let bmark in this._cache) {
+ for (let bmark in bookmarkcache) {
if (bmark[0] == uri.spec) {
var id = bmark[5];
if (title)
@@ -268,7 +88,7 @@ const Bookmarks = Module("bookmarks", {
isBookmarked: function isBookmarked(url) {
try {
return services.get("bookmarks").getBookmarkIdsForURI(makeURI(url), {})
- .some(this._cache.isRegularBookmark);
+ .some(bookmarkcache.isRegularBookmark);
}
catch (e) {
return false;
@@ -280,7 +100,7 @@ const Bookmarks = Module("bookmarks", {
try {
let uri = util.newURI(url);
let bmarks = services.get("bookmarks").getBookmarkIdsForURI(uri, {})
- .filter(this._cache.isRegularBookmark);
+ .filter(bookmarkcache.isRegularBookmark);
bmarks.forEach(services.get("bookmarks").removeItem);
return bmarks.length;
}
@@ -350,7 +170,7 @@ const Bookmarks = Module("bookmarks", {
// format of returned array:
// [keyword, helptext, url]
getKeywords: function getKeywords() {
- return this._cache.keywords;
+ return bookmarkcache.keywords;
},
// full search string including engine name as first word in @param text
@@ -451,38 +271,53 @@ const Bookmarks = Module("bookmarks", {
"Show jumplist",
function () {
let sh = history.session;
- let list = template.jumps(sh.index, sh);
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ commandline.commandOutput(template.jumps(sh.index, sh));
},
{ argCount: "0" });
// TODO: Clean this up.
- function tags(context, args) {
- let filter = context.filter;
- let have = filter.split(",");
+ const tags = {
+ names: ["-tags", "-T"],
+ description: "A comma-separated list of tags",
+ completer: function tags(context, args) {
+ let filter = context.filter;
+ let have = filter.split(",");
- args.completeFilter = have.pop();
+ args.completeFilter = have.pop();
- let prefix = filter.substr(0, filter.length - args.completeFilter.length);
- let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(this._cache.bookmarks))]));
+ let prefix = filter.substr(0, filter.length - args.completeFilter.length);
+ let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(bookmarkcache.bookmarks))]));
- return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)];
- }
+ return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)];
+ },
+ type: CommandOption.LIST
+ };
- function title(context, args) {
- if (!args.bang)
- return [[content.document.title, "Current Page Title"]];
- context.keys.text = "title";
- context.keys.description = "url";
- return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
- }
+ const title = {
+ names: ["-title", "-t"],
+ description: "Bookmark page title or description",
+ completer: function title(context, args) {
+ if (!args.bang)
+ return [[content.document.title, "Current Page Title"]];
+ context.keys.text = "title";
+ context.keys.description = "url";
+ return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
+ },
+ type: CommandOption.STRING
+ };
- function keyword(context, args) {
- if (!args.bang)
- return [];
- context.keys.text = "keyword";
- return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
- }
+ const keyword = {
+ names: ["-keyword", "-k"],
+ description: "Keyword by which this bookmark may be opened (:open {keyword})",
+ completer: function keyword(context, args) {
+ if (!args.bang)
+ return [];
+ context.keys.text = "keyword";
+ return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
+ },
+ type: CommandOption.STRING,
+ validator: function (arg) /^\S+$/.test(arg)
+ };
commands.add(["bma[rk]"],
"Add a bookmark",
@@ -503,14 +338,13 @@ const Bookmarks = Module("bookmarks", {
bang: true,
completer: function (context, args) {
if (!args.bang) {
+ context.title = ["Page URL"];
context.completions = [[content.document.documentURI, "Current Location"]];
return;
}
completion.bookmark(context, args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
},
- options: [[["-title", "-t"], commands.OPTION_STRING, null, title],
- [["-tags", "-T"], commands.OPTION_LIST, null, tags],
- [["-keyword", "-k"], commands.OPTION_STRING, function (arg) /\w/.test(arg)]]
+ options: [title, tags, keyword]
});
commands.add(["bmarks"],
@@ -525,22 +359,26 @@ const Bookmarks = Module("bookmarks", {
context.filter = args.join(" ");
completion.bookmark(context, args["-tags"]);
},
- options: [[["-tags", "-T"], commands.OPTION_LIST, null, tags],
- [["-max", "-m"], commands.OPTION_INT]]
+ options: [tags,
+ {
+ names: ["-max", "-m"],
+ description: "The maximum number of items to list or open",
+ type: CommandOption.INT
+ }
+ ]
});
commands.add(["delbm[arks]"],
"Delete a bookmark",
function (args) {
- if (args.bang) {
+ if (args.bang)
commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i)) {
- bookmarks._cache.bookmarks.forEach(function (bmark) { services.get("bookmarks").removeItem(bmark.id); });
+ bookmarkcache.bookmarks.forEach(function (bmark) { services.get("bookmarks").removeItem(bmark.id); });
dactyl.echomsg("All bookmarks deleted", 1, commandline.FORCE_SINGLELINE);
}
});
- }
else {
let url = args.string || buffer.URL;
let deletedCount = bookmarks.remove(url);
@@ -621,9 +459,7 @@ const Bookmarks = Module("bookmarks", {
if (v)
context.filters.push(function (item) this._match(v, item[k]));
}
- // Need to make a copy because set completions() checks instanceof Array,
- // and this may be an Array from another window.
- context.completions = Array.slice(storage["bookmark-cache"].bookmarks);
+ context.completions = bookmarkcache.bookmarks;
completion.urls(context, tags);
};
diff --git a/common/content/buffer.js b/common/content/buffer.js
index cb50f16f..ed050a00 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -8,8 +8,6 @@
/** @scope modules */
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", modules);
-
const Point = Struct("x", "y");
/**
@@ -19,8 +17,6 @@ const Point = Struct("x", "y");
* @instance buffer
*/
const Buffer = Module("buffer", {
- requires: ["config", "util"],
-
init: function () {
this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {};
@@ -146,10 +142,6 @@ const Buffer = Module("buffer", {
},
destroy: function () {
- try {
- config.browser.removeProgressListener(this.progressListener);
- }
- catch (e) {} // Why? --djk
},
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc) {
@@ -214,14 +206,10 @@ const Buffer = Module("buffer", {
/**
* @property {Object} The document loading progress listener.
*/
- progressListener: {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIWebProgressListener,
- Ci.nsIXULBrowserWindow
- ]),
-
+ progressListener: update({ __proto__: window.XULBrowserWindow }, {
// XXX: function may later be needed to detect a canceled synchronous openURL()
- onStateChange: function (webProgress, request, flags, status) {
+ onStateChange: function onStateChange(webProgress, request, flags, status) {
+ onStateChange.superapply(this, arguments);
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
// receive statechange events for loading images and other parts of the web page
if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
@@ -247,7 +235,8 @@ const Buffer = Module("buffer", {
}
},
// for notifying the user about secure web pages
- onSecurityChange: function (webProgress, request, state) {
+ onSecurityChange: function onSecurityChange(webProgress, request, state) {
+ onSecurityChange.superapply(this, arguments);
// TODO: do something useful with STATE_SECURE_MED and STATE_SECURE_LOW
if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
statusline.setClass("insecure");
@@ -258,14 +247,17 @@ const Buffer = Module("buffer", {
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
statusline.setClass("secure");
},
- onStatusChange: function (webProgress, request, status, message) {
+ onStatusChange: function onStatusChange(webProgress, request, status, message) {
+ onStatusChange.superapply(this, arguments);
statusline.updateUrl(message);
},
- onProgressChange: function (webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
+ onProgressChange: function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
+ onProgressChange.superapply(this, arguments);
statusline.updateProgress(curTotalProgress/maxTotalProgress);
},
// happens when the users switches tabs
- onLocationChange: function () {
+ onLocationChange: function onLocationChange() {
+ onLocationChange.superapply(this, arguments);
statusline.updateUrl();
statusline.updateProgress();
@@ -279,10 +271,12 @@ const Buffer = Module("buffer", {
}, 500);
},
// called at the very end of a page load
- asyncUpdateUI: function () {
+ asyncUpdateUI: function asyncUpdateUI() {
+ asyncUpdateUI.superapply(this, arguments);
setTimeout(function () { statusline.updateUrl(); }, 100);
},
- setOverLink: function (link, b) {
+ setOverLink: function setOverLink(link, b) {
+ setOverLink.superapply(this, arguments);
let ssli = options["showstatuslinks"];
if (link && ssli) {
if (ssli == 1)
@@ -298,15 +292,7 @@ const Buffer = Module("buffer", {
modes.show();
}
},
-
- // nsIXULBrowserWindow stubs
- setJSDefaultStatus: function (status) {},
- setJSStatus: function (status) {},
-
- // Stub for something else, presumably. Not in any documented
- // interface.
- onLinkIconAvailable: function () {}
- },
+ }),
/**
* @property {Array} The alternative style sheets for the current
@@ -321,19 +307,6 @@ const Buffer = Module("buffer", {
},
/**
- * @property {Array[Window]} All frames in the current buffer.
- */
- get allFrames() {
- let frames = [];
- (function (frame) {
- if (frame.document.body instanceof HTMLBodyElement)
- frames.push(frame);
- Array.forEach(frame.frames, arguments.callee);
- })(window.content);
- return frames;
- },
-
- /**
* @property {Object} A map of page info sections to their
* content generating functions.
*/
@@ -448,6 +421,19 @@ const Buffer = Module("buffer", {
},
/**
+ * Returns a list of all frames in the given current buffer.
+ */
+ allFrames: function (win) {
+ let frames = [];
+ (function rec(frame) {
+ if (frame.document.body instanceof HTMLBodyElement)
+ frames.push(frame);
+ Array.forEach(frame.frames, rec);
+ })(win || window.content);
+ return frames;
+ },
+
+ /**
* Returns the currently selected word. If the selection is
* null, it tries to guess the word that the caret is
* positioned in.
@@ -581,7 +567,7 @@ const Buffer = Module("buffer", {
let ret = followFrame(window.content);
if (!ret)
// only loop through frames if the main content didn't match
- ret = Array.some(buffer.allFrames.frames, followFrame);
+ ret = Array.some(buffer.allFrames().frames, followFrame);
if (!ret)
dactyl.beep();
@@ -805,7 +791,7 @@ const Buffer = Module("buffer", {
return;
count = Math.max(count, 1);
- let frames = buffer.allFrames;
+ let frames = buffer.allFrames();
if (frames.length == 0) // currently top is always included
return;
@@ -1367,7 +1353,11 @@ const Buffer = Module("buffer", {
};
},
events: function () {
- /*
+ try {
+ config.browser.removeProgressListener(window.XULBrowserWindow);
+ }
+ catch (e) {} // Why? --djk
+ config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
window.XULBrowserWindow = this.progressListener;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@@ -1376,12 +1366,6 @@ const Buffer = Module("buffer", {
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow)
.XULBrowserWindow = this.progressListener;
- */
-
- try {
- config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
- }
- catch (e) {} // Why? --djk
let appContent = document.getElementById("appcontent");
if (appContent) {
diff --git a/common/content/commandline.js b/common/content/commandline.js
index 974d7446..fc4c2075 100644
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -15,8 +15,6 @@
* this class when the chrome is ready.
*/
const CommandLine = Module("commandline", {
- requires: ["config", "dactyl", "modes", "services", "storage", "template", "util"],
-
init: function () {
const self = this;
@@ -95,7 +93,7 @@ const CommandLine = Module("commandline", {
////////////////////// TIMERS //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
- this._statusTimer = new Timer(5, 100, function statusTell() {
+ this._statusTimer = Timer(5, 100, function statusTell() {
if (self._completions == null)
return;
if (self._completions.selected == null)
@@ -104,7 +102,7 @@ const CommandLine = Module("commandline", {
statusline.updateProgress("match " + (self._completions.selected + 1) + " of " + self._completions.items.length);
});
- this._autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) {
+ this._autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) {
self._completions.complete(true, false);
self._completions.itemList.show();
@@ -114,8 +112,8 @@ const CommandLine = Module("commandline", {
// This timer just prevents <Tab>s from queueing up when the
// system is under load (and, thus, giving us several minutes of
// the completion list scrolling). Multiple <Tab> presses are
- // still processed normally, as the time is flushed on "keyup".
- this._tabTimer = new Timer(0, 0, function tabTell(event) {
+ // still processed normally, as the timer is flushed on "keyup".
+ this._tabTimer = Timer(0, 0, function tabTell(event) {
if (self._completions)
self._completions.tab(event.shiftKey);
});
@@ -464,6 +462,18 @@ const CommandLine = Module("commandline", {
},
/**
+ * Displays the multi-line output of a command, preceded by the last
+ * executed ex command string.
+ *
+ * @param {XML} xml The output as an E4X XML object.
+ */
+ commandOutput: function (xml) {
+ XML.ignoreWhitespace = false;
+ XML.prettyPrinting = false;
+ this.echo(<>:{this.command}<br/>{xml}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
+ },
+
+ /**
* Hides the command line, and shows any status messages that
* are under it.
*/
@@ -492,7 +502,7 @@ const CommandLine = Module("commandline", {
* commandline.FORCE_MULTILINE - Forces the message to appear in
* the MOW.
*/
- echo: function echo(str, highlightGroup, flags) {
+ echo: requiresMainThread(function echo(str, highlightGroup, flags) {
// dactyl.echo uses different order of flags as it omits the highlight group, change commandline.echo argument order? --mst
if (this._silent)
return;
@@ -506,37 +516,34 @@ const CommandLine = Module("commandline", {
services.get("windowWatcher").activeWindow.dactyl)
return;
- // The DOM isn't threadsafe. It must only be accessed from the main thread.
- util.callInMainThread(function () {
- if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed)
- return;
+ if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed)
+ return;
- let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
- let action = this._echoLine;
+ let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
+ let action = this._echoLine;
- // TODO: this is all a bit convoluted - clean up.
- // assume that FORCE_MULTILINE output is fully styled
- if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this.widgets.message.value == this._lastEcho)) {
- highlightGroup += " Message";
- action = this._echoMultiline;
- }
+ // TODO: this is all a bit convoluted - clean up.
+ // assume that FORCE_MULTILINE output is fully styled
+ if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this.widgets.message.value == this._lastEcho)) {
+ highlightGroup += " Message";
+ action = this._echoMultiline;
+ }
- if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE))
- action = this._echoMultiline;
+ if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE))
+ action = this._echoMultiline;
- if (single)
- this._lastEcho = null;
- else {
- if (this.widgets.message.value == this._lastEcho)
- this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>,
- this.widgets.message.getAttributeNS(NS.uri, "highlight"));
- this._lastEcho = (action == this._echoLine) && str;
- }
+ if (single)
+ this._lastEcho = null;
+ else {
+ if (this.widgets.message.value == this._lastEcho)
+ this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>,
+ this.widgets.message.getAttributeNS(NS.uri, "highlight"));
+ this._lastEcho = (action == this._echoLine) && str;
+ }
- if (action)
- action.call(this, str, highlightGroup, single);
- }, this);
- },
+ if (action)
+ action.call(this, str, highlightGroup, single);
+ }),
/**
* Prompt the user. Sets modes.main to COMMAND_LINE, which the user may
@@ -675,7 +682,7 @@ const CommandLine = Module("commandline", {
modes.pop();
}
}
- else { // any other key
+ else {
//this.resetCompletions();
}
// allow this event to be handled by the host app
@@ -1097,7 +1104,7 @@ const CommandLine = Module("commandline", {
}
let hist = this.store.get(this.index);
- // user pressed DOWN when there is no newer this._history item
+ // user pressed DOWN when there is no newer history item
if (!hist)
hist = this.original;
else
@@ -1165,13 +1172,6 @@ const CommandLine = Module("commandline", {
get wildtype() this.wildtypes[this.wildIndex] || "",
- get type() ({
- list: this.wildmode.checkHas(this.wildtype, "list"),
- longest: this.wildmode.checkHas(this.wildtype, "longest"),
- first: this.wildmode.checkHas(this.wildtype, ""),
- full: this.wildmode.checkHas(this.wildtype, "full")
- }),
-
complete: function complete(show, tabPressed) {
this.context.reset();
this.context.tabPressed = tabPressed;
@@ -1181,6 +1181,9 @@ const CommandLine = Module("commandline", {
this.wildIndex = 0;
},
+ haveType: function (type)
+ this.wildmode.checkHas(this.wildtype, type == "first" ? "" : type),
+
preview: function preview() {
this.previewClear();
if (this.wildIndex < 0 || this.suffix || !this.items.length)
@@ -1364,7 +1367,7 @@ const CommandLine = Module("commandline", {
break;
}
- if (this.type.list)
+ if (this.haveType("list"))
this.itemList.show();
this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1);
@@ -1549,17 +1552,14 @@ const ItemList = Class("ItemList", {
this._completionElements = [];
var iframe = document.getElementById(id);
- if (!iframe) {
- dactyl.log("No iframe with id: " + id + " found, strange things may happen!"); // "The truth is out there..." -- djk
- return; // XXX
- }
this._doc = iframe.contentDocument;
+ this._win = iframe.contentWindow;
this._container = iframe.parentNode;
this._doc.body.id = id + "-content";
this._doc.body.appendChild(this._doc.createTextNode(""));
- this._doc.body.style.borderTop = "1px solid black"; // FIXME: For cases where this._completions/MOW are shown at once, or ls=0. Should use :highlight.
+ this._doc.body.style.borderTop = "1px solid black"; // FIXME: For cases where completions/MOW are shown at once, or ls=0. Should use :highlight.
this._gradient = template.gradient("GradientLeft", "GradientRight");
@@ -1578,7 +1578,8 @@ const ItemList = Class("ItemList", {
if (this._container.collapsed)
this._div.style.minWidth = document.getElementById("dactyl-commandline").scrollWidth + "px";
- this._minHeight = Math.max(this._minHeight, this._divNodes.completions.getBoundingClientRect().bottom);
+ this._minHeight = Math.max(this._minHeight,
+ this._win.scrollY + this._divNodes.completions.getBoundingClientRect().bottom);
this._container.height = this._minHeight;
if (this._container.collapsed)
diff --git a/common/content/commands.js b/common/content/commands.js
index 1cda7784..cb7cc3ae 100644
--- a/common/content/commands.js
+++ b/common/content/commands.js
@@ -12,6 +12,75 @@
// commands.add() instead
/**
+ * A structure representing the options available for a command.
+ *
+ * @property {[string]} names An array of option names. The first name
+ * is the canonical option name.
+ * @property {number} type The option's value type. This is one of:
+ * (@link CommandOption.NOARG),
+ * (@link CommandOption.STRING),
+ * (@link CommandOption.BOOL),
+ * (@link CommandOption.INT),
+ * (@link CommandOption.FLOAT),
+ * (@link CommandOption.LIST),
+ * (@link CommandOption.ANY)
+ * @property {function} validator A validator function
+ * @property {function (CompletionContext, object)} completer A list of
+ * completions, or a completion function which will be passed a
+ * {@link CompletionContext} and an object like that returned by
+ * {@link commands.parseArgs} with the following additional keys:
+ * completeOpt - The name of the option currently being completed.
+ * @property {boolean} multiple Whether this option can be specified multiple times
+ * @property {string} description A description of the option
+ */
+const CommandOption = Struct("names", "type", "validator", "completer", "multiple", "description");
+CommandOption.defaultValue("description", function () "");
+CommandOption.defaultValue("type", function () CommandOption.NOARG);
+CommandOption.defaultValue("multiple", function () false);
+update(CommandOption, {
+ // FIXME: remove later, when our option handler is better
+ /**
+ * @property {number} The option argument is unspecified. Any argument
+ * is accepted and caller is responsible for parsing the return
+ * value.
+ * @final
+ */
+ ANY: 0,
+
+ /**
+ * @property {number} The option doesn't accept an argument.
+ * @final
+ */
+ NOARG: 1,
+ /**
+ * @property {number} The option accepts a boolean argument.
+ * @final
+ */
+ BOOL: 2,
+ /**
+ * @property {number} The option accepts a string argument.
+ * @final
+ */
+ STRING: 3,
+ /**
+ * @property {number} The option accepts an integer argument.
+ * @final
+ */
+ INT: 4,
+ /**
+ * @property {number} The option accepts a float argument.
+ * @final
+ */
+ FLOAT: 5,
+ /**
+ * @property {number} The option accepts a string list argument.
+ * E.g. "foo,bar"
+ * @final
+ */
+ LIST: 6
+});
+
+/**
* A class representing Ex commands. Instances are created by
* the {@link Commands} class.
*
@@ -35,8 +104,6 @@
* @private
*/
const Command = Class("Command", {
- requires: ["config"],
-
init: function (specs, description, action, extraInfo) {
specs = Array.concat(specs); // XXX
let parsedSpecs = Command.parseSpecs(specs);
@@ -51,6 +118,8 @@ const Command = Class("Command", {
if (extraInfo)
update(this, extraInfo);
+ if (this.options)
+ this.options = this.options.map(CommandOption.fromArray, CommandOption);
},
/**
@@ -191,7 +260,7 @@ const Command = Class("Command", {
* invocation which should be restored on subsequent @dactyl
* startups.
*/
- serial: null,
+ serialize: null,
/**
* @property {boolean} When true, invocations of this command
* may contain private data which should be purged from
@@ -240,47 +309,6 @@ const Commands = Module("commands", {
this._exMap = {};
},
- // FIXME: remove later, when our option handler is better
- /**
- * @property {number} The option argument is unspecified. Any argument
- * is accepted and caller is responsible for parsing the return
- * value.
- * @final
- */
- OPTION_ANY: 0,
-
- /**
- * @property {number} The option doesn't accept an argument.
- * @final
- */
- OPTION_NOARG: 1,
- /**
- * @property {number} The option accepts a boolean argument.
- * @final
- */
- OPTION_BOOL: 2,
- /**
- * @property {number} The option accepts a string argument.
- * @final
- */
- OPTION_STRING: 3,
- /**
- * @property {number} The option accepts an integer argument.
- * @final
- */
- OPTION_INT: 4,
- /**
- * @property {number} The option accepts a float argument.
- * @final
- */
- OPTION_FLOAT: 5,
- /**
- * @property {number} The option accepts a string list argument.
- * E.g. "foo,bar"
- * @final
- */
- OPTION_LIST: 6,
-
/**
* @property Indicates that no count was specified for this
* command invocation.
@@ -442,29 +470,8 @@ const Commands = Module("commands", {
*
* @param {string} str The Ex command-line string to parse. E.g.
* "-x=foo -opt=bar arg1 arg2"
- * @param {Array} options The options accepted. These are specified as
- * an array [names, type, validator, completions, multiple].
- * names - an array of option names. The first name is the
- * canonical option name.
- * type - the option's value type. This is one of:
- * (@link Commands#OPTION_NOARG),
- * (@link Commands#OPTION_STRING),
- * (@link Commands#OPTION_BOOL),
- * (@link Commands#OPTION_INT),
- * (@link Commands#OPTION_FLOAT),
- * (@link Commands#OPTION_LIST),
- * (@link Commands#OPTION_ANY)
- * validator - a validator function
- * completer - a list of completions, or a completion function
- * multiple - whether this option can be specified multiple times
- * E.g.
- * options = [[["-force"], OPTION_NOARG],
- * [["-fullscreen", "-f"], OPTION_BOOL],
- * [["-language"], OPTION_STRING, validateFunc, ["perl", "ruby"]],
- * [["-speed"], OPTION_INT],
- * [["-acceleration"], OPTION_FLOAT],
- * [["-accessories"], OPTION_LIST, null, ["foo", "bar"], true],
- * [["-other"], OPTION_ANY]];
+ * @param {[CommandOption]} options The options accepted. These are specified
+ * as an array of {@link CommandOption} structures.
* @param {string} argCount The number of arguments accepted.
* "0": no arguments
* "1": exactly one argument
@@ -518,7 +525,7 @@ const Commands = Module("commands", {
function matchOpts(arg) {
// Push possible option matches into completions
if (complete && !onlyArgumentsRemaining)
- completeOpts = [[opt[0], opt[0][0]] for ([i, opt] in Iterator(options)) if (!(opt[0][0] in args))];
+ completeOpts = options.filter(function (opt) opt.multiple || !(opt.names[0] in args));
}
function resetCompletions() {
completeOpts = null;
@@ -562,14 +569,14 @@ const Commands = Module("commands", {
var optname = "";
if (!onlyArgumentsRemaining) {
for (let [, opt] in Iterator(options)) {
- for (let [, optname] in Iterator(opt[0])) {
+ for (let [, optname] in Iterator(opt.names)) {
if (sub.indexOf(optname) == 0) {
invalid = false;
arg = null;
quote = null;
count = 0;
let sep = sub[optname.length];
- if (sep == "=" || /\s/.test(sep) && opt[1] != this.OPTION_NOARG) {
+ if (sep == "=" || /\s/.test(sep) && opt.type != CommandOption.NOARG) {
[count, arg, quote, error] = getNextArg(sub.substr(optname.length + 1));
dactyl.assert(!error, error);
@@ -595,7 +602,7 @@ const Commands = Module("commands", {
args.completeFilter = arg;
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
}
- let type = Commands.argTypes[opt[1]];
+ let type = Commands.argTypes[opt.type];
if (type && (!complete || arg != null)) {
let orig = arg;
arg = type.parse(arg);
@@ -610,8 +617,8 @@ const Commands = Module("commands", {
}
// we have a validator function
- if (typeof opt[2] == "function") {
- if (opt[2].call(this, arg) == false) {
+ if (typeof opt.validator == "function") {
+ if (opt.validator.call(this, arg) == false) {
echoerr("Invalid argument for option: " + optname);
if (complete)
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
@@ -620,11 +627,13 @@ const Commands = Module("commands", {
}
}
+ matchOpts(sub);
+
// option allowed multiple times
- if (!!opt[4])
- args[opt[0][0]] = (args[opt[0][0]] || []).concat(arg);
+ if (opt.multiple)
+ args[opt.names[0]] = (args[opt.names[0]] || []).concat(arg);
else
- args[opt[0][0]] = opt[1] == this.OPTION_NOARG || arg;
+ args[opt.names[0]] = opt.type == this.OPTION_NOARG || arg;
i += optname.length + count;
if (i == str.length)
@@ -683,17 +692,18 @@ const Commands = Module("commands", {
if (complete) {
if (args.completeOpt) {
let opt = args.completeOpt;
- let context = complete.fork(opt[0][0], args.completeStart);
+ let context = complete.fork(opt.names[0], args.completeStart);
context.filter = args.completeFilter;
- if (typeof opt[3] == "function")
- var compl = opt[3](context, args);
+ if (typeof opt.completer == "function")
+ var compl = opt.completer(context, args);
else
- compl = opt[3] || [];
- context.title = [opt[0][0]];
+ compl = opt.completer || [];
+ context.title = [opt.names[0]];
context.quote = args.quote;
context.completions = compl;
}
complete.advance(args.completeStart);
+ complete.keys = { text: "names", description: "description" };
complete.title = ["Options"];
if (completeOpts)
complete.completions = completeOpts;
@@ -1012,18 +1022,16 @@ const Commands = Module("commands", {
// : No, array comprehensions are fine, generator statements aren't. --Kris
let cmds = this._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd)));
- if (cmds.length > 0) {
- let str = template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"],
- ([cmd.bang ? "!" : " ",
- cmd.name,
- cmd.argCount,
- cmd.count ? "0c" : "",
- completerToString(cmd.completer),
- cmd.replacementText || "function () { ... }"]
- for ([, cmd] in Iterator(cmds))));
-
- commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- }
+ if (cmds.length > 0)
+ commandline.commandOutput(
+ template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"]),
+ ([cmd.bang ? "!" : " ",
+ cmd.name,
+ cmd.argCount,
+ cmd.count ? "0c" : "",
+ completerToString(cmd.completer),
+ cmd.replacementText || "function () { ... }"]
+ for ([, cmd] in Iterator(cmds))));
else
dactyl.echomsg("No user-defined commands found");
}
@@ -1036,23 +1044,33 @@ const Commands = Module("commands", {
completion.ex(context);
},
options: [
- [["-nargs"], commands.OPTION_STRING,
- function (arg) /^[01*?+]$/.test(arg),
- [["0", "No arguments are allowed (default)"],
- ["1", "One argument is allowed"],
- ["*", "Zero or more arguments are allowed"],
- ["?", "Zero or one argument is allowed"],
- ["+", "One or more arguments is allowed"]]],
- [["-bang"], commands.OPTION_NOARG],
- [["-count"], commands.OPTION_NOARG],
- [["-description"], commands.OPTION_STRING],
- // TODO: "E180: invalid complete value: " + arg
- [["-complete"], commands.OPTION_STRING,
- function (arg) arg in completeOptionMap || /custom,\w+/.test(arg),
- function (context) [[k, ""] for ([k, v] in Iterator(completeOptionMap))]]
+ { names: ["-bang"], description: "Command may be proceeded by a !" },
+ { names: ["-count"], description: "Command may be preceeded by a count" },
+ {
+ names: ["-description"],
+ description: "A user-visible description of the command",
+ type: CommandOption.STRING
+ }, {
+ // TODO: "E180: invalid complete value: " + arg
+ names: ["-complete"],
+ description: "The argument completion function",
+ completer: function (context) [[k, ""] for ([k, v] in Iterator(completeOptionMap))],
+ type: CommandOption.STRING,
+ validator: function (arg) arg in completeOptionMap || /custom,\w+/.test(arg),
+ }, {
+ names: ["-nargs"],
+ description: "The allowed number of arguments",
+ completer: [["0", "No arguments are allowed (default)"],
+ ["1", "One argument is allowed"],
+ ["*", "Zero or more arguments are allowed"],
+ ["?", "Zero or one argument is allowed"],
+ ["+", "One or more arguments are allowed"]],
+ type: CommandOption.STRING,
+ validator: function (arg) /^[01*?+]$/.test(arg)
+ },
],
literal: 1,
- serial: function () [ {
+ serialize: function () [ {
command: this.name,
bang: true,
options: util.Array.toObject(
diff --git a/common/content/completion.js b/common/content/completion.js
index 9a9268a0..1434b38e 100755
--- a/common/content/completion.js
+++ b/common/content/completion.js
@@ -297,7 +297,7 @@ const CompletionContext = Class("CompletionContext", {
let [k, v] = i;
let _k = "_" + k;
if (typeof v == "string" && /^[.[]/.test(v))
- v = eval("(function (i) i" + v + ")");
+ v = Function("i", "return i" + v);
if (typeof v == "function")
res.__defineGetter__(k, function () _k in this ? this[_k] : (this[_k] = v(this.item)));
else
@@ -325,7 +325,7 @@ const CompletionContext = Class("CompletionContext", {
let lock = {};
this.cache.backgroundLock = lock;
this.incomplete = true;
- let thread = this.getCache("backgroundThread", dactyl.newThread);
+ let thread = this.getCache("backgroundThread", util.newThread);
util.callAsync(thread, this, function () {
if (this.cache.backgroundLock != lock)
return;
@@ -510,7 +510,7 @@ const CompletionContext = Class("CompletionContext", {
if (!context.autoComplete && !context.tabPressed && context.editor)
context.waitingForTab = true;
else if (completer)
- return completer.apply(self || this, [context].concat(Array.slice(arguments, arguments.callee.length)));
+ return completer.apply(self || this, [context].concat(Array.slice(arguments, fork.length)));
if (completer)
return null;
@@ -668,14 +668,13 @@ const Completion = Module("completion", {
context = context.contexts["/list"];
context.wait();
- let list = template.commandOutput(commandline.command,
+ commandline.commandOutput(
<div highlight="Completions">
{ template.map(context.contextList.filter(function (c) c.hasItems),
function (context)
template.completionRow(context.title, "CompTitle") +
template.map(context.items, function (item) context.createRow(item), null, 100)) }
</div>);
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
////////////////////////////////////////////////////////////////////////////////
@@ -765,13 +764,11 @@ const Completion = Module("completion", {
commands.add(["contexts"],
"List the completion contexts used during completion of an ex command",
function (args) {
- commandline.echo(template.commandOutput(commandline.command,
+ commandline.commandOutput(
<div highlight="Completions">
{ template.completionRow(["Context", "Title"], "CompTitle") }
{ template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
- </div>),
- null, commandline.FORCE_MULTILINE);
-
+ </div>);
},
{
argCount: "1",
diff --git a/common/content/configbase.js b/common/content/configbase.js
index 9005f122..5fb75d29 100644
--- a/common/content/configbase.js
+++ b/common/content/configbase.js
@@ -229,13 +229,13 @@ const ConfigBase = Class(ModuleBase, {
HelpOptionalArg color: #6A97D4;
HelpBody display: block; margin: 1em auto; max-width: 100ex;
- HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
+ HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
HelpCode display: block; white-space: pre; margin-left: 2em; font-family: Terminus, Fixed, monospace;
HelpDefault margin-right: 1ex; white-space: pre;
HelpDescription display: block;
- HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
+ HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
HelpEx display: inline-block; color: #527BBD; font-weight: bold;
@@ -250,21 +250,21 @@ const ConfigBase = Class(ModuleBase, {
HelpKey color: #102663;
- HelpLink,html|a,dactyl://help/* text-decoration: none;
+ HelpLink,html|a,dactyl://help/* text-decoration: none;
HelpLink[href]:hover text-decoration: underline;
- HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
- HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
- HelpListItem,html|li,dactyl://help/* display: list-item;
+ HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
+ HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
+ HelpListItem,html|li,dactyl://help/* display: list-item;
HelpNote color: red; font-weight: bold;
HelpOpt color: #106326;
HelpOptInfo display: inline-block; margin-bottom: 1ex;
- HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
+ HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
HelpParagraph:first-child margin-top: 0;
- HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD;
+ HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 2em;
HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
HelpString::before content: '"';
diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js
index 32f23e9a..af0cc3be 100644
--- a/common/content/dactyl-overlay.js
+++ b/common/content/dactyl-overlay.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2008-2008 Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2010 Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
@@ -11,55 +11,61 @@
modules.modules = modules;
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Components.interfaces.mozIJSSubScriptLoader);
- function load(script) {
+ .getService(Components.interfaces.mozIJSSubScriptLoader);
+
+ modules.load = function load(script) {
for (let [i, base] in Iterator(prefix)) {
try {
- loader.loadSubScript(base + script, modules);
+ loader.loadSubScript(base + script + ".js", modules);
return;
}
catch (e) {
- if (i + 1 < prefix.length)
- continue;
- if (Components.utils.reportError)
- Components.utils.reportError(e);
- dump("dactyl: Loading script " + script + ": " + e + "\n");
- dump(e.stack + "\n");
+ if (e !== "Error opening input stream (invalid filename?)")
+ dump("dactyl: Trying: " + (base + script + ".js") + ": " + e + "\n" + e.stack);
}
}
- }
+ try {
+ Components.utils.import("resource://dactyl/" + script + ".jsm", modules);
+ }
+ catch (e) {
+ dump("dactyl: Loading script " + script + ": " + e.result + " " + e + "\n");
+ dump(Error().stack + "\n");
+ }
+ };
let prefix = [BASE];
- ["base.js",
- "modules.js",
- "autocommands.js",
- "buffer.js",
- "commandline.js",
- "commands.js",
- "completion.js",
- "configbase.js",
- "config.js",
- "dactyl.js",
- "editor.js",
- "events.js",
- "finder.js",
- "hints.js",
- "io.js",
- "javascript.js",
- "mappings.js",
- "marks.js",
- "modes.js",
- "options.js",
- "services.js",
- "statusline.js",
- "style.js",
- "template.js",
- "util.js",
- ].forEach(load);
+ ["base",
+ "modules",
+ "storage",
+ "util",
+ "autocommands",
+ "buffer",
+ "commandline",
+ "commands",
+ "completion",
+ "configbase",
+ "config",
+ "dactyl",
+ "editor",
+ "events",
+ "finder",
+ "highlight",
+ "hints",
+ "io",
+ "javascript",
+ "mappings",
+ "marks",
+ "modes",
+ "options",
+ "services",
+ "statusline",
+ "styles",
+ "template",
+ ].forEach(modules.load);
prefix.unshift("chrome://" + modules.Config.prototype.name.toLowerCase() + "/content/");
- modules.Config.prototype.scripts.forEach(load);
+ modules.Config.prototype.scripts.forEach(modules.load);
})();
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
index 320d6304..b56548b1 100644
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -8,7 +8,9 @@
/** @scope modules */
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", modules);
+default xml namespace = XHTML;
+XML.ignoreWhitespace = false;
+XML.prettyPrinting = false;
const plugins = { __proto__: modules };
const userContext = { __proto__: modules };
@@ -17,27 +19,6 @@ const EVAL_ERROR = "__dactyl_eval_error";
const EVAL_RESULT = "__dactyl_eval_result";
const EVAL_STRING = "__dactyl_eval_string";
-// Move elsewhere?
-const Storage = Module("storage", {
- requires: ["services"],
-
- init: function () {
- Components.utils.import("resource://dactyl/storage.jsm", this);
- modules.Timer = this.Timer; // Fix me, please.
-
- try {
- let infoPath = services.create("file");
- infoPath.initWithPath(File.expandPath(IO.runtimePath.replace(/,.*/, "")));
- infoPath.append("info");
- infoPath.append(dactyl.profileName);
- this.storage.infoPath = infoPath;
- }
- catch (e) {}
-
- return this.storage;
- }
-});
-
const FailedAssertion = Class("FailedAssertion", Error, {
init: function (message) {
this.message = message;
@@ -45,8 +26,6 @@ const FailedAssertion = Class("FailedAssertion", Error, {
});
const Dactyl = Module("dactyl", {
- requires: ["config", "services"],
-
init: function () {
window.dactyl = this;
window.liberator = this;
@@ -61,8 +40,6 @@ const Dactyl = Module("dactyl", {
// without explicitly selecting a profile.
/** @property {string} The name of the current user profile. */
this.profileName = services.get("directory").get("ProfD", Ci.nsIFile).leafName.replace(/^.+?\./, "");
-
- config.features.push(Dactyl.getPlatformFeature());
},
destroy: function () {
@@ -139,29 +116,26 @@ const Dactyl = Module("dactyl", {
* bell may be either audible or visual depending on the value of the
* 'visualbell' option.
*/
- beep: function () {
+ beep: requiresMainThread(function () {
// FIXME: popups clear the command line
if (options["visualbell"]) {
- util.callInMainThread(function () {
- // flash the visual bell
- let popup = document.getElementById("dactyl-visualbell");
- let win = config.visualbellWindow;
- let rect = win.getBoundingClientRect();
- let width = rect.right - rect.left;
- let height = rect.bottom - rect.top;
-
- // NOTE: this doesn't seem to work in FF3 with full box dimensions
- popup.openPopup(win, "overlap", 1, 1, false, false);
- popup.sizeTo(width - 2, height - 2);
- setTimeout(function () { popup.hidePopup(); }, 20);
- });
+ // flash the visual bell
+ let popup = document.getElementById("dactyl-visualbell");
+ let win = config.visualbellWindow;
+ let rect = win.getBoundingClientRect();
+ let width = rect.right - rect.left;
+ let height = rect.bottom - rect.top;
+
+ // NOTE: this doesn't seem to work in FF3 with full box dimensions
+ popup.openPopup(win, "overlap", 1, 1, false, false);
+ popup.sizeTo(width - 2, height - 2);
+ setTimeout(function () { popup.hidePopup(); }, 20);
}
else {
let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
soundService.beep();
}
- return false; // so you can do: if (...) return dactyl.beep();
- },
+ }),
/**
* Prints a message to the console. If <b>msg</b> is an object it is
@@ -217,9 +191,11 @@ const Dactyl = Module("dactyl", {
echoerr: function (str, flags) {
flags |= commandline.APPEND_TO_MESSAGES;
+ if (isinstance(str, ["Error", "Exception"]))
+ dactyl.reportError(str);
if (typeof str == "object" && "echoerr" in str)
str = str.echoerr;
- else if (str instanceof Error)
+ else if (isinstance(str, ["Error"]))
str = str.fileName + ":" + str.lineNumber + ": " + str;
if (options["errorbells"])
@@ -504,7 +480,7 @@ const Dactyl = Module("dactyl", {
// Process plugin help entries.
XML.ignoreWhiteSpace = false;
- XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil.
+ XML.prettyPrinting = false;
XML.prettyIndent = 4;
let body = XML();
@@ -594,7 +570,7 @@ const Dactyl = Module("dactyl", {
data.push(">");
if (node instanceof HTMLHeadElement)
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
- Array.map(node.childNodes, arguments.callee);
+ Array.map(node.childNodes, fix);
data.push("</"); data.push(node.localName); data.push(">");
}
break;
@@ -1105,6 +1081,9 @@ const Dactyl = Module("dactyl", {
dactyl.help(tag);
}
}, {
+ config: function () {
+ config.features.push(Dactyl.getPlatformFeature());
+ },
// Only general options are added here, which are valid for all Dactyl extensions
options: function () {
@@ -1502,30 +1481,25 @@ const Dactyl = Module("dactyl", {
extensions = extensions.filter(function (extension) extension.name.indexOf(args[0]) >= 0);
extensions.sort(function (a, b) String.localeCompare(a.name, b.name));
- if (extensions.length > 0) {
- let list = template.tabular(
- ["Name", "Version", "Status", "Description"], [],
- ([template.icon({ icon: e.iconURL }, e.name),
- e.version,
- (e.isActive ? <span highlight="Enabled">enabled</span>
- : <span highlight="Disabled">disabled</span>) +
- ((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
- <>&#xa0;({e.userDisabled || e.appDisabled
- ? <span highlight="Disabled">disabled</span>
- : <span highlight="Enabled">enabled</span>}
- on restart)
- </>),
- e.description] for ([, e] in Iterator(extensions)))
- );
-
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- }
- else {
- if (filter)
- dactyl.echoerr("Exxx: No extension matching " + filter.quote());
- else
- dactyl.echoerr("No extensions installed");
- }
+ if (extensions.length > 0)
+ commandline.commandOutput(
+ template.tabular(["Name", "Version", "Status", "Description"], [],
+ ([template.icon({ icon: e.iconURL }, e.name),
+ e.version,
+ (e.isActive ? <span highlight="Enabled">enabled</span>
+ : <span highlight="Disabled">disabled</span>) +
+ ((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
+ <>&#xa0;({e.userDisabled || e.appDisabled
+ ? <span highlight="Disabled">disabled</span>
+ : <span highlight="Enabled">enabled</span>}
+ on restart)
+ </>),
+ e.description]
+ for ([, e] in Iterator(extensions)))));
+ else if (filter)
+ dactyl.echoerr("Exxx: No extension matching " + filter.quote());
+ else
+ dactyl.echoerr("No extensions installed");
});
},
{ argCount: "?" });
@@ -1696,7 +1670,7 @@ const Dactyl = Module("dactyl", {
else
totalUnits = "msec";
- let str = template.commandOutput(commandline.command,
+ commandline.commandOutput(
<table>
<tr highlight="Title" align="left">
<th colspan="3">Code execution summary</th>
@@ -1705,7 +1679,6 @@ const Dactyl = Module("dactyl", {
<tr><td>&#xa0;&#xa0;Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
<tr><td>&#xa0;&#xa0;Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
</table>);
- commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
else {
let beforeTime = Date.now();
@@ -1767,8 +1740,9 @@ const Dactyl = Module("dactyl", {
if (args.bang)
dactyl.open("about:");
else
- dactyl.echo(template.commandOutput(commandline.command,
- <>{config.name} {dactyl.version} running on:<br/>{navigator.userAgent}</>));
+ commandline.commandOutput(<>
+ {config.name} {dactyl.version} running on:<br/>{navigator.userAgent}
+ </>);
}, {
argCount: "0",
bang: true
diff --git a/common/content/editor.js b/common/content/editor.js
index ee77772f..6a2c7197 100644
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -11,8 +11,6 @@
/** @instance editor */
const Editor = Module("editor", {
- requires: ["config"],
-
init: function () {
// store our last search with f, F, t or T
//
@@ -408,10 +406,10 @@ const Editor = Module("editor", {
// blink the textbox after returning
if (textBox) {
let colors = [tmpBg, oldBg, tmpBg, oldBg];
- (function () {
+ (function next() {
textBox.style.backgroundColor = colors.shift();
if (colors.length > 0)
- setTimeout(arguments.callee, 100);
+ setTimeout(next, 100);
})();
}
@@ -577,10 +575,8 @@ const Editor = Module("editor", {
dactyl.echo(mode + " " + lhs + " " + rhs, commandline.FORCE_SINGLELINE); // 2 spaces, 3 spaces
}
- else {
- list = template.tabular(["", "LHS", "RHS"], [], list);
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- }
+ else
+ commandline.commandOutput(template.tabular(["", "LHS", "RHS"], [], list));
},
/**
@@ -670,7 +666,7 @@ const Editor = Module("editor", {
}, {
completer: function (context, args) completion.abbreviation(context, args, mode),
literal: 0,
- serial: function () [ {
+ serialize: function () [ {
command: this.name,
arguments: [lhs],
literalArg: abbr[1]
diff --git a/common/content/events.js b/common/content/events.js
index 574dd3f9..455bd0e0 100644
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -12,8 +12,6 @@
* @instance events
*/
const Events = Module("events", {
- requires: ["autocommands", "config"],
-
init: function () {
const self = this;
@@ -737,12 +735,10 @@ const Events = Module("events", {
// TODO: Merge with onFocusChange
onFocus: function (event) {
- function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
-
let elem = event.originalTarget;
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
- if (hasHTMLDocument(win) && !buffer.focusAllowed(win)
+ if (Events.isContentNode(elem) && !buffer.focusAllowed(win)
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement]))
elem.blur();
},
@@ -1107,6 +1103,13 @@ const Events = Module("events", {
editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
"month", "number", "password", "range", "search",
"tel", "text", "time", "url", "week"]),
+ isContentNode: function (node) {
+ let win = (node.ownerDocument || node).defaultView;
+ for (; win; win = win.parent != win && win.parent)
+ if (win == window.content)
+ return true;
+ return false;
+ },
isInputElemFocused: function () {
let elem = dactyl.focus;
return elem instanceof HTMLInputElement && set.has(Events.editableInputs, elem.type) ||
diff --git a/common/content/finder.js b/common/content/finder.js
index 463506ff..21650c83 100644
--- a/common/content/finder.js
+++ b/common/content/finder.js
@@ -8,8 +8,6 @@
/** @instance rangefinder */
const RangeFinder = Module("rangefinder", {
- requires: ["config"],
-
init: function () {
this.lastSearchPattern = "";
},
@@ -128,7 +126,7 @@ const RangeFinder = Module("rangefinder", {
set rangeFind(val) buffer.localStore.rangeFind = val,
/**
- * Highlights all occurances of <b>str</b> in the buffer.
+ * Highlights all occurrences of <b>str</b> in the buffer.
*
* @param {string} str The string to highlight.
*/
@@ -146,6 +144,11 @@ const RangeFinder = Module("rangefinder", {
}
}, {
}, {
+ modes: function () {
+ /* Must come before commandline. */
+ modes.addMode("FIND_FORWARD", true);
+ modes.addMode("FIND_BACKWARD", true);
+ },
commandline: function () {
// Event handlers for search - closure is needed
commandline.registerCallback("change", modes.FIND_FORWARD, this.closure.onKeyPress);
@@ -197,10 +200,6 @@ const RangeFinder = Module("rangefinder", {
});
},
- modes: function () {
- modes.addMode("FIND_FORWARD", true);
- modes.addMode("FIND_BACKWARD", true);
- },
options: function () {
options.safeSetPref("accessibility.typeaheadfind.autostart", false);
// The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187
@@ -256,8 +255,8 @@ const RangeFinder = Module("rangefinder", {
* implementation will begin searching from the position of the
* caret in the last active frame. This is contrary to the behavior
* of the builtin component, which always starts a search from the
- * begining of the first frame in the case of frameset documents,
- * and cycles through all frames from begining to end. This makes it
+ * beginning of the first frame in the case of frameset documents,
+ * and cycles through all frames from beginning to end. This makes it
* impossible to choose the starting point of a search for such
* documents, and represents a major detriment to productivity where
* large amounts of data are concerned (e.g., for API documents).
@@ -302,8 +301,8 @@ const RangeFind = Class("RangeFind", {
},
compareRanges: function (r1, r2)
- this.backward ? r1.compareBoundaryPoints(Range.END_TO_START, r2)
- : -r1.compareBoundaryPoints(Range.START_TO_END, r2),
+ this.backward ? r1.compareBoundaryPoints(r1.END_TO_START, r2)
+ : -r1.compareBoundaryPoints(r1.START_TO_END, r2),
findRange: function (range) {
let doc = range.startContainer.ownerDocument;
@@ -331,7 +330,7 @@ const RangeFind = Class("RangeFind", {
this.lastRange.commonAncestorContainer).snapshotItem(0);
if (node) {
node.focus();
- // Rehighlight collapsed selection
+ // Re-highlight collapsed selection
this.selectedRange = this.lastRange;
}
},
@@ -358,7 +357,7 @@ const RangeFind = Class("RangeFind", {
let string = this.lastString;
for (let r in this.iter(string)) {
let controller = this.range.selectionController;
- for (let node=r.startContainer; node; node=node.parentNode)
+ for (let node = r.startContainer; node; node = node.parentNode)
if (node instanceof Ci.nsIDOMNSEditableElement) {
controller = node.editor.selectionController;
break;
@@ -370,11 +369,30 @@ const RangeFind = Class("RangeFind", {
this.selections.push(sel);
}
this.highlighted = this.lastString;
- this.selectedRange = this.lastRange;
+ if (this.lastRange)
+ this.selectedRange = this.lastRange;
this.addListeners();
}
},
+ indexIter: function (private_) {
+ let idx = this.range.index;
+ if (this.backward)
+ var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
+ else
+ var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)];
+
+ for (let i in groups[0])
+ yield i;
+
+ if (!private_) {
+ this.wrapped = true;
+ this.lastRange = null;
+ for (let i in groups[1])
+ yield i;
+ }
+ },
+
iter: function (word) {
let saved = ["range", "lastRange", "lastString"].map(function (s) [s, this[s]], this);
try {
@@ -382,7 +400,7 @@ const RangeFind = Class("RangeFind", {
this.lastRange = null;
this.lastString = word;
var res;
- while ((res = this.search(null, this.reverse, true)))
+ while (res = this.search(null, this.reverse, true))
yield res;
}
finally {
@@ -504,25 +522,8 @@ const RangeFind = Class("RangeFind", {
if (word == "")
var range = this.startRange;
- else {
- function indices() {
- let idx = this.range.index;
- if (this.backward)
- var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
- else
- var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)];
-
- for (let i in groups[0])
- yield i;
-
- if (!private_) {
- this.wrapped = true;
- this.lastRange = null;
- for (let i in groups[1])
- yield i;
- }
- }
- for (let i in indices.call(this)) {
+ else
+ for (let i in this.indexIter(private_)) {
if (!private_ && this.range.window != this.ranges[i].window && this.range.window != this.ranges[i].window.parent) {
this.range.descroll();
this.range.deselect();
@@ -540,7 +541,6 @@ const RangeFind = Class("RangeFind", {
if (range)
break;
}
- }
if (range)
this.lastRange = range.cloneRange();
@@ -559,11 +559,11 @@ const RangeFind = Class("RangeFind", {
},
addListeners: function () {
- for (let range in values(this.ranges))
+ for (let range in array.itervalues(this.ranges))
range.window.addEventListener("unload", this.closure.onUnload, true);
},
purgeListeners: function () {
- for (let range in values(this.ranges))
+ for (let range in array.itervalues(this.ranges))
range.window.removeEventListener("unload", this.closure.onUnload, true);
},
onUnload: function (event) {
@@ -591,8 +591,8 @@ const RangeFind = Class("RangeFind", {
},
intersects: function (range)
- this.range.compareBoundaryPoints(Range.START_TO_END, range) >= 0 &&
- this.range.compareBoundaryPoints(Range.END_TO_START, range) <= 0,
+ this.range.compareBoundaryPoints(range.START_TO_END, range) >= 0 &&
+ this.range.compareBoundaryPoints(range.END_TO_START, range) <= 0,
save: function () {
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
@@ -625,8 +625,8 @@ const RangeFind = Class("RangeFind", {
}),
contains: function (range, r)
- range.compareBoundaryPoints(Range.START_TO_END, r) >= 0 &&
- range.compareBoundaryPoints(Range.END_TO_START, r) <= 0,
+ range.compareBoundaryPoints(range.START_TO_END, r) >= 0 &&
+ range.compareBoundaryPoints(range.END_TO_START, r) <= 0,
endpoint: function (range, before) {
range = range.cloneRange();
range.collapse(before);
@@ -634,7 +634,7 @@ const RangeFind = Class("RangeFind", {
},
equal: function (r1, r2) {
try {
- return !r1.compareBoundaryPoints(Range.START_TO_START, r2) && !r1.compareBoundaryPoints(Range.END_TO_END, r2)
+ return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2)
}
catch (e) {}
return false;
diff --git a/common/content/hints.js b/common/content/hints.js
index 4ffe9ca7..2c6b8efc 100644
--- a/common/content/hints.js
+++ b/common/content/hints.js
@@ -10,8 +10,6 @@
/** @instance hints */
const Hints = Module("hints", {
- requires: ["config"],
-
init: function () {
this._hintMode = null;
diff --git a/common/content/history.js b/common/content/history.js
index abed5893..d8962567 100644
--- a/common/content/history.js
+++ b/common/content/history.js
@@ -7,8 +7,6 @@
"use strict";
const History = Module("history", {
- requires: ["config"],
-
get format() bookmarks.format,
get service() services.get("history"),
@@ -196,7 +194,7 @@ const History = Module("history", {
bang: true,
completer: function (context) { context.quote = null; completion.history(context); },
// completer: function (filter) completion.history(filter)
- options: [[["-max", "-m"], commands.OPTION_INT]]
+ options: [{ names: ["-max", "-m"], description: "The maximum number of items to list", type: CommandOption.INT }]
});
},
completion: function () {
diff --git a/common/content/io.js b/common/content/io.js
index 51a00e37..ebe86d6a 100755
--- a/common/content/io.js
+++ b/common/content/io.js
@@ -10,311 +10,27 @@
/** @scope modules */
plugins.contexts = {};
-const Script = Class("Script", {
- init: function (file) {
- let self = plugins.contexts[file.path];
- if (self) {
- if (self.onUnload)
- self.onUnload();
- return self;
- }
- plugins.contexts[file.path] = this;
- this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
- this.PATH = file.path;
- this.toString = this.toString;
- this.__context__ = this;
- this.__proto__ = plugins;
-
- // This belongs elsewhere
- for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
- if (dir.contains(file, false))
- plugins[this.NAME] = this;
- }
- return this;
- }
-});
-
-/**
- * @class File A class to wrap nsIFile objects and simplify operations
- * thereon.
- *
- * @param {nsIFile|string} path Expanded according to {@link IO#expandPath}
- * @param {boolean} checkPWD Whether to allow expansion relative to the
- * current directory. @default true
- */
-const File = Class("File", {
- init: function (path, checkPWD) {
- if (arguments.length < 2)
- checkPWD = true;
-
- let file = services.create("file");
-
- if (path instanceof Ci.nsIFile)
- file = path;
- else if (/file:\/\//.test(path))
- file = services.create("file:").getFileFromURLSpec(path);
- else {
- let expandedPath = File.expandPath(path);
-
- if (!File.isAbsolutePath(expandedPath) && checkPWD)
- file = File.joinPaths(io.getCurrentDirectory().path, expandedPath);
- else
- file.initWithPath(expandedPath);
- }
- let self = XPCSafeJSObjectWrapper(file);
- self.__proto__ = File.prototype;
+function Script(file) {
+ let self = plugins.contexts[file.path];
+ if (self) {
+ if (self.onUnload)
+ self.onUnload();
return self;
- },
-
- /**
- * Iterates over the objects in this directory.
- */
- iterDirectory: function () {
- if (!this.isDirectory())
- throw Error("Not a directory");
- let entries = this.directoryEntries;
- while (entries.hasMoreElements())
- yield File(entries.getNext().QueryInterface(Ci.nsIFile));
- },
- /**
- * Returns the list of files in this directory.
- *
- * @param {boolean} sort Whether to sort the returned directory
- * entries.
- * @returns {nsIFile[]}
- */
- readDirectory: function (sort) {
- if (!this.isDirectory())
- throw Error("Not a directory");
-
- let array = [e for (e in this.iterDirectory())];
- if (sort)
- array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
- return array;
- },
-
- /**
- * Reads this file's entire contents in "text" mode and returns the
- * content as a string.
- *
- * @param {string} encoding The encoding from which to decode the file.
- * @default options["fileencoding"]
- * @returns {string}
- */
- read: function (encoding) {
- let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
- let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
-
- if (!encoding)
- encoding = options["fileencoding"];
-
- ifstream.init(this, -1, 0, 0);
- icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
-
- let buffer = [];
- let str = {};
- while (icstream.readString(4096, str) != 0)
- buffer.push(str.value);
-
- icstream.close();
- ifstream.close();
- return buffer.join("");
- },
-
- /**
- * Writes the string <b>buf</b> to this file.
- *
- * @param {string} buf The file content.
- * @param {string|number} mode The file access mode, a bitwise OR of
- * the following flags:
- * {@link #MODE_RDONLY}: 0x01
- * {@link #MODE_WRONLY}: 0x02
- * {@link #MODE_RDWR}: 0x04
- * {@link #MODE_CREATE}: 0x08
- * {@link #MODE_APPEND}: 0x10
- * {@link #MODE_TRUNCATE}: 0x20
- * {@link #MODE_SYNC}: 0x40
- * Alternatively, the following abbreviations may be used:
- * ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE}
- * ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND}
- * @default ">"
- * @param {number} perms The file mode bits of the created file. This
- * is only used when creating a new file and does not change
- * permissions if the file exists.
- * @default 0644
- * @param {string} encoding The encoding to used to write the file.
- * @default options["fileencoding"]
- */
- write: function (buf, mode, perms, encoding) {
- let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
- function getStream(defaultChar) {
- let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
- stream.init(ofstream, encoding, 0, defaultChar);
- return stream;
- }
-
- if (!encoding)
- encoding = options["fileencoding"];
-
- if (mode == ">>")
- mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_APPEND;
- else if (!mode || mode == ">")
- mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE;
-
- if (!perms)
- perms = parseInt('0644', 8);
-
- ofstream.init(this, mode, perms, 0);
- let ocstream = getStream(0);
- try {
- ocstream.writeString(buf);
- }
- catch (e) {
- dactyl.dump(e);
- if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
- ocstream = getStream("?".charCodeAt(0));
- ocstream.writeString(buf);
- return false;
- }
- else
- throw e;
- }
- finally {
- try {
- ocstream.close();
- }
- catch (e) {}
- ofstream.close();
- }
- return true;
- }
-}, {
- /**
- * @property {number} Open for reading only.
- * @final
- */
- MODE_RDONLY: 0x01,
-
- /**
- * @property {number} Open for writing only.
- * @final
- */
- MODE_WRONLY: 0x02,
-
- /**
- * @property {number} Open for reading and writing.
- * @final
- */
- MODE_RDWR: 0x04,
-
- /**
- * @property {number} If the file does not exist, the file is created.
- * If the file exists, this flag has no effect.
- * @final
- */
- MODE_CREATE: 0x08,
-
- /**
- * @property {number} The file pointer is set to the end of the file
- * prior to each write.
- * @final
- */
- MODE_APPEND: 0x10,
-
- /**
- * @property {number} If the file exists, its length is truncated to 0.
- * @final
- */
- MODE_TRUNCATE: 0x20,
-
- /**
- * @property {number} If set, each write will wait for both the file
- * data and file status to be physically updated.
- * @final
- */
- MODE_SYNC: 0x40,
-
- /**
- * @property {number} With MODE_CREATE, if the file does not exist, the
- * file is created. If the file already exists, no action and NULL
- * is returned.
- * @final
- */
- MODE_EXCL: 0x80,
-
- expandPathList: function (list) list.map(this.expandPath),
-
- expandPath: function (path, relative) {
-
- // expand any $ENV vars - this is naive but so is Vim and we like to be compatible
- // TODO: Vim does not expand variables set to an empty string (and documents it).
- // Kris reckons we shouldn't replicate this 'bug'. --djk
- // TODO: should we be doing this for all paths?
- function expand(path) path.replace(
- !dactyl.has("Win32") ? /\$(\w+)\b|\${(\w+)}/g
- : /\$(\w+)\b|\${(\w+)}|%(\w+)%/g,
- function (m, n1, n2, n3) services.get("environment").get(n1 || n2 || n3) || m
- );
- path = expand(path);
-
- // expand ~
- // Yuck.
- if (!relative && RegExp("~(?:$|[/" + util.escapeRegex(IO.PATH_SEP) + "])").test(path)) {
- // Try $HOME first, on all systems
- let home = services.get("environment").get("HOME");
-
- // Windows has its own idiosyncratic $HOME variables.
- if (!home && dactyl.has("Win32"))
- home = services.get("environment").get("USERPROFILE") ||
- services.get("environment").get("HOMEDRIVE") + services.get("environment").get("HOMEPATH");
-
- path = home + path.substr(1);
- }
-
- // TODO: Vim expands paths twice, once before checking for ~, once
- // after, but doesn't document it. Is this just a bug? --Kris
- path = expand(path);
- return path.replace("/", IO.PATH_SEP, "g");
- },
-
- getPathsFromPathList: function (list) {
- if (!list)
- return [];
- // empty list item means the current directory
- return list.replace(/,$/, "").split(",")
- .map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir);
- },
-
- replacePathSep: function (path) path.replace("/", IO.PATH_SEP, "g"),
-
- joinPaths: function (head, tail) {
- let path = this(head);
- try {
- path.appendRelativePath(this.expandPath(tail, true)); // FIXME: should only expand env vars and normalise path separators
- // TODO: This code breaks the external editor at least in ubuntu
- // because /usr/bin/gvim becomes /usr/bin/vim.gnome normalized and for
- // some strange reason it will start without a gui then (which is not
- // optimal if you don't start firefox from a terminal ;)
- // Why do we need this code?
- // if (path.exists() && path.normalize)
- // path.normalize();
- }
- catch (e) {
- return { exists: function () false, __noSuchMethod__: function () { throw e; } };
- }
- return path;
- },
-
- isAbsolutePath: function (path) {
- try {
- services.create("file").initWithPath(path);
- return true;
- }
- catch (e) {
- return false;
- }
+ }
+ self = { __proto__: plugins };
+ plugins.contexts[file.path] = self;
+ self.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
+ self.PATH = file.path;
+ self.__context__ = self;
+ self.__proto__ = plugins;
+
+ // This belongs elsewhere
+ for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
+ if (dir.contains(file, false))
+ plugins[self.NAME] = self;
}
-});
+ return self;
+}
// TODO: why are we passing around strings rather than file objects?
/**
@@ -322,8 +38,6 @@ const File = Class("File", {
* @instance io
*/
const IO = Module("io", {
- requires: ["config", "services"],
-
init: function () {
this._processDir = services.get("directory").get("CurWorkD", Ci.nsIFile);
this._cwd = this._processDir;
@@ -363,7 +77,10 @@ const IO = Module("io", {
* @property {function} File class.
* @final
*/
- File: File,
+ File: Class("File", File, {
+ init: function init(path, checkCWD)
+ init.supercall(this, path, (arguments.length < 2 || checkCWD) && io.getCurrentDirectory())
+ }),
/**
* @property {Object} The current file sourcing context. As a file is
@@ -423,7 +140,7 @@ const IO = Module("io", {
if (newDir == "-")
[this._cwd, this._oldcwd] = [this._oldcwd, this.getCurrentDirectory()];
else {
- let dir = File(newDir);
+ let dir = io.File(newDir);
if (!dir.exists() || !dir.isDirectory()) {
dactyl.echoerr("E344: Can't find directory " + dir.path.quote());
@@ -489,7 +206,7 @@ const IO = Module("io", {
file.append(config.tempFile);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt('0600', 8));
- return File(file);
+ return io.File(file);
},
/**
@@ -507,7 +224,7 @@ const IO = Module("io", {
let file;
if (File.isAbsolutePath(program))
- file = File(program, true);
+ file = io.File(program, true);
else {
let dirs = services.get("environment").get("PATH").split(dactyl.has("Win32") ? ";" : ":");
// Windows tries the CWD first TODO: desirable?
@@ -600,7 +317,7 @@ lookup:
dactyl.dump("sourcing " + filename);
let time = Date.now();
try {
- var file = File(filename);
+ var file = io.File(filename);
this.sourcing = {
file: file.path,
line: 0
@@ -803,12 +520,7 @@ lookup:
/**
* @property {string} The current platform's path seperator.
*/
- get PATH_SEP() {
- delete this.PATH_SEP;
- let f = services.get("directory").get("CurProcD", Ci.nsIFile);
- f.append("foo");
- return this.PATH_SEP = f.path.substr(f.parent.path.length, 1);
- }
+ PATH_SEP: File.PATH_SEP
}, {
commands: function () {
commands.add(["cd", "chd[ir]"],
@@ -876,13 +588,13 @@ lookup:
dactyl.assert(args.length <= 1, "E172: Only one file name allowed");
let filename = args[0] || io.getRCFile(null, true).path;
- let file = File(filename);
+ let file = io.File(filename);
dactyl.assert(!file.exists() || args.bang,
"E189: " + filename.quote() + " exists (add ! to override)");
// TODO: Use a set/specifiable list here:
- let lines = [cmd.serial().map(commands.commandToString) for (cmd in commands) if (cmd.serial)];
+ let lines = [cmd.serialize().map(commands.commandToString) for (cmd in commands) if (cmd.serialize)];
lines = util.Array.flatten(lines);
// source a user .pentadactylrc file
@@ -922,10 +634,9 @@ lookup:
commands.add(["scrip[tnames]"],
"List all sourced script names",
function () {
- let list = template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
- ([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))); // TODO: add colon and remove column titles for pedantic Vim compatibility?
-
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ commandline.commandOutput(
+ template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
+ ([i + 1, file] for ([i, file] in Iterator(io._scriptNames))))); // TODO: add colon and remove column titles for pedantic Vim compatibility?
},
{ argCount: "0" });
@@ -967,8 +678,7 @@ lookup:
let output = io.system(arg);
commandline.command = "!" + arg;
- commandline.echo(template.commandOutput(commandline.command,
- <span highlight="CmdOutput">{output}</span>));
+ commandline.commandOutput(<span highlight="CmdOutput">{output}</span>);
autocommands.trigger("ShellCmdPost", {});
}, {
@@ -1034,7 +744,7 @@ lookup:
context.key = dir;
context.generate = function generate_file() {
try {
- return File(dir).readDirectory();
+ return io.File(dir).readDirectory();
}
catch (e) {}
return [];
@@ -1065,7 +775,7 @@ lookup:
});
},
javascript: function () {
- JavaScript.setCompleter([this.File, File.expandPath],
+ JavaScript.setCompleter([File, File.expandPath],
[function (context, obj, args) {
context.quote[2] = "";
completion.file(context, true);
@@ -1088,7 +798,9 @@ lookup:
options.add(["fileencoding", "fenc"],
"Sets the character encoding of read and written files",
"string", "UTF-8", {
- completer: function (context) completion.charset(context)
+ completer: function (context) completion.charset(context),
+ getter: function () File.defaultEncoding,
+ setter: function (value) (File.defaultEncoding = value)
});
options.add(["cdpath", "cd"],
"List of directories searched when executing :cd",
diff --git a/common/content/javascript.js b/common/content/javascript.js
index 91c18a72..ea730d2a 100644
--- a/common/content/javascript.js
+++ b/common/content/javascript.js
@@ -26,7 +26,6 @@ const JavaScript = Module("javascript", {
// Some object members are only accessible as function calls
getKey: function (obj, key) {
try {
- // if (!Object.prototype.__lookupGetter__.call(obj, key))
return obj[key];
}
catch (e) {}
@@ -35,20 +34,18 @@ const JavaScript = Module("javascript", {
iter: function iter(obj, toplevel) {
"use strict";
- const self = this;
if (obj == null)
return;
- let orig = obj;
- if(!options["jsdebugger"])
- function value(key) self.getKey(orig, key);
- else {
- let top = services.get("debugger").wrapValue(obj);
- function value(key) top.getProperty(key).value.getWrappedValue();
+ let seen = {};
+ for (let key in properties(obj, !toplevel)) {
+ set.add(seen, key);
+ yield [key, this.getKey(obj, key)];
}
- for (let key in properties(obj, !toplevel))
- yield [key, value(key)];
+ for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel))
+ if (!set.has(seen, key))
+ yield [key, this.getKey(obj, key)];
},
objectKeys: function objectKeys(obj, toplevel) {
diff --git a/common/content/mappings.js b/common/content/mappings.js
index eb595c05..da646b9b 100644
--- a/common/content/mappings.js
+++ b/common/content/mappings.js
@@ -35,6 +35,7 @@ const Map = Class("Map", {
this.modes = modes;
this.names = keys.map(events.canonicalKeys);
+ this.name = this.names[0];
this.action = action;
this.description = description;
@@ -122,8 +123,6 @@ const Map = Class("Map", {
* @instance mappings
*/
const Mappings = Module("mappings", {
- requires: ["modes"],
-
init: function () {
this._main = []; // default mappings
this._user = []; // user created mappings
@@ -177,9 +176,9 @@ const Mappings = Module("mappings", {
// Return all mappings present in all @modes
_mappingsIterator: function (modes, stack) {
modes = modes.slice();
- return (map for ([i, map] in Iterator(stack[modes.shift()]))
+ return (map for ([i, map] in Iterator(stack[modes.shift()].sort(function (m1, m2) String.localeCompare(m1.name, m2.name))))
if (modes.every(function (mode) stack[mode].some(
- function (m) m.rhs == map.rhs && m.names[0] == map.names[0]))))
+ function (mapping) m.rhs == map.rhs && m.name == map.name))))
},
// NOTE: just normal mode for now
@@ -363,11 +362,10 @@ const Mappings = Module("mappings", {
</table>;
// TODO: Move this to an ItemList to show this automatically
- if (list.*.length() == list.text().length()) {
+ if (list.*.length() == list.text().length())
dactyl.echomsg("No mapping found");
- return;
- }
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ else
+ commandline.commandOutput(list);
}
}, {
}, {
@@ -413,11 +411,9 @@ const Mappings = Module("mappings", {
const opts = {
completer: function (context, args) completion.userMapping(context, args, modes),
- options: [
- [["<silent>", "<Silent>"], commands.OPTION_NOARG]
- ],
literal: 1,
- serial: function () {
+ options: [{ names: ["<silent>", "<Silent>"] }],
+ serialize: function () {
let noremap = this.name.indexOf("noremap") > -1;
return [
{
diff --git a/common/content/marks.js b/common/content/marks.js
index 2fd1f0b5..f6ee4755 100644
--- a/common/content/marks.js
+++ b/common/content/marks.js
@@ -11,8 +11,6 @@
* @instance marks
*/
const Marks = Module("marks", {
- requires: ["config", "storage"],
-
init: function init() {
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
this._urlMarks = storage.newMap("url-marks", { store: true, privateData: true });
@@ -176,15 +174,15 @@ const Marks = Module("marks", {
dactyl.assert(marks.length > 0, "E283: No marks matching " + filter.quote());
}
- let list = template.tabular(
- ["Mark", "Line", "Column", "File"],
- ["", "text-align: right", "text-align: right", "color: green"],
- ([mark[0],
- Math.round(mark[1].position.x * 100) + "%",
- Math.round(mark[1].position.y * 100) + "%",
- mark[1].location]
- for ([, mark] in Iterator(marks))));
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ commandline.commandOutput(
+ template.tabular(
+ ["Mark", "Line", "Column", "File"],
+ ["", "text-align: right", "text-align: right", "color: green"],
+ ([mark[0],
+ Math.round(mark[1].position.x * 100) + "%",
+ Math.round(mark[1].position.y * 100) + "%",
+ mark[1].location]
+ for ([, mark] in Iterator(marks)))));
},
_onPageLoad: function _onPageLoad(event) {
diff --git a/common/content/modes.js b/common/content/modes.js
index fd7611b4..d9aa994b 100644
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -9,8 +9,6 @@
/** @scope modules */
const Modes = Module("modes", {
- requires: ["config", "util"],
-
init: function () {
this._main = 1; // NORMAL
this._extended = 0; // NONE
@@ -49,8 +47,6 @@ const Modes = Module("modes", {
this.addMode("MENU", true); // a popupmenu is active
this.addMode("LINE", true); // linewise visual mode
this.addMode("PROMPT", true);
-
- config.modes.forEach(function (mode) { this.addMode.apply(this, mode); }, this);
},
_getModeMessage: function () {
diff --git a/common/content/modules.js b/common/content/modules.js
index b485157b..840f87c5 100644
--- a/common/content/modules.js
+++ b/common/content/modules.js
@@ -53,12 +53,17 @@ const ModuleBase = Class("ModuleBase", {
*
* @returns {function} The constructor for the resulting module.
*/
-function Module(name, prototype, classProperties, moduleInit) {
+function Module(name) {
+ let args = Array.slice(arguments);
+
var base = ModuleBase;
- if (callable(prototype))
- base = Array.splice(arguments, 1, 1)[0];
+ if (callable(args[1]))
+ base = args.splice(1, 1)[0];
+ let [, prototype, classProperties, moduleInit] = args;
const module = Class(name, base, prototype, classProperties);
+
module.INIT = moduleInit || {};
+ module.prototype.INIT = module.INIT;
module.requires = prototype.requires || [];
Module.list.push(module);
Module.constructors[name] = module;
@@ -67,67 +72,82 @@ function Module(name, prototype, classProperties, moduleInit) {
Module.list = [];
Module.constructors = {};
-window.addEventListener("load", function () {
- window.removeEventListener("load", arguments.callee, false);
+window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, false);
+
+ Module.list.forEach(function(module) {
+ modules.__defineGetter__(module.name, function() {
+ delete modules[module.name];
+ return load(module.name, null, Components.stack.caller);
+ });
+ });
function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, Config.prototype.name.toLowerCase() + ": "));
const start = Date.now();
const deferredInit = { load: [] };
const seen = set();
- const loaded = [];
+ const loaded = set(["init"]);
+
+ function init(module) {
+ function init(func)
+ function () func.call(module, dactyl, modules, window);
+
+ set.add(loaded, module.constructor.name);
+ for (let [mod, func] in Iterator(module.INIT)) {
+ if (mod in loaded)
+ init(func)();
+ else {
+ deferredInit[mod] = deferredInit[mod] || [];
+ deferredInit[mod].push(init(func));
+ }
+ }
+ }
+ defmodule.modules.map(init);
+
+ function load(module, prereq, frame) {
+ if (isstring(module)) {
+ if (!Module.constructors.hasOwnProperty(module))
+ modules.load(module);
+ module = Module.constructors[module];
+ }
- function load(module, prereq) {
try {
- if (module.name in modules)
+ if (module.name in loaded)
return;
if (module.name in seen)
throw Error("Module dependency loop.");
set.add(seen, module.name);
for (let dep in values(module.requires))
- load(Module.constructors[dep], module.name, dep);
+ load(Module.constructors[dep], module.name);
dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name);
+ if (frame && frame.filename)
+ dump(" from: " + frame.filename + ":" + frame.lineNumber);
+
+ delete modules[module.name];
modules[module.name] = module();
- loaded.push(module.name);
-
- function init(mod, module)
- function () module.INIT[mod].call(modules[module.name], modules[mod]);
- for (let mod in values(loaded)) {
- try {
- if (mod in module.INIT)
- init(mod, module)();
- delete module.INIT[mod];
- }
- catch (e) {
- if (modules.dactyl)
- dactyl.reportError(e);
- }
- }
- for (let mod in values(Object.keys(module.INIT))) {
- deferredInit[mod] = deferredInit[mod] || [];
- deferredInit[mod].push(init(mod, module));
- }
+
+ init(modules[module.name]);
for (let [, fn] in iter(deferredInit[module.name] || []))
fn();
}
catch (e) {
- dump("Loading " + (module && module.name) + ": " + e + "\n");
+ dump("Loading " + (module && module.name) + ": " + e);
if (e.stack)
dump(e.stack);
}
+ return modules[module.name];
}
+
Module.list.forEach(load);
deferredInit["load"].forEach(call);
- for (let module in values(Module.list))
- delete module.INIT;
-
- dump("Loaded in " + (Date.now() - start) + "ms\n");
+ dump("Loaded in " + (Date.now() - start) + "ms");
}, false);
-window.addEventListener("unload", function () {
- window.removeEventListener("unload", arguments.callee, false);
+window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
for (let [, mod] in iter(modules))
if (mod instanceof ModuleBase && "destroy" in mod)
mod.destroy();
diff --git a/common/content/options.js b/common/content/options.js
index d60a773b..8f28e7be 100644
--- a/common/content/options.js
+++ b/common/content/options.js
@@ -238,8 +238,8 @@ const Option = Class("Option", {
* "string" - String, e.g., "Pentadactyl"
* "charlist" - Character list, e.g., "rb"
* "regexlist" - Regex list, e.g., "^foo,bar$"
- * "stringmap" - String map, e.g., "key=v,foo=bar"
- * "regexmap" - Regex map, e.g., "^key=v,foo$=bar"
+ * "stringmap" - String map, e.g., "key:v,foo:bar"
+ * "regexmap" - Regex map, e.g., "^key:v,foo$:bar"
*/
type: null,
@@ -351,9 +351,9 @@ const Option = Class("Option", {
boolean: function (value) value == "true" || value == true ? true : false,
charlist: function (value) Array.slice(value),
stringlist: function (value) (value === "") ? [] : value.split(","),
- stringmap: function (value) array(v.split("=") for (v in values(value.split(",")))).toObject(),
+ stringmap: function (value) array(v.split(":") for (v in values(value.split(",")))).toObject(),
regexlist: function (value) (value === "") ? [] : value.split(",").map(Option.parseRegex),
- regexmap: function (value) value.split(",").map(function (v) v.split("="))
+ regexmap: function (value) value.split(",").map(function (v) v.split(":"))
.map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex('.?', k))
},
@@ -478,8 +478,6 @@ Option.ops["regexmap"] = Option.ops["stringlist"];
* @instance options
*/
const Options = Module("options", {
- requires: ["config", "highlight", "storage"],
-
init: function () {
this._optionHash = {};
this._prefContexts = [];
@@ -661,8 +659,7 @@ const Options = Module("options", {
}
};
- let list = template.options("Options", opts());
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ commandline.commandOutput(template.options("Options", opts()));
},
/**
@@ -687,7 +684,7 @@ const Options = Module("options", {
if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
continue;
- value = options.getPref(pref);
+ let value = options.getPref(pref);
let option = {
isDefault: !userValue,
@@ -701,8 +698,8 @@ const Options = Module("options", {
}
};
- let list = template.options(config.hostApplication + " Options", prefs());
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ commandline.commandOutput(
+ template.options(config.hostApplication + " Options", prefs()));
},
/**
@@ -783,15 +780,7 @@ const Options = Module("options", {
return this._loadPreference(name, forcedDefault);
},
- /**
- * Sets the preference <b>name</b> to </b>value</b> but warns the user
- * if the value is changed from its default.
- *
- * @param {string} name The preference name.
- * @param {value} value The new preference value.
- */
- // FIXME: Well it used to. I'm looking at you mst! --djk
- safeSetPref: function (name, value, message) {
+ _checkPrefSafe: function (name, message) {
let curval = this._loadPreference(name, null, false);
let defval = this._loadPreference(name, null, true);
let saved = this._loadPreference(Options.SAVED + name);
@@ -802,6 +791,30 @@ const Options = Module("options", {
msg += " " + message;
dactyl.echomsg(msg);
}
+ },
+
+ /**
+ * Resets the preference <b>name</b> to </b>value</b> but warns the user
+ * if the value is changed from its default.
+ *
+ * @param {string} name The preference name.
+ * @param {value} value The new preference value.
+ */
+ safeResetPref: function (name, message) {
+ this._checkPrefSafe(name, message);
+ this.resetPref(name);
+ this.resetPref(Options.SAVED + name);
+ },
+
+ /**
+ * Sets the preference <b>name</b> to </b>value</b> but warns the user
+ * if the value is changed from its default.
+ *
+ * @param {string} name The preference name.
+ * @param {value} value The new preference value.
+ */
+ safeSetPref: function (name, value, message) {
+ this._checkPrefSafe(name, message);
this._storePreference(name, value);
this._storePreference(Options.SAVED + name, value);
},
@@ -825,9 +838,7 @@ const Options = Module("options", {
try {
services.get("pref").clearUserPref(name);
}
- catch (e) {
- // ignore - thrown if not a user set value
- }
+ catch (e) {} // ignore - thrown if not a user set value
},
/**
@@ -1238,7 +1249,7 @@ const Options = Module("options", {
completer: function (context, args) {
return setCompleter(context, args);
},
- serial: function () [
+ serialize: function () [
{
command: this.name,
arguments: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js
index fc44eb79..70dc1b92 100644
--- a/common/content/quickmarks.js
+++ b/common/content/quickmarks.js
@@ -12,8 +12,6 @@
* @instance quickmarks
*/
const QuickMarks = Module("quickmarks", {
- requires: ["config", "storage"],
-
init: function () {
this._qmarks = storage.newMap("quickmarks", { store: true });
},
diff --git a/common/content/sanitizer.js b/common/content/sanitizer.js
index 68b5ed69..83d93aaa 100644
--- a/common/content/sanitizer.js
+++ b/common/content/sanitizer.js
@@ -15,8 +15,6 @@
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
const Sanitizer = Module("sanitizer", {
- requires: ["dactyl"],
-
init: function () {
const self = this;
dactyl.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
@@ -144,10 +142,13 @@ const Sanitizer = Module("sanitizer", {
context.completions = options.get("sanitizeitems").completer();
},
options: [
- [["-timespan", "-t"],
- commands.OPTION_INT,
- function (arg) /^[0-4]$/.test(arg),
- function () options.get("sanitizetimespan").completer()]
+ {
+ names: ["-timespan", "-t"],
+ description: "Timespan for which to sanitize items",
+ completer: function () options.get("sanitizetimespan").completer(),
+ type: CommandOption.INT,
+ validator: function (arg) /^[0-4]$/.test(arg)
+ }
]
});
},
diff --git a/common/content/style.js b/common/content/style.js
deleted file mode 100644
index 778d1a5d..00000000
--- a/common/content/style.js
+++ /dev/null
@@ -1,832 +0,0 @@
-// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
-//
-// This work is licensed for reuse under an MIT license. Details are
-// given in the LICENSE.txt file included with this file.
-"use strict";
-
-/** @scope modules */
-
-/**
- * @constant
- * @property {string} The default highlighting rules. They have the
- * form:
- * rule ::= selector space space+ css
- * selector ::= group
- * | group "," css-selector
- * | group "," css-selector "," scope
- * group ::= groupname
- * | groupname css-selector
- */
-// <css>
-Highlights.prototype.CSS = <![CDATA[
- Boolean color: red;
- Function color: navy;
- Null color: blue;
- Number color: blue;
- Object color: maroon;
- String color: green;
-
- Key font-weight: bold;
-
- Enabled color: blue;
- Disabled color: red;
-
- Normal color: black; background: white;
- ErrorMsg color: white; background: red; font-weight: bold;
- InfoMsg color: black; background: white;
- ModeMsg color: black; background: white;
- MoreMsg color: green; background: white;
- WarningMsg color: red; background: white;
- Message white-space: normal; min-width: 100%; padding-left: 2em; text-indent: -2em; display: block;
- NonText color: blue; min-height: 16px; padding-left: 2px;
- Preview color: gray;
-
- CmdLine,>* font-family: monospace; padding: 1px;
- CmdOutput white-space: pre;
-
- CompGroup
- CompGroup:not(:first-of-type) margin-top: .5em;
- CompTitle color: magenta; background: white; font-weight: bold;
- CompTitle>* padding: 0 .5ex;
- CompMsg font-style: italic; margin-left: 16px;
- CompItem
- CompItem[selected] background: yellow;
- CompItem>* padding: 0 .5ex;
- CompIcon width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
- CompIcon>img max-width: 16px; max-height: 16px; vertical-align: middle;
- CompResult width: 45%; overflow: hidden;
- CompDesc color: gray; width: 50%;
- CompLess text-align: center; height: 0; line-height: .5ex; padding-top: 1ex;
- CompLess::after content: "\2303" /* Unicode up arrowhead */
- CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
- CompMore::after content: "\2304" /* Unicode down arrowhead */
-
- Gradient height: 1px; margin-bottom: -1px; margin-top: -1px;
- GradientLeft background-color: magenta;
- GradientRight background-color: white;
-
- Indicator color: blue;
- Filter font-weight: bold;
-
- Keyword color: red;
- Tag color: blue;
-
- LineNr color: orange; background: white;
- Question color: green; background: white; font-weight: bold;
-
- StatusLine color: white; background: black;
- StatusLineBroken color: black; background: #FFa0a0 /* light-red */
- StatusLineSecure color: black; background: #a0a0FF /* light-blue */
- StatusLineExtended color: black; background: #a0FFa0 /* light-green */
-
- TabClose,.tab-close-button
- TabIcon,.tab-icon
- TabText,.tab-text
- TabNumber font-weight: bold; margin: 0px; padding-right: .3ex;
- TabIconNumber {
- font-weight: bold;
- color: white;
- text-align: center;
- text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
- }
-
- Title color: magenta; background: white; font-weight: bold;
- URL text-decoration: none; color: green; background: inherit;
- URL:hover text-decoration: underline; cursor: pointer;
-
- FrameIndicator,,* {
- background-color: red;
- opacity: 0.5;
- z-index: 999;
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
-
- Bell border: none; background-color: black;
- Hint,,* {
- font-family: monospace;
- font-size: 10px;
- font-weight: bold;
- color: white;
- background-color: red;
- border-color: ButtonShadow;
- border-width: 0px;
- border-style: solid;
- padding: 0px 1px 0px 1px;
- }
- Hint::after,,* content: attr(number);
- HintElem,,* background-color: yellow; color: black;
- HintActive,,* background-color: #88FF00; color: black;
- HintImage,,* opacity: .5;
-
- Help font-size: 8pt; line-height: 1.4em; font-family: -moz-fixed;
-
- HelpArg color: #6A97D4;
- HelpOptionalArg color: #6A97D4;
-
- HelpBody display: block; margin: 1em auto; max-width: 100ex;
- HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
- HelpCode display: block; white-space: pre; margin-left: 2em; font-family: Terminus, Fixed, monospace;
-
- HelpDefault margin-right: 1ex; white-space: pre;
-
- HelpDescription display: block;
- HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
-
- HelpEx display: inline-block; color: #527BBD; font-weight: bold;
-
- HelpExample display: block; margin: 1em 0;
- HelpExample::before content: "Example: "; font-weight: bold;
-
- HelpInfo display: block; width: 20em; margin-left: auto;
- HelpInfoLabel display: inline-block; width: 6em; color: magenta; font-weight: bold; vertical-align: text-top;
- HelpInfoValue display: inline-block; width: 14em; text-decoration: none; vertical-align: text-top;
-
- HelpItem display: block; margin: 1em 1em 1em 10em; clear: both;
-
- HelpKey color: #102663;
-
- HelpLink,html|a,dactyl://help/* text-decoration: none;
- HelpLink[href]:hover text-decoration: underline;
-
- HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
- HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
- HelpListItem,html|li,dactyl://help/* display: list-item;
-
- HelpNote color: red; font-weight: bold;
-
- HelpOpt color: #106326;
- HelpOptInfo display: inline-block; margin-bottom: 1ex;
-
- HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
- HelpParagraph:first-child margin-top: 0;
- HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD;
-
- HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
- HelpString::before content: '"';
- HelpString::after content: '"';
- HelpString[delim]::before content: attr(delim);
- HelpString[delim]::after content: attr(delim);
-
- HelpHead,html|h1,dactyl://help/* {
- display: block;
- margin: 1em 0;
- padding-bottom: .2ex;
- border-bottom-width: 1px;
- font-size: 2em;
- font-weight: bold;
- color: #527BBD;
- clear: both;
- }
- HelpSubhead,html|h2,dactyl://help/* {
- display: block;
- margin: 1em 0;
- padding-bottom: .2ex;
- border-bottom-width: 1px;
- font-size: 1.2em;
- font-weight: bold;
- color: #527BBD;
- clear: both;
- }
- HelpSubsubhead,html|h3,dactyl://help/* {
- display: block;
- margin: 1em 0;
- padding-bottom: .2ex;
- font-size: 1.1em;
- font-weight: bold;
- color: #527BBD;
- clear: both;
- }
-
- HelpTOC
- HelpTOC>ol ol margin-left: -1em;
-
- HelpTab,html|dl,dactyl://help/* display: table; width: 100%; margin: 1em 0; border-bottom-width: 1px; border-top-width: 1px; padding: .5ex 0; table-layout: fixed;
- HelpTabColumn,html|column,dactyl://help/* display: table-column;
- HelpTabColumn:first-child width: 25%;
- HelpTabTitle,html|dt,dactyl://help/* display: table-cell; padding: .1ex 1ex; font-weight: bold;
- HelpTabDescription,html|dd,dactyl://help/* display: table-cell; padding: .1ex 1ex; border-width: 0px;
- HelpTabRow,html|dl>html|tr,dactyl://help/* display: table-row;
-
- HelpTag display: inline-block; color: #527BBD; margin-left: 1ex; font-size: 8pt; font-weight: bold;
- HelpTags display: block; float: right; clear: right;
- HelpTopic color: #102663;
- HelpType margin-right: 2ex;
-
- HelpWarning color: red; font-weight: bold;
-
- Logo
-
- Search,,* {
- font-size: inherit;
- padding: 0;
- color: black;
- background-color: yellow;
- }
- ]]>.toString();
-
-/**
- * A class to manage highlighting rules. The parameters are the
- * standard parameters for any {@link Storage} object.
- *
- * @author Kris Maglione <maglione.k@gmail.com>
- */
-function Highlights(name, store) {
- let self = this;
- let highlight = {};
- let styles = storage.styles;
-
- const Highlight = Struct("class", "selector", "filter", "default", "value", "base");
- Highlight.defaultValue("filter", function ()
- this.base ? this.base.filter :
- ["chrome://dactyl/*",
- "dactyl:*",
- "file://*"].concat(config.styleableChrome).join(","));
- Highlight.defaultValue("selector", function () self.selector(this.class));
- Highlight.defaultValue("value", function () this.default);
- Highlight.defaultValue("base", function () {
- let base = this.class.match(/^(\w*)/)[0];
- return base != this.class && base in highlight ? highlight[base] : null;
- });
- Highlight.prototype.toString = function () "Highlight(" + this.class + ")\n\t" + [k + ": " + util.escapeString(v || "undefined") for ([k, v] in this)].join("\n\t");
-
- function keys() [k for ([k, v] in Iterator(highlight))].sort();
-
- this.__iterator__ = function () (highlight[v] for ([k, v] in Iterator(keys())));
-
- this.get = function (k) highlight[k];
- this.set = function (key, newStyle, force, append) {
- let [, class_, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
-
- if (!(class_ in highlight))
- return "Unknown highlight keyword: " + class_;
-
- let style = highlight[key] || Highlight(key);
- styles.removeSheet(true, style.selector);
-
- if (append)
- newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
- if (/^\s*$/.test(newStyle))
- newStyle = null;
- if (newStyle == null) {
- if (style.default == null) {
- delete highlight[style.class];
- styles.removeSheet(true, style.selector);
- return null;
- }
- newStyle = style.default;
- force = true;
- }
-
- let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
- .replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
- if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
- css = style.selector + " { " + css + " }";
-
- let error = styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
- if (error)
- return error;
- }
- style.value = newStyle;
- highlight[style.class] = style;
- return null;
- };
-
- /**
- * Gets a CSS selector given a highlight group.
- *
- * @param {string} class
- */
- this.selector = function (class_) {
- let [, hl, rest] = class_.match(/^(\w*)(.*)/);
- let pattern = "[dactyl|highlight~=" + hl + "]"
- if (highlight[hl] && highlight[hl].class != class_)
- pattern = highlight[hl].selector;
- return pattern + rest;
- };
-
- /**
- * Clears all highlighting rules. Rules with default values are
- * reset.
- */
- this.clear = function () {
- for (let [k, v] in Iterator(highlight))
- this.set(k, null, true);
- };
-
- /**
- * Bulk loads new CSS rules.
- *
- * @param {string} css The rules to load. See {@link Highlights#css}.
- */
- this.loadCSS = function (css) {
- css.replace(/^(\s*\S*\s+)\{((?:.|\n)*?)\}\s*$/gm, function (_, _1, _2) _1 + " " + _2.replace(/\n\s*/g, " "))
- .split("\n").filter(function (s) /\S/.test(s))
- .forEach(function (style) {
- style = Highlight.apply(Highlight, Array.slice(style.match(/^\s*((?:[^,\s]|\s\S)+)(?:,((?:[^,\s]|\s\S)+)?)?(?:,((?:[^,\s]|\s\S)+))?\s*(.*)$/), 1));
- if (/^[>+ ]/.test(style.selector))
- style.selector = self.selector(style.class) + style.selector;
-
- let old = highlight[style.class];
- highlight[style.class] = style;
- if (old && old.value != old.default)
- style.value = old.value;
- });
- for (let [class_, hl] in Iterator(highlight)) {
- if (hl.value == hl.default)
- this.set(class_);
- }
- };
- this.loadCSS(this.CSS);
-}
-
-/**
- * Manages named and unnamed user style sheets, which apply to both
- * chrome and content pages. The parameters are the standard
- * parameters for any {@link Storage} object.
- *
- * @author Kris Maglione <maglione.k@gmail.com>
- */
-function Styles(name, store) {
- // Can't reference dactyl or Components inside Styles --
- // they're members of the window object, which disappear
- // with this window.
- const self = this;
- const util = modules.util;
- const sleep = dactyl.sleep;
- const storage = modules.storage;
- const ios = services.get("io");
- const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
- const namespace = "@namespace html " + XHTML.uri.quote() + ";\n" +
- "@namespace xul " + XUL.uri.quote() + ";\n" +
- "@namespace dactyl " + NS.uri.quote() + ";\n";
-
- const Sheet = Struct("name", "id", "sites", "css", "system", "agent");
- Sheet.prototype.__defineGetter__("fullCSS", function wrapCSS() {
- let filter = this.sites;
- let css = this.css;
- if (filter[0] == "*")
- return namespace + css;
- let selectors = filter.map(function (part) (/[*]$/.test(part) ? "url-prefix" :
- /[\/:]/.test(part) ? "url"
- : "domain")
- + '("' + part.replace(/"/g, "%22").replace(/[*]$/, "") + '")')
- .join(", ");
- return namespace + "/* Dactyl style #" + this.id + " */ @-moz-document " + selectors + "{\n" + css + "\n}\n";
- });
- Sheet.prototype.__defineGetter__("enabled", function () this._enabled);
- Sheet.prototype.__defineSetter__("enabled", function (on) {
- this._enabled = Boolean(on);
- if (on) {
- self.registerSheet(cssUri(this.fullCSS));
- if (this.agent)
- self.registerSheet(cssUri(this.fullCSS), true);
- }
- else {
- self.unregisterSheet(cssUri(this.fullCSS));
- self.unregisterSheet(cssUri(this.fullCSS), true);
- }
- });
-
- let cssUri = function (css) "chrome-data:text/css," + window.encodeURI(css);
-
- let userSheets = [];
- let systemSheets = [];
- let userNames = {};
- let systemNames = {};
-
- let id = 0;
-
- this.__iterator__ = function () Iterator(userSheets.concat(systemSheets));
- this.__defineGetter__("systemSheets", function () Iterator(systemSheets));
- this.__defineGetter__("userSheets", function () Iterator(userSheets));
- this.__defineGetter__("systemNames", function () Iterator(systemNames));
- this.__defineGetter__("userNames", function () Iterator(userNames));
-
- /**
- * Add a new style sheet.
- *
- * @param {boolean} system Declares whether this is a system or
- * user sheet. System sheets are used internally by
- * @dactyl.
- * @param {string} name The name given to the style sheet by
- * which it may be later referenced.
- * @param {string} filter The sites to which this sheet will
- * apply. Can be a domain name or a URL. Any URL ending in
- * "*" is matched as a prefix.
- * @param {string} css The CSS to be applied.
- */
- this.addSheet = function (system, name, filter, css, agent) {
- let sheets = system ? systemSheets : userSheets;
- let names = system ? systemNames : userNames;
- if (name && name in names)
- this.removeSheet(system, name);
-
- let sheet = Sheet(name, id++, filter.split(",").filter(util.identity), String(css), null, system, agent);
-
- try {
- sheet.enabled = true;
- }
- catch (e) {
- return e.echoerr || e;
- }
- sheets.push(sheet);
-
- if (name)
- names[name] = sheet;
- return null;
- };
-
- /**
- * Get a sheet with a given name or index.
- *
- * @param {boolean} system
- * @param {string or number} sheet The sheet to retrieve. Strings indicate
- * sheet names, while numbers indicate indices.
- */
- this.get = function get(system, sheet) {
- let sheets = system ? systemSheets : userSheets;
- let names = system ? systemNames : userNames;
- if (typeof sheet === "number")
- return sheets[sheet];
- return names[sheet];
- };
-
- /**
- * Find sheets matching the parameters. See {@link #addSheet}
- * for parameters.
- *
- * @param {boolean} system
- * @param {string} name
- * @param {string} filter
- * @param {string} css
- * @param {number} index
- */
- this.findSheets = function (system, name, filter, css, index) {
- let sheets = system ? systemSheets : userSheets;
- let names = system ? systemNames : userNames;
-
- // Grossly inefficient.
- let matches = [k for ([k, v] in Iterator(sheets))];
- if (index)
- matches = String(index).split(",").filter(function (i) i in sheets);
- if (name)
- matches = matches.filter(function (i) sheets[i] == names[name]);
- if (css)
- matches = matches.filter(function (i) sheets[i].css == css);
- if (filter)
- matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0);
- return matches.map(function (i) sheets[i]);
- };
-
- /**
- * Remove a style sheet. See {@link #addSheet} for parameters.
- * In cases where <b>filter</b> is supplied, the given filters
- * are removed from matching sheets. If any remain, the sheet is
- * left in place.
- *
- * @param {boolean} system
- * @param {string} name
- * @param {string} filter
- * @param {string} css
- * @param {number} index
- */
- this.removeSheet = function (system, name, filter, css, index) {
- let self = this;
- if (arguments.length == 1) {
- var matches = [system];
- system = matches[0].system;
- }
- let sheets = system ? systemSheets : userSheets;
- let names = system ? systemNames : userNames;
-
- if (filter && filter.indexOf(",") > -1)
- return filter.split(",").reduce(
- function (n, f) n + self.removeSheet(system, name, f, index), 0);
-
- if (filter == undefined)
- filter = "";
-
- if (!matches)
- matches = this.findSheets(system, name, filter, css, index);
- if (matches.length == 0)
- return null;
-
- for (let [, sheet] in Iterator(matches.reverse())) {
- sheet.enabled = false;
- if (name)
- delete names[name];
- if (sheets.indexOf(sheet) > -1)
- sheets.splice(sheets.indexOf(sheet), 1);
-
- /* Re-add if we're only changing the site filter. */
- if (filter) {
- let sites = sheet.sites.filter(function (f) f != filter);
- if (sites.length)
- this.addSheet(system, name, sites.join(","), css, sheet.agent);
- }
- }
- return matches.length;
- };
-
- /**
- * Register a user style sheet at the given URI.
- *
- * @param {string} uri The URI of the sheet to register.
- * @param {boolean} agent If true, sheet is registered as an agent sheet.
- * @param {boolean} reload Whether to reload any sheets that are
- * already registered.
- */
- this.registerSheet = function (uri, agent, reload) {
- if (reload)
- this.unregisterSheet(uri, agent);
- uri = ios.newURI(uri, null, null);
- if (reload || !sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
- sss.loadAndRegisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
- };
-
- /**
- * Unregister a sheet at the given URI.
- *
- * @param {string} uri The URI of the sheet to unregister.
- */
- this.unregisterSheet = function (uri, agent) {
- uri = ios.newURI(uri, null, null);
- if (sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
- sss.unregisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
- };
-}
-
-Module("styles", {
- requires: ["config", "dactyl", "storage", "util"],
-
- init: function () {
- let (array = util.Array) {
- update(Styles.prototype, {
- get sites() array([v.sites for ([k, v] in this.userSheets)]).flatten().uniq().__proto__,
- completeSite: function (context, content) {
- context.anchored = false;
- try {
- context.fork("current", 0, this, function (context) {
- context.title = ["Current Site"];
- context.completions = [
- [content.location.host, "Current Host"],
- [content.location.href, "Current URL"]
- ];
- });
- }
- catch (e) {}
- context.fork("others", 0, this, function (context) {
- context.title = ["Site"];
- context.completions = [[s, ""] for ([, s] in Iterator(styles.sites))];
- });
- }
- });
- }
- return storage.newObject("styles", Styles, { store: false });
- }
-}, {
-}, {
- commands: function () {
- commands.add(["sty[le]"],
- "Add or list user styles",
- function (args) {
- let [filter, css] = args;
- let name = args["-name"];
-
- if (!css) {
- let list = Array.concat([i for (i in styles.userNames)],
- [i for (i in styles.userSheets) if (!i[1].name)]);
- let str = template.tabular(["", "Name", "Filter", "CSS"],
- ["min-width: 1em; text-align: center; color: red; font-weight: bold;",
- "padding: 0 1em 0 1ex; vertical-align: top;",
- "padding: 0 1em 0 0; vertical-align: top;"],
- ([sheet.enabled ? "" : "\u00d7",
- key,
- sheet.sites.join(","),
- sheet.css]
- for ([i, [key, sheet]] in Iterator(list))
- if ((!filter || sheet.sites.indexOf(filter) >= 0) && (!name || sheet.name == name))));
- commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- }
- else {
- if ("-append" in args) {
- let sheet = styles.get(false, name);
- if (sheet) {
- filter = sheet.sites.concat(filter).join(",");
- css = sheet.css + " " + css;
- }
- }
- let err = styles.addSheet(false, name, filter, css);
- if (err)
- dactyl.echoerr(err);
- }
- },
- {
- bang: true,
- completer: function (context, args) {
- let compl = [];
- if (args.completeArg == 0)
- styles.completeSite(context, content);
- else if (args.completeArg == 1) {
- let sheet = styles.get(false, args["-name"]);
- if (sheet)
- context.completions = [[sheet.css, "Current Value"]];
- }
- },
- hereDoc: true,
- literal: 1,
- options: [[["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]],
- [["-append", "-a"], commands.OPTION_NOARG]],
- serial: function () [
- {
- command: this.name,
- bang: true,
- options: sty.name ? { "-name": sty.name } : {},
- arguments: [sty.sites.join(",")],
- literalArg: sty.css
- } for ([k, sty] in styles.userSheets)
- ]
- });
-
- [
- {
- name: ["stylee[nable]", "stye[nable]"],
- desc: "Enable a user style sheet",
- action: function (sheet) sheet.enabled = true,
- filter: function (sheet) !sheet.enabled
- },
- {
- name: ["styled[isable]", "styd[isable]"],
- desc: "Disable a user style sheet",
- action: function (sheet) sheet.enabled = false,
- filter: function (sheet) sheet.enabled
- },
- {
- name: ["stylet[oggle]", "styt[oggle]"],
- desc: "Toggle a user style sheet",
- action: function (sheet) sheet.enabled = !sheet.enabled
- },
- {
- name: ["dels[tyle]"],
- desc: "Remove a user style sheet",
- action: function (sheet) styles.removeSheet(sheet)
- }
- ].forEach(function (cmd) {
- commands.add(cmd.name, cmd.desc,
- function (args) {
- styles.findSheets(false, args["-name"], args[0], args.literalArg, args["-index"])
- .forEach(cmd.action);
- },
- {
- completer: function (context) { context.completions = styles.sites.map(function (site) [site, ""]); },
- literal: 1,
- options: [[["-index", "-i"], commands.OPTION_INT, null,
- function (context) {
- context.compare = CompletionContext.Sort.number;
- return [[i, <>{sheet.sites.join(",")}: {sheet.css.replace("\n", "\\n")}</>]
- for ([i, sheet] in styles.userSheets)
- if (!cmd.filter || cmd.filter(sheet))];
- }],
- [["-name", "-n"], commands.OPTION_STRING, null,
- function () [[name, sheet.css]
- for ([name, sheet] in Iterator(styles.userNames))
- if (!cmd.filter || cmd.filter(sheet))]]]
- });
- });
- },
- completion: function () {
- JavaScript.setCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
- [ // Prototype: (system, name, filter, css, index)
- null,
- function (context, obj, args) args[0] ? this.systemNames : this.userNames,
- function (context, obj, args) this.completeSite(context, content),
- null,
- function (context, obj, args) args[0] ? this.systemSheets : this.userSheets
- ]);
- }
-});
-
-Module("highlight", {
- requires: ["config", "styles"],
-
- init: function () {
- const self = storage.newObject("highlight", Highlights, { store: false });
-
- if (self.CSS != Highlights.prototype.CSS) {
- self.CSS = Highlights.prototype.CSS;
- self.loadCSS(self.CSS);
- }
- return self;
- }
-}, {
-}, {
- commands: function () {
- commands.add(["colo[rscheme]"],
- "Load a color scheme",
- function (args) {
- let scheme = args[0];
-
- if (scheme == "default")
- highlight.clear();
- else
- dactyl.assert(io.sourceFromRuntimePath(["colors/" + scheme + ".vimp"]),
- "E185: Cannot find color scheme " + scheme);
- autocommands.trigger("ColorScheme", { name: scheme });
- },
- {
- argCount: "1",
- completer: function (context) completion.colorScheme(context)
- });
-
- commands.add(["hi[ghlight]"],
- "Set the style of certain display elements",
- function (args) {
- let style = <![CDATA[
- ;
- display: inline-block !important;
- position: static !important;
- margin: 0px !important; padding: 0px !important;
- width: 3em !important; min-width: 3em !important; max-width: 3em !important;
- height: 1em !important; min-height: 1em !important; max-height: 1em !important;
- overflow: hidden !important;
- ]]>;
- let clear = args[0] == "clear";
- if (clear)
- args.shift();
-
- let [key, css] = args;
- dactyl.assert(!(clear && css), "E488: Trailing characters");
-
- if (!css && !clear) {
- // List matching keys
- let str = template.tabular(["Key", "Sample", "CSS"],
- ["padding: 0 1em 0 0; vertical-align: top",
- "text-align: center"],
- ([h.class,
- <span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,
- template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)]
- for (h in highlight)
- if (!key || h.class.indexOf(key) > -1)));
- commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- return;
- }
- if (!key && clear)
- highlight.clear();
- else {
- let error = highlight.set(key, css, clear, "-append" in args);
- if (error)
- dactyl.echoerr(error);
- }
- },
- {
- // TODO: add this as a standard highlight completion function?
- completer: function (context, args) {
- // Complete a highlight group on :hi clear ...
- if (args.completeArg > 0 && args[0] == "clear")
- args.completeArg = args.completeArg > 1 ? -1 : 0;
-
- if (args.completeArg == 0)
- context.completions = [[v.class, v.value] for (v in highlight)];
- else if (args.completeArg == 1) {
- let hl = highlight.get(args[0]);
- if (hl)
- context.completions = [[hl.value, "Current Value"], [hl.default || "", "Default Value"]];
- }
- },
- hereDoc: true,
- literal: 1,
- options: [[["-append", "-a"], commands.OPTION_NOARG]],
- serial: function () [
- {
- command: this.name,
- arguments: [k],
- literalArg: v
- }
- for ([k, v] in Iterator(highlight))
- if (v.value != v.default)
- ]
- });
- },
- completion: function () {
- completion.colorScheme = function colorScheme(context) {
- context.title = ["Color Scheme", "Runtime Path"];
- context.keys = { text: function (f) f.leafName.replace(/\.vimp$/, ""), description: ".parent.path" };
- context.completions = util.Array.flatten(
- io.getRuntimeDirectories("colors").map(
- function (dir) dir.readDirectory().filter(
- function (file) /\.vimp$/.test(file.leafName))))
-
- };
-
- completion.highlightGroup = function highlightGroup(context) {
- context.title = ["Highlight Group", "Value"];
- context.completions = [[v.class, v.value] for (v in highlight)];
- };
- }
-});
-
-// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/tabs.js b/common/content/tabs.js
index a1b53949..efba231c 100644
--- a/common/content/tabs.js
+++ b/common/content/tabs.js
@@ -14,8 +14,6 @@
* @instance tabs
*/
const Tabs = Module("tabs", {
- requires: ["config"],
-
init: function () {
this._alternates = [config.tabbrowser.mCurrentTab, null];
@@ -187,7 +185,7 @@ const Tabs = Module("tabs", {
this._groups = this._groups = iframe ? iframe.contentWindow : null;
if (this._groups)
while (!this._groups.TabItems)
- dactyl.threadYield(false, true);
+ util.threadYield(false, true);
return this._groups;
},
@@ -196,14 +194,15 @@ const Tabs = Module("tabs", {
* selected tab if <b>index</b> is not specified. This is a 0-based
* index.
*
- * @param {number} index The index of the tab required.
+ * @param {number|Node} index The index of the tab required or the tab itself
* @returns {Object}
*/
getTab: function (index) {
+ if (index instanceof Node)
+ return index;
if (index != null)
return config.tabbrowser.mTabs[index];
- else
- return config.tabbrowser.mCurrentTab;
+ return config.tabbrowser.mCurrentTab;
},
/**
diff --git a/common/content/base.js b/common/modules/base.jsm
index e4a58bbb..20b22715 100644
--- a/common/content/base.js
+++ b/common/modules/base.jsm
@@ -9,6 +9,67 @@ const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let use = {};
+let loaded = {};
+let currentModule;
+function defmodule(name, module, params) {
+ module.NAME = name;
+ module.EXPORTED_SYMBOLS = params.exports || [];
+ dump("defmodule " + name + "\n");
+ for(let [, mod] in Iterator(params.require || []))
+ require(module, mod);
+
+ for(let [, mod] in Iterator(params.use || []))
+ if (loaded.hasOwnProperty(mod))
+ require(module, mod, "use");
+ else {
+ use[mod] = use[mod] || [];
+ use[mod].push(module);
+ }
+ currentModule = module;
+}
+defmodule.modules = [];
+
+function endmodule() {
+ dump("endmodule " + currentModule.NAME + "\n");
+ loaded[currentModule.NAME] = 1;
+ for(let [, mod] in Iterator(use[currentModule.NAME] || []))
+ require(mod, currentModule.NAME, "use");
+}
+
+function require(obj, name, from) {
+ try {
+ dump((from || "require") + ": loading " + name + " into " + obj.NAME + "\n");
+ Cu.import("resource://dactyl/" + name + ".jsm", obj);
+ }
+ catch (e) {
+ dump("loading " + String.quote("resource://dactyl/" + name + ".jsm") + "\n");
+ dump(" " + e.fileName + ":" + e.lineNumber + ": " + e +"\n");
+ }
+}
+
+defmodule("base", this, {
+ // sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/ "\2",/p' base.jsm | sort | fmt
+ exports: [
+ "Cc", "Ci", "Class", "Cr", "Cu", "Module", "Object", "Runnable",
+ "Struct", "StructBase", "Timer", "allkeys", "array", "call",
+ "callable", "curry", "debuggerProperties", "defmodule", "dict",
+ "endmodule", "extend", "foreach", "isarray", "isgenerator",
+ "isinstance", "isobject", "isstring", "issubclass", "iter", "memoize",
+ "properties", "requiresMainThread", "set", "update", "values",
+ ],
+ use: ["services"]
+});
+
+function Runnable(self, func, args) {
+ return {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRunnable]),
+ run: function () { func.apply(self, args || []); }
+ };
+}
+
function allkeys(obj) {
let ret = {};
try {
@@ -37,7 +98,7 @@ function allkeys(obj) {
}
function debuggerProperties(obj) {
- if (modules.services && services.get("debugger").isOn) {
+ if (loaded.services && services.get("debugger").isOn) {
let ret = {};
services.get("debugger").wrapValue(obj).getProperties(ret, {});
return ret.value;
@@ -58,10 +119,17 @@ if (!Object.getOwnPropertyNames)
function properties(obj, prototypes) {
let orig = obj;
let seen = {};
- for (; obj; obj = prototypes && obj.__proto__)
- for (let key in values(Object.getOwnPropertyNames(obj)))
+ for (; obj; obj = prototypes && obj.__proto__) {
+ try {
+ var iter = values(Object.getOwnPropertyNames(obj));
+ }
+ catch (e) {
+ iter = (prop.name.stringValue for (prop in values(debuggerProperties(obj))));
+ }
+ for (let key in iter)
if (!prototypes || !set.add(seen, key) && obj != orig)
yield key
+ }
}
function values(obj) {
@@ -141,13 +209,17 @@ function isinstance(targ, src) {
}
src = Array.concat(src);
for (var i = 0; i < src.length; i++) {
- if (targ instanceof src[i])
- return true;
- if (typeof src[i] == "string")
- return Object.prototype.toString(targ) == "[object " + src[i] + "]";
- var type = types[typeof targ];
- if (type && issubclass(src[i], type))
- return true;
+ if (typeof src[i] == "string") {
+ if (Object.prototype.toString.call(targ) == "[object " + src[i] + "]")
+ return true;
+ }
+ else {
+ if (targ instanceof src[i])
+ return true;
+ var type = types[typeof targ];
+ if (type && issubclass(src[i], type))
+ return true;
+ }
}
return false;
}
@@ -240,12 +312,12 @@ function curry(fn, length, self, acc) {
if (acc == null)
acc = [];
- return function () {
+ return function curried() {
let args = acc.concat(Array.slice(arguments));
// The curried result should preserve 'this'
if (arguments.length == 0)
- return close(self || this, arguments.callee);
+ return close(self || this, curried);
if (args.length >= length)
return fn.apply(self || this, args);
@@ -255,6 +327,22 @@ function curry(fn, length, self, acc) {
}
/**
+ * Wraps a function so that when called it will always run synchronously
+ * in the main thread. Return values are not preserved.
+ *
+ * @param {function}
+ * @returns {function}
+ */
+function requiresMainThread(callback)
+ function wrapper() {
+ let mainThread = services.get("threadManager").mainThread;
+ if (services.get("threadManager").isMainThread)
+ callback.apply(this, arguments);
+ else
+ mainThread.dispatch(Runnable(this, callback, arguments), mainThread.DISPATCH_NORMAL);
+ }
+
+/**
* Updates an object with the properties of another object. Getters
* and setters are copied as expected. Moreover, any function
* properties receive new 'supercall' and 'superapply' properties,
@@ -263,7 +351,7 @@ function curry(fn, length, self, acc) {
*
* let a = { foo: function (arg) "bar " + arg }
* let b = { __proto__: a }
- * update(b, { foo: function () arguments.callee.supercall(this, "baz") });
+ * update(b, { foo: function foo() foo.supercall(this, "baz") });
*
* a.foo("foo") -> "bar foo"
* b.foo() -> "bar baz"
@@ -393,7 +481,18 @@ function Class() {
});
return Constructor;
}
-Class.toString = function () "[class " + this.constructor.name + "]",
+if (Object.defineProperty)
+ Class.replaceProperty = function (obj, prop, value) {
+ Object.defineProperty(obj, prop, { configurable: true, enumerable: true, value: value, writable: true });
+ return value;
+ };
+else
+ Class.replaceProperty = function (obj, prop, value) {
+ obj.__defineGetter__(prop, function () value);
+ obj.__defineSetter__(prop, function (val) { value = val; });
+ return value;
+ };
+Class.toString = function () "[class " + this.name + "]";
Class.prototype = {
/**
* Initializes new instances of this class. Called automatically
@@ -423,6 +522,25 @@ Class.prototype = {
};
/**
+ * Constructs a mew Module class and instantiates an instance into the current
+ * module global object.
+ *
+ * @param {string} name The name of the instance.
+ * @param {Object} prototype The instance prototype.
+ * @param {Object} classProperties Properties to be applied to the class constructor.
+ * @return {Class}
+ */
+function Module(name, prototype, classProperties, init) {
+ const module = Class(name, prototype, classProperties);
+ let instance = module();
+ module.name = name.toLowerCase();
+ instance.INIT = init || {};
+ currentModule[module.name] = instance;
+ defmodule.modules.push(instance);
+ return module;
+}
+
+/**
* @class Struct
*
* Creates a new Struct constructor, used for creating objects with
@@ -439,7 +557,7 @@ Class.prototype = {
*/
function Struct() {
let args = Array.slice(arguments);
- const Struct = Class("Struct", StructBase, {
+ const Struct = Class("Struct", Struct_Base, {
length: args.length,
members: args
});
@@ -449,7 +567,7 @@ function Struct() {
});
return Struct;
}
-const StructBase = Class("StructBase", {
+let Struct_Base = Class("StructBase", Array, {
init: function () {
for (let i = 0; i < arguments.length; i++)
if (arguments[i] != undefined)
@@ -464,6 +582,11 @@ const StructBase = Class("StructBase", {
return ([k, self[k]] for (k in values(self.members)))
}
}, {
+ fromArray: function (ary) {
+ ary.__proto__ = this.prototype;
+ return ary;
+ },
+
/**
* Sets a lazily constructed default value for a member of
* the struct. The value is constructed once, the first time
@@ -477,17 +600,65 @@ const StructBase = Class("StructBase", {
defaultValue: function (key, val) {
let i = this.prototype.members.indexOf(key);
this.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
- this.prototype.__defineSetter__(i, function (value) {
- this.__defineGetter__(i, function () value);
- this.__defineSetter__(i, function (val) { value = val; });
- });
+ this.prototype.__defineSetter__(i, function (value)
+ Class.replaceProperty(this, i, value));
+ }
+});
+
+const Timer = Class("Timer", {
+ init: function (minInterval, maxInterval, callback) {
+ this._timer = services.create("timer");
+ this.callback = callback;
+ this.minInterval = minInterval;
+ this.maxInterval = maxInterval;
+ this.doneAt = 0;
+ this.latest = 0;
+ },
+
+ notify: function (timer) {
+ this._timer.cancel();
+ this.latest = 0;
+ // minInterval is the time between the completion of the command and the next firing
+ this.doneAt = Date.now() + this.minInterval;
+
+ try {
+ this.callback(this.arg);
+ }
+ finally {
+ this.doneAt = Date.now() + this.minInterval;
+ }
+ },
+
+ tell: function (arg) {
+ if (arguments.length > 0)
+ this.arg = arg;
+
+ let now = Date.now();
+ if (this.doneAt == -1)
+ this._timer.cancel();
+
+ let timeout = this.minInterval;
+ if (now > this.doneAt && this.doneAt > -1)
+ timeout = 0;
+ else if (this.latest)
+ timeout = Math.min(timeout, this.latest - now);
+ else
+ this.latest = now + this.maxInterval;
+
+ this._timer.initWithCallback(this, Math.max(timeout, 0), this._timer.TYPE_ONE_SHOT);
+ this.doneAt = -1;
+ },
+
+ reset: function () {
+ this._timer.cancel();
+ this.doneAt = 0;
+ },
+
+ flush: function () {
+ if (this.doneAt == -1)
+ this.notify();
}
});
-// Add no-sideeffect array methods. Can't set new Array() as the prototype or
-// get length() won't work.
-for (let k in values(["concat", "every", "filter", "forEach", "indexOf", "join", "lastIndexOf",
- "map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"]))
- StructBase.prototype[k] = Array.prototype[k];
/**
* Array utility methods.
@@ -605,9 +776,8 @@ const array = Class("util.Array", Array, {
},
/**
- * Zips the contents of two arrays. The resulting array is twice the
- * length of ary1, with any shortcomings of ary2 replaced with null
- * strings.
+ * Zips the contents of two arrays. The resulting array is the length of
+ * ary1, with any shortcomings of ary2 replaced with null strings.
*
* @param {Array} ary1
* @param {Array} ary2
@@ -616,9 +786,13 @@ const array = Class("util.Array", Array, {
zip: function zip(ary1, ary2) {
let res = []
for(let [i, item] in Iterator(ary1))
- res.push(item, i in ary2 ? ary2[i] : "");
+ res.push([item, i in ary2 ? ary2[i] : ""]);
return res;
}
});
-// vim: set fdm=marker sw=4 ts=4 et:
+endmodule();
+
+// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n");}
+
+// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm
new file mode 100644
index 00000000..8fe022ee
--- /dev/null
+++ b/common/modules/bookmarkcache.jsm
@@ -0,0 +1,162 @@
+// Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+//
+// This work is licensed for reuse under an MIT license. Details are
+// given in the LICENSE.txt file included with this file.
+"use strict";
+
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("bookmarkcache", this, {
+ exports: ["Bookmark", "BookmarkCache", "Keyword", "bookmarkcache"],
+ require: ["services", "util"]
+});
+
+
+const Bookmark = Struct("url", "title", "icon", "keyword", "tags", "id");
+const Keyword = Struct("keyword", "title", "icon", "url");
+Bookmark.defaultValue("icon", function () BookmarkCache.getFavicon(this.url));
+Bookmark.prototype.__defineGetter__("extra", function () [
+ ["keyword", this.keyword, "Keyword"],
+ ["tags", this.tags.join(", "), "Tag"]
+ ].filter(function (item) item[1]));
+
+const bookmarks = services.get("bookmarks");
+const history = services.get("history");
+const tagging = services.get("tagging");
+const name = "bookmark-cache";
+
+const BookmarkCache = Module("BookmarkCache", {
+ init: function init() {
+
+ bookmarks.addObserver(this, false);
+ },
+
+ __iterator__: function () (val for ([, val] in Iterator(self.bookmarks))),
+
+ get bookmarks() Class.replaceProperty(this, "bookmarks", this.load()),
+
+ rootFolders: ["toolbarFolder", "bookmarksMenuFolder", "unfiledBookmarksFolder"]
+ .map(function (s) bookmarks[s]),
+
+ _deleteBookmark: function deleteBookmark(id) {
+ let length = bookmarks.length;
+ bookmarks = bookmarks.filter(function (item) item.id != id);
+ return bookmarks.length < length;
+ },
+
+ _loadBookmark: function loadBookmark(node) {
+ if (node.uri == null) // How does this happen?
+ return false;
+ let uri = util.newURI(node.uri);
+ let keyword = bookmarks.getKeywordForBookmark(node.itemId);
+ let tags = tagging.getTagsForURI(uri, {}) || [];
+ return Bookmark(node.uri, node.title, node.icon && node.icon.spec, keyword, tags, node.itemId);
+ },
+
+ readBookmark: function readBookmark(id) {
+ return {
+ itemId: id,
+ uri: bookmarks.getBookmarkURI(id).spec,
+ title: bookmarks.getItemTitle(id)
+ };
+ },
+
+ findRoot: function findRoot(id) {
+ do {
+ var root = id;
+ id = bookmarks.getFolderIdForItem(id);
+ } while (id != bookmarks.placesRoot && id != root);
+ return root;
+ },
+
+ isBookmark: function (id) this.rootFolders.indexOf(this.findRoot(id)) >= 0,
+
+ isRegularBookmark: function isRegularBookmark(id) {
+ do {
+ var root = id;
+ if (services.get("livemark") && services.get("livemark").isLivemark(id))
+ return false;
+ id = bookmarks.getFolderIdForItem(id);
+ } while (id != bookmarks.placesRoot && id != root);
+ return this.rootFolders.indexOf(root) >= 0;
+ },
+
+ get keywords() [Keyword(k.keyword, k.title, k.icon, k.url) for ([, k] in Iterator(this.bookmarks)) if (k.keyword)],
+
+ // Should be made thread safe.
+ load: function load() {
+ let bookmarks = [];
+
+ let folders = this.rootFolders.slice();
+ let query = history.getNewQuery();
+ let options = history.getNewQueryOptions();
+ while (folders.length > 0) {
+ query.setFolders(folders, 1);
+ folders.shift();
+ let result = history.executeQuery(query, options);
+ let folder = result.root;
+ folder.containerOpen = true;
+
+ // iterate over the immediate children of this folder
+ for (let i = 0; i < folder.childCount; i++) {
+ let node = folder.getChild(i);
+ if (node.type == node.RESULT_TYPE_FOLDER) // folder
+ folders.push(node.itemId);
+ else if (node.type == node.RESULT_TYPE_URI) // bookmark
+ bookmarks.push(this._loadBookmark(node));
+ }
+
+ // close a container after using it!
+ folder.containerOpen = false;
+ }
+
+ return bookmarks;
+ },
+
+ onBeginUpdateBatch: function onBeginUpdateBatch() {},
+ onEndUpdateBatch: function onEndUpdateBatch() {},
+ onItemVisited: function onItemVisited() {},
+ onItemMoved: function onItemMoved() {},
+ onItemAdded: function onItemAdded(itemId, folder, index) {
+ if (bookmarks.getItemType(itemId) == bookmarks.TYPE_BOOKMARK) {
+ if (self.isBookmark(itemId)) {
+ let bmark = this._loadBookmark(this.readBookmark(itemId));
+ this.bookmarks.push(bmark);
+ storage.fireEvent(name, "add", bmark);
+ }
+ }
+ },
+ onItemRemoved: function onItemRemoved(itemId, folder, index) {
+ if (this._deleteBookmark(itemId))
+ storage.fireEvent(name, "remove", itemId);
+ },
+ onItemChanged: function onItemChanged(itemId, property, isAnnotation, value) {
+ if (isAnnotation)
+ return;
+ let bookmark = bookmarks.filter(function (item) item.id == itemId)[0];
+ if (bookmark) {
+ if (property == "tags")
+ value = tagging.getTagsForURI(util.newURI(bookmark.url), {});
+ if (property in bookmark)
+ bookmark[property] = value;
+ storage.fireEvent(name, "change", itemId);
+ }
+ },
+ QueryInterface: function QueryInterface(iid) {
+ if (iid.equals(Ci.nsINavBookmarkObserver) || iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}, {
+ getFavicon: function getFavicon(uri) {
+ try {
+ return service.get("favicon").getFaviconImageForPage(util.newURI(uri)).spec;
+ }
+ catch (e) {
+ return "";
+ }
+ }
+});
+
+endmodule();
+
+// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm
new file mode 100644
index 00000000..fa184d0e
--- /dev/null
+++ b/common/modules/highlight.jsm
@@ -0,0 +1,241 @@
+// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail>
+//
+// This work is licensed for reuse under an MIT license. Details are
+// given in the LICENSE.txt file included with this file.
+"use strict";
+
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("highlight", this, {
+ exports: ["Highlight", "Highlights", "highlight"],
+ require: ["services", "styles"],
+ use: ["template"]
+});
+
+const Highlight = Struct("class", "selector", "filter", "default", "value", "base");
+
+Highlight.defaultValue("filter", function ()
+ this.base ? this.base.filter :
+ ["chrome://dactyl/*",
+ "dactyl:*",
+ "file://*"].concat(highlight.styleableChrome).join(","));
+Highlight.defaultValue("selector", function () highlight.selector(this.class));
+Highlight.defaultValue("value", function () this.default);
+Highlight.defaultValue("base", function () {
+ let base = /^(\w*)/.exec(this.class)[0];
+ return (base != this.class && base in highlight.highlight) ? highlight.highlight[base] : null;
+});
+Highlight.prototype.toString = function ()
+ "Highlight(" + this.class + ")\n\t"
+ + [k + ": " + String.quote(v) for ([k, v] in this)]
+ .join("\n\t");
+
+/**
+ * A class to manage highlighting rules. The parameters are the
+ * standard parameters for any {@link Storage} object.
+ *
+ * @author Kris Maglione <maglione.k@gmail.com>
+ */
+const Highlights = Module("Highlight", {
+ init: function () {
+ this.highlight = {};
+ },
+
+ keys: function keys() Object.keys(this.highlight).sort(),
+
+ __iterator__: function () values(this.highlight),
+
+ get: function (k) this.highlight[k],
+ set: function (key, newStyle, force, append) {
+ let [, class_, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
+
+ if (!(class_ in this.highlight))
+ return "Unknown highlight keyword: " + class_;
+
+ let style = this.highlight[key] || Highlight(key);
+ styles.removeSheet(true, style.selector);
+
+ if (append)
+ newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
+ if (/^\s*$/.test(newStyle))
+ newStyle = null;
+ if (newStyle == null) {
+ if (style.default == null) {
+ delete this.highlight[style.class];
+ styles.removeSheet(true, style.selector);
+ return null;
+ }
+ newStyle = style.default;
+ force = true;
+ }
+
+ let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
+ .replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
+ if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
+ css = style.selector + " { " + css + " }";
+
+ let error = styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
+ if (error)
+ return error;
+ }
+ style.value = newStyle;
+ this.highlight[style.class] = style;
+ return null;
+ },
+
+ /**
+ * Gets a CSS selector given a highlight group.
+ *
+ * @param {string} class
+ */
+ selector: function (class_) {
+ let [, hl, rest] = class_.match(/^(\w*)(.*)/);
+ let pattern = "[dactyl|highlight~=" + hl + "]"
+ if (this.highlight[hl] && this.highlight[hl].class != class_)
+ pattern = this.highlight[hl].selector;
+ return pattern + rest;
+ },
+
+ /**
+ * Clears all highlighting rules. Rules with default values are
+ * reset.
+ */
+ clear: function () {
+ for (let [k, v] in Iterator(this.highlight))
+ this.set(k, null, true);
+ },
+
+ /**
+ * Bulk loads new CSS rules.
+ *
+ * @param {string} css The rules to load. See {@link Highlights#css}.
+ */
+ loadCSS: function (css) {
+ css.replace(/^(\s*\S*\s+)\{((?:.|\n)*?)\}\s*$/gm, function (_, _1, _2) _1 + " " + _2.replace(/\n\s*/g, " "))
+ .split("\n").filter(function (s) /\S/.test(s))
+ .forEach(function (style) {
+ style = Highlight.apply(Highlight,
+ Array.slice(style.match(/^\s*((?:[^,\s]|\s\S)+)(?:,((?:[^,\s]|\s\S)+)?)?(?:,((?:[^,\s]|\s\S)+))?\s*(.*)$/),
+ 1));
+ if (/^[>+ ]/.test(style.selector))
+ style.selector = this.selector(style.class) + style.selector;
+
+ let old = this.highlight[style.class];
+ this.highlight[style.class] = style;
+ if (old && old.value != old.default)
+ style.value = old.value;
+ }, this);
+ for (let [class_, hl] in Iterator(this.highlight))
+ if (hl.value == hl.default)
+ this.set(class_);
+ }
+}, {
+}, {
+ commands: function (dactyl, modules) {
+ const commands = modules.commands;
+ commands.add(["colo[rscheme]"],
+ "Load a color scheme",
+ function (args) {
+ let scheme = args[0];
+
+ if (scheme == "default")
+ highlight.clear();
+ else
+ dactyl.assert(modules.io.sourceFromRuntimePath(["colors/" + scheme + ".vimp"]),
+ "E185: Cannot find color scheme " + scheme);
+ modules.autocommands.trigger("ColorScheme", { name: scheme });
+ },
+ {
+ argCount: "1",
+ completer: function (context) completion.colorScheme(context)
+ });
+
+ commands.add(["hi[ghlight]"],
+ "Set the style of certain display elements",
+ function (args) {
+ let style = <![CDATA[
+ ;
+ display: inline-block !important;
+ position: static !important;
+ margin: 0px !important; padding: 0px !important;
+ width: 3em !important; min-width: 3em !important; max-width: 3em !important;
+ height: 1em !important; min-height: 1em !important; max-height: 1em !important;
+ overflow: hidden !important;
+ ]]>;
+ let clear = args[0] == "clear";
+ if (clear)
+ args.shift();
+
+ let [key, css] = args;
+ dactyl.assert(!(clear && css), "E488: Trailing characters");
+
+ if (!css && !clear)
+ modules.commandline.commandOutput(
+ template.tabular(["Key", "Sample", "CSS"],
+ ["padding: 0 1em 0 0; vertical-align: top",
+ "text-align: center"],
+ ([h.class,
+ <span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,
+ template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)]
+ for (h in highlight)
+ if (!key || h.class.indexOf(key) > -1))));
+ else if (!key && clear)
+ highlight.clear();
+ else {
+ let error = highlight.set(key, css, clear, "-append" in args);
+ if (error)
+ dactyl.echoerr(error);
+ }
+ },
+ {
+ // TODO: add this as a standard highlight completion function?
+ completer: function (context, args) {
+ // Complete a highlight group on :hi clear ...
+ if (args.completeArg > 0 && args[0] == "clear")
+ args.completeArg = args.completeArg > 1 ? -1 : 0;
+
+ if (args.completeArg == 0)
+ context.completions = [[v.class, v.value] for (v in highlight)];
+ else if (args.completeArg == 1) {
+ let hl = highlight.get(args[0]);
+ if (hl)
+ context.completions = [[hl.value, "Current Value"], [hl.default || "", "Default Value"]];
+ }
+ },
+ hereDoc: true,
+ literal: 1,
+ options: [{ names: ["-append", "-a"], description: "Append new CSS to the existing value" }],
+ serialize: function () [
+ {
+ command: this.name,
+ arguments: [k],
+ literalArg: v
+ }
+ for ([k, v] in Iterator(highlight))
+ if (v.value != v.default)
+ ]
+ });
+ },
+ completion: function (dactyl, modules) {
+ const completion = modules.completion;
+ completion.colorScheme = function colorScheme(context) {
+ context.title = ["Color Scheme", "Runtime Path"];
+ context.keys = { text: function (f) f.leafName.replace(/\.vimp$/, ""), description: ".parent.path" };
+ context.completions = util.Array.flatten(
+ modules.io.getRuntimeDirectories("colors").map(
+ function (dir) dir.readDirectory().filter(
+ function (file) /\.vimp$/.test(file.leafName))))
+
+ };
+
+ completion.highlightGroup = function highlightGroup(context) {
+ context.title = ["Highlight Group", "Value"];
+ context.completions = [[v.class, v.value] for (v in highlight)];
+ };
+ }
+});
+
+// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
+
+endmodule();
+
+// vim:se fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/content/services.js b/common/modules/services.jsm
index 19cbfb09..afef4cd8 100644
--- a/common/content/services.js
+++ b/common/modules/services.jsm
@@ -4,14 +4,12 @@
// given in the LICENSE.txt file included with this file.
"use strict";
-/** @scope modules */
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("services", this, {
+ exports: ["Services", "services"]
+});
-/**
- * Cached XPCOM services and classes.
- *
- * @constructor
- */
-const Services = Module("services", {
+const Services = Module("Services", {
init: function () {
this.classes = {};
this.services = {};
@@ -54,9 +52,6 @@ const Services = Module("services", {
this.addClass("timer", "@mozilla.org/timer;1", Ci.nsITimer);
this.addClass("xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
this.addClass("zipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter);
-
- if (!this.get("extensionManager"))
- Components.utils.import("resource://gre/modules/AddonManager.jsm", modules);
},
_create: function (classes, ifaces, meth) {
@@ -120,10 +115,19 @@ const Services = Module("services", {
*/
create: function (name) this.classes[name]()
}, {
+}, {
+ init: function (dactyl, modules) {
+ if (!this.get("extensionManager"))
+ Components.utils.import("resource://gre/modules/AddonManager.jsm", modules);
+ },
javascript: function (dactyl, modules) {
- JavaScript.setCompleter(this.get, [function () services.services]);
- JavaScript.setCompleter(this.create, [function () [[c, ""] for (c in services.classes)]]);
+ modules.JavaScript.setCompleter(this.get, [function () services.services]);
+ modules.JavaScript.setCompleter(this.create, [function () [[c, ""] for (c in services.classes)]]);
}
});
-// vim: set fdm=marker sw=4 ts=4 et:
+endmodule();
+
+// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n");}
+
+// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm
index f0fd006e..4d09d380 100644
--- a/common/modules/storage.jsm
+++ b/common/modules/storage.jsm
@@ -21,107 +21,22 @@
}}} ***** END LICENSE BLOCK *****/
"use strict";
-var EXPORTED_SYMBOLS = ["storage", "Timer"];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-// XXX: does not belong here
-function Timer(minInterval, maxInterval, callback) {
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- this.doneAt = 0;
- this.latest = 0;
- this.notify = function (aTimer) {
- timer.cancel();
- this.latest = 0;
- // minInterval is the time between the completion of the command and the next firing
- this.doneAt = Date.now() + minInterval;
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("storage", this, {
+ exports: ["File", "storage"],
+ require: ["services", "util"]
+});
- try {
- callback(this.arg);
- }
- finally {
- this.doneAt = Date.now() + minInterval;
- }
- };
- this.tell = function (arg) {
- if (arguments.length > 0)
- this.arg = arg;
-
- let now = Date.now();
- if (this.doneAt == -1)
- timer.cancel();
-
- let timeout = minInterval;
- if (now > this.doneAt && this.doneAt > -1)
- timeout = 0;
- else if (this.latest)
- timeout = Math.min(timeout, this.latest - now);
- else
- this.latest = now + maxInterval;
-
- timer.initWithCallback(this, Math.max(timeout, 0), timer.TYPE_ONE_SHOT);
- this.doneAt = -1;
- };
- this.reset = function () {
- timer.cancel();
- this.doneAt = 0;
- };
- this.flush = function () {
- if (this.doneAt == -1)
- this.notify();
- };
-}
+var prefService = services.get("pref").getBranch("extensions.dactyl.datastore.");
+
+const win32 = services.get("runtime").OS == "Win32";
function getFile(name) {
let file = storage.infoPath.clone();
file.append(name);
- return file;
-}
-
-function readFile(file) {
- let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
- let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
-
- try {
- fileStream.init(file, -1, 0, 0);
- stream.init(fileStream, "UTF-8", 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
-
- let hunks = [];
- let res = {};
- while (stream.readString(4096, res) != 0)
- hunks.push(res.value);
-
- stream.close();
- fileStream.close();
-
- return hunks.join("");
- }
- catch (e) {}
-}
-
-function writeFile(file, data) {
- if (!file.exists())
- file.create(file.NORMAL_FILE_TYPE, parseInt('0600', 8));
-
- let fileStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
- let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
-
- fileStream.init(file, 0x20 | 0x08 | 0x02, parseInt('0600', 8), 0); // PR_TRUNCATE | PR_CREATE | PR_WRITE
- stream.init(fileStream, "UTF-8", 0, 0);
-
- stream.writeString(data);
-
- stream.close();
- fileStream.close();
+ return File(file);
}
-var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
-var prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService)
- .getBranch("extensions.dactyl.datastore.");
-var json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
-
function getCharPref(name) {
try {
return prefService.getComplexValue(name, Ci.nsISupportsString).data;
@@ -140,9 +55,9 @@ function loadPref(name, store, type) {
if (store)
var pref = getCharPref(name);
if (!pref && storage.infoPath)
- var file = readFile(getFile(name));
+ var file = getFile(name).read();
if (pref || file)
- var result = json.decode(pref || file);
+ var result = services.get("json").decode(pref || file);
if (pref) {
prefService.clearUserPref(name);
savePref({ name: name, store: true, serial: pref });
@@ -157,122 +72,110 @@ function savePref(obj) {
if (obj.privateData && storage.privateMode)
return;
if (obj.store && storage.infoPath)
- writeFile(getFile(obj.name), obj.serial);
+ getFile(obj.name).write(obj.serial);
}
-var prototype = {
+const StoreBase = Class("StoreBase", {
OPTIONS: ["privateData"],
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg); },
+ get serial() services.get("json").encode(this._object),
save: function () { savePref(this); },
- init: function (name, store, data, options) {
+ init: function (name, store, load, options) {
+ this._load = load;
+
this.__defineGetter__("store", function () store);
this.__defineGetter__("name", function () name);
for (let [k, v] in Iterator(options))
if (this.OPTIONS.indexOf(k) >= 0)
this[k] = v;
this.reload();
- }
-};
-
-function ObjectStore(name, store, load, options) {
- var object = {};
-
- this.reload = function reload() {
- object = load() || {};
+ },
+ reload: function reload() {
+ this._object = this._load() || this._constructor();
this.fireEvent("change", null);
- };
+ }
+});
- this.init.apply(this, arguments);
- this.__defineGetter__("serial", function () json.encode(object));
+const ObjectStore = Class("ObjectStore", StoreBase, {
+ _constructor: Object,
- this.set = function set(key, val) {
- var defined = key in object;
- var orig = object[key];
- object[key] = val;
+ set: function set(key, val) {
+ var defined = key in this._object;
+ var orig = this._object[key];
+ this._object[key] = val;
if (!defined)
this.fireEvent("add", key);
else if (orig != val)
this.fireEvent("change", key);
- };
+ },
- this.remove = function remove(key) {
- var ret = object[key];
- delete object[key];
+ remove: function remove(key) {
+ var ret = this._object[key];
+ delete this._object[key];
this.fireEvent("remove", key);
return ret;
- };
-
- this.get = function get(val, default_) val in object ? object[val] : default_;
+ },
- this.clear = function () {
- object = {};
- };
+ get: function get(val, default_) val in this._object ? this._object[val] : default_,
- this.__iterator__ = function () Iterator(object);
-}
-ObjectStore.prototype = prototype;
+ clear: function () {
+ this._object = {};
+ },
-function ArrayStore(name, store, load, options) {
- var array = [];
+ __iterator__: function () Iterator(this._object),
+});
- this.reload = function reload() {
- array = load() || [];
- this.fireEvent("change", null);
- };
+const ArrayStore = Class("ArrayStore", StoreBase, {
+ _constructor: Array,
- this.init.apply(this, arguments);
- this.__defineGetter__("serial", function () json.encode(array));
- this.__defineGetter__("length", function () array.length);
+ get length() this._object.length,
- this.set = function set(index, value) {
- var orig = array[index];
- array[index] = value;
+ set: function set(index, value) {
+ var orig = this._object[index];
+ this._object[index] = value;
this.fireEvent("change", index);
- };
+ },
- this.push = function push(value) {
- array.push(value);
- this.fireEvent("push", array.length);
- };
+ push: function push(value) {
+ this._object.push(value);
+ this.fireEvent("push", this._object.length);
+ },
- this.pop = function pop(value) {
- var ret = array.pop();
- this.fireEvent("pop", array.length);
+ pop: function pop(value) {
+ var ret = this._object.pop();
+ this.fireEvent("pop", this._object.length);
return ret;
- };
+ },
- this.truncate = function truncate(length, fromEnd) {
- var ret = array.length;
- if (array.length > length) {
+ truncate: function truncate(length, fromEnd) {
+ var ret = this._object.length;
+ if (this._object.length > length) {
if (fromEnd)
- array.splice(0, array.length - length);
- array.length = length;
+ this._object.splice(0, this._object.length - length);
+ this._object.length = length;
this.fireEvent("truncate", length);
}
return ret;
- };
+ },
// XXX: Awkward.
- this.mutate = function mutate(aFuncName) {
- var funcName = aFuncName;
- arguments[0] = array;
- array = Array[funcName].apply(Array, arguments);
+ mutate: function mutate(funcName) {
+ var _funcName = funcName;
+ arguments[0] = this._object;
+ this._object = Array[_funcName].apply(Array, arguments);
this.fireEvent("change", null);
- };
+ },
- this.get = function get(index) {
- return index >= 0 ? array[index] : array[array.length + index];
- };
+ get: function get(index) index >= 0 ? this._object[index] : this._object[this._object.length + index],
- this.__iterator__ = function () Iterator(array);
-}
-ArrayStore.prototype = prototype;
+ __iterator__: function () Iterator(this._object),
+});
var keys = {};
var observers = {};
var timers = {};
-var storage = {
+const Storage = Module("Storage", {
alwaysReload: {},
newObject: function newObject(key, constructor, params) {
if (!(key in keys) || params.reload || this.alwaysReload[key]) {
@@ -363,6 +266,304 @@ var storage = {
this.load(key);
return this._privateMode = Boolean(val);
}
-};
+}, {
+}, {
+ init: function (dactyl, modules) {
+ let infoPath = services.create("file");
+ infoPath.initWithPath(File.expandPath(modules.IO.runtimePath.replace(/,.*/, "")));
+ infoPath.append("info");
+ infoPath.append(dactyl.profileName);
+ storage.infoPath = infoPath;
+ }
+});
+
+/**
+ * @class File A class to wrap nsIFile objects and simplify operations
+ * thereon.
+ *
+ * @param {nsIFile|string} path Expanded according to {@link IO#expandPath}
+ * @param {boolean} checkPWD Whether to allow expansion relative to the
+ * current directory. @default true
+ */
+const File = Class("File", {
+ init: function (path, checkPWD) {
+ let file = services.create("file");
+
+ if (path instanceof Ci.nsIFile)
+ file = path;
+ else if (/file:\/\//.test(path))
+ file = services.create("file:").getFileFromURLSpec(path);
+ else {
+ let expandedPath = File.expandPath(path);
+
+ if (!File.isAbsolutePath(expandedPath) && checkPWD)
+ file = File.joinPaths(checkPWD, expandedPath);
+ else
+ file.initWithPath(expandedPath);
+ }
+ let self = XPCSafeJSObjectWrapper(file);
+ self.__proto__ = File.prototype;
+ return self;
+ },
+
+ /**
+ * Iterates over the objects in this directory.
+ */
+ iterDirectory: function () {
+ if (!this.isDirectory())
+ throw Error("Not a directory");
+ let entries = this.directoryEntries;
+ while (entries.hasMoreElements())
+ yield File(entries.getNext().QueryInterface(Ci.nsIFile));
+ },
+
+ /**
+ * Reads this file's entire contents in "text" mode and returns the
+ * content as a string.
+ *
+ * @param {string} encoding The encoding from which to decode the file.
+ * @default options["fileencoding"]
+ * @returns {string}
+ */
+ read: function (encoding) {
+ let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
+
+ if (!encoding)
+ encoding = File.defaultEncoding;
+
+ ifstream.init(this, -1, 0, 0);
+ icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
+
+ let buffer = [];
+ let str = {};
+ while (icstream.readString(4096, str) != 0)
+ buffer.push(str.value);
+
+ icstream.close();
+ ifstream.close();
+ return buffer.join("");
+ },
+
+ /**
+ * Returns the list of files in this directory.
+ *
+ * @param {boolean} sort Whether to sort the returned directory
+ * entries.
+ * @returns {nsIFile[]}
+ */
+ readDirectory: function (sort) {
+ if (!this.isDirectory())
+ throw Error("Not a directory");
+
+ let array = [e for (e in this.iterDirectory())];
+ if (sort)
+ array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
+ return array;
+ },
+
+ /**
+ * Writes the string <b>buf</b> to this file.
+ *
+ * @param {string} buf The file content.
+ * @param {string|number} mode The file access mode, a bitwise OR of
+ * the following flags:
+ * {@link #MODE_RDONLY}: 0x01
+ * {@link #MODE_WRONLY}: 0x02
+ * {@link #MODE_RDWR}: 0x04
+ * {@link #MODE_CREATE}: 0x08
+ * {@link #MODE_APPEND}: 0x10
+ * {@link #MODE_TRUNCATE}: 0x20
+ * {@link #MODE_SYNC}: 0x40
+ * Alternatively, the following abbreviations may be used:
+ * ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE}
+ * ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND}
+ * @default ">"
+ * @param {number} perms The file mode bits of the created file. This
+ * is only used when creating a new file and does not change
+ * permissions if the file exists.
+ * @default 0644
+ * @param {string} encoding The encoding to used to write the file.
+ * @default options["fileencoding"]
+ */
+ write: function (buf, mode, perms, encoding) {
+ let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ function getStream(defaultChar) {
+ let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
+ stream.init(ofstream, encoding, 0, defaultChar);
+ return stream;
+ }
+
+ if (!encoding)
+ encoding = File.defaultEncoding;
+
+ if (mode == ">>")
+ mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_APPEND;
+ else if (!mode || mode == ">")
+ mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE;
+
+ if (!perms)
+ perms = parseInt('0644', 8);
+
+ ofstream.init(this, mode, perms, 0);
+ let ocstream = getStream(0);
+ try {
+ ocstream.writeString(buf);
+ }
+ catch (e) {
+ if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
+ ocstream = getStream("?".charCodeAt(0));
+ ocstream.writeString(buf);
+ return false;
+ }
+ else
+ throw e;
+ }
+ finally {
+ try {
+ ocstream.close();
+ }
+ catch (e) {}
+ ofstream.close();
+ }
+ return true;
+ }
+}, {
+ /**
+ * @property {number} Open for reading only.
+ * @final
+ */
+ MODE_RDONLY: 0x01,
+
+ /**
+ * @property {number} Open for writing only.
+ * @final
+ */
+ MODE_WRONLY: 0x02,
+
+ /**
+ * @property {number} Open for reading and writing.
+ * @final
+ */
+ MODE_RDWR: 0x04,
+
+ /**
+ * @property {number} If the file does not exist, the file is created.
+ * If the file exists, this flag has no effect.
+ * @final
+ */
+ MODE_CREATE: 0x08,
+
+ /**
+ * @property {number} The file pointer is set to the end of the file
+ * prior to each write.
+ * @final
+ */
+ MODE_APPEND: 0x10,
+
+ /**
+ * @property {number} If the file exists, its length is truncated to 0.
+ * @final
+ */
+ MODE_TRUNCATE: 0x20,
+
+ /**
+ * @property {number} If set, each write will wait for both the file
+ * data and file status to be physically updated.
+ * @final
+ */
+ MODE_SYNC: 0x40,
+
+ /**
+ * @property {number} With MODE_CREATE, if the file does not exist, the
+ * file is created. If the file already exists, no action and NULL
+ * is returned.
+ * @final
+ */
+ MODE_EXCL: 0x80,
+
+ /**
+ * @property {string} The current platform's path seperator.
+ */
+ get PATH_SEP() {
+ delete this.PATH_SEP;
+ let f = services.get("directory").get("CurProcD", Ci.nsIFile);
+ f.append("foo");
+ return this.PATH_SEP = f.path.substr(f.parent.path.length, 1);
+ },
+
+ defaultEncoding: "UTF-8",
+
+ expandPath: function (path, relative) {
+
+ // expand any $ENV vars - this is naive but so is Vim and we like to be compatible
+ // TODO: Vim does not expand variables set to an empty string (and documents it).
+ // Kris reckons we shouldn't replicate this 'bug'. --djk
+ // TODO: should we be doing this for all paths?
+ function expand(path) path.replace(
+ !win32 ? /\$(\w+)\b|\${(\w+)}/g
+ : /\$(\w+)\b|\${(\w+)}|%(\w+)%/g,
+ function (m, n1, n2, n3) services.get("environment").get(n1 || n2 || n3) || m
+ );
+ path = expand(path);
+
+ // expand ~
+ // Yuck.
+ if (!relative && RegExp("~(?:$|[/" + util.escapeRegex(File.PATH_SEP) + "])").test(path)) {
+ // Try $HOME first, on all systems
+ let home = services.get("environment").get("HOME");
+
+ // Windows has its own idiosyncratic $HOME variables.
+ if (!home && win32)
+ home = services.get("environment").get("USERPROFILE") ||
+ services.get("environment").get("HOMEDRIVE") + services.get("environment").get("HOMEPATH");
+
+ path = home + path.substr(1);
+ }
+
+ // TODO: Vim expands paths twice, once before checking for ~, once
+ // after, but doesn't document it. Is this just a bug? --Kris
+ path = expand(path);
+ return path.replace("/", File.PATH_SEP, "g");
+ },
+
+ expandPathList: function (list) list.map(this.expandPath),
+
+ getPathsFromPathList: function (list) {
+ if (!list)
+ return [];
+ // empty list item means the current directory
+ return list.replace(/,$/, "").split(",")
+ .map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir);
+ },
+
+ isAbsolutePath: function (path) {
+ try {
+ services.create("file").initWithPath(path);
+ return true;
+ }
+ catch (e) {
+ return false;
+ }
+ },
+
+ joinPaths: function (head, tail) {
+ let path = this(head);
+ try {
+ // FIXME: should only expand env vars and normalise path separators
+ path.appendRelativePath(this.expandPath(tail, true));
+ }
+ catch (e) {
+ return { exists: function () false, __noSuchMethod__: function () { throw e; } };
+ }
+ return path;
+ },
+
+ replacePathSep: function (path) path.replace("/", File.PATH_SEP, "g")
+});
+
+// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n");}
+
+endmodule();
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm
new file mode 100644
index 00000000..8b8b1c1c
--- /dev/null
+++ b/common/modules/styles.jsm
@@ -0,0 +1,373 @@
+// Copyright (c) 2008-2010 by Kris Maglione <maglione.k at Gmail>
+//
+// This work is licensed for reuse under an MIT license. Details are
+// given in the LICENSE.txt file included with this file.
+"use strict";
+
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("styles", this, {
+ exports: ["Style", "Styles", "styles"],
+ require: ["services", "util"]
+});
+
+const sss = services.get("stylesheet");
+function cssUri(css) "chrome-data:text/css," + encodeURI(css);
+const namespace = "@namespace html " + XHTML.uri.quote() + ";\n" +
+ "@namespace xul " + XUL.uri.quote() + ";\n" +
+ "@namespace dactyl " + NS.uri.quote() + ";\n";
+
+const Sheet = Struct("name", "id", "sites", "css", "system", "agent");
+Sheet.prototype.__defineGetter__("fullCSS", function wrapCSS() {
+ let filter = this.sites;
+ let css = this.css;
+ if (filter[0] == "*")
+ return namespace + css;
+
+ let selectors = filter.map(function (part)
+ (/[*]$/.test(part) ? "url-prefix" :
+ /[\/:]/.test(part) ? "url"
+ : "domain")
+ + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")')
+ .join(", ");
+ return "/* Dactyl style #" + this.id + " */ " + namespace + " @-moz-document " + selectors + "{\n" + css + "\n}\n";
+});
+Sheet.prototype.__defineGetter__("enabled", function () this._enabled);
+Sheet.prototype.__defineSetter__("enabled", function (on) {
+ this._enabled = Boolean(on);
+ let meth = on ? "registerSheet" : "unregisterSheet";
+
+ styles[meth](cssUri(this.fullCSS));
+ if (this.agent)
+ styles[meth](cssUri(this.fullCSS), true);
+});
+
+/**
+ * Manages named and unnamed user style sheets, which apply to both
+ * chrome and content pages. The parameters are the standard
+ * parameters for any {@link Storage} object.
+ *
+ * @author Kris Maglione <maglione.k@gmail.com>
+ */
+const Styles = Module("Styles", {
+ init: function() {
+ this._id = 0;
+ this.userSheets = [];
+ this.systemSheets = [];
+ this.userNames = {};
+ this.systemNames = {};
+ },
+
+ get sites() array(this.userSheets).map(function (s) s.sites).flatten().uniq().__proto__,
+
+ __iterator__: function () Iterator(this.userSheets.concat(this.systemSheets)),
+
+ /**
+ * Add a new style sheet.
+ *
+ * @param {boolean} system Declares whether this is a system or
+ * user sheet. System sheets are used internally by
+ * @dactyl.
+ * @param {string} name The name given to the style sheet by
+ * which it may be later referenced.
+ * @param {string} filter The sites to which this sheet will
+ * apply. Can be a domain name or a URL. Any URL ending in
+ * "*" is matched as a prefix.
+ * @param {string} css The CSS to be applied.
+ */
+ addSheet: function addSheet(system, name, filter, css, agent) {
+ let sheets = system ? this.systemSheets : this.userSheets;
+ let names = system ? this.systemNames : this.userNames;
+ if (name && name in names)
+ this.removeSheet(system, name);
+
+ let sheet = Sheet(name, this._id++, filter.split(",").filter(util.identity), String(css), null, system, agent);
+
+ try {
+ sheet.enabled = true;
+ }
+ catch (e) {
+ return e.echoerr || e;
+ }
+ sheets.push(sheet);
+
+ if (name)
+ names[name] = sheet;
+ return null;
+ },
+
+ /**
+ * Get a sheet with a given name or index.
+ *
+ * @param {boolean} system
+ * @param {string or number} sheet The sheet to retrieve. Strings indicate
+ * sheet names, while numbers indicate indices.
+ */
+ get: function getget(system, sheet) {
+ let sheets = system ? this.systemSheets : this.userSheets;
+ let names = system ? this.systemNames : this.userNames;
+ if (typeof sheet === "number")
+ return sheets[sheet];
+ return names[sheet];
+ },
+
+ /**
+ * Find sheets matching the parameters. See {@link #addSheet}
+ * for parameters.
+ *
+ * @param {boolean} system
+ * @param {string} name
+ * @param {string} filter
+ * @param {string} css
+ * @param {number} index
+ */
+ findSheets: function findSheets(system, name, filter, css, index) {
+ let sheets = system ? this.systemSheets : this.userSheets;
+ let names = system ? this.systemNames : this.userNames;
+
+ // Grossly inefficient.
+ let matches = [k for ([k, v] in Iterator(sheets))];
+ if (index)
+ matches = String(index).split(",").filter(function (i) i in sheets);
+ if (name)
+ matches = matches.filter(function (i) sheets[i] == names[name]);
+ if (css)
+ matches = matches.filter(function (i) sheets[i].css == css);
+ if (filter)
+ matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0);
+ return matches.map(function (i) sheets[i]);
+ },
+
+ /**
+ * Remove a style sheet. See {@link #addSheet} for parameters.
+ * In cases where <b>filter</b> is supplied, the given filters
+ * are removed from matching sheets. If any remain, the sheet is
+ * left in place.
+ *
+ * @param {boolean} system
+ * @param {string} name
+ * @param {string} filter
+ * @param {string} css
+ * @param {number} index
+ */
+ removeSheet: function removeSheet(system, name, filter, css, index) {
+ let self = this;
+ if (arguments.length == 1) {
+ var matches = [system];
+ system = matches[0].system;
+ }
+ let sheets = system ? this.systemSheets : this.userSheets;
+ let names = system ? this.systemNames : this.userNames;
+
+ if (filter && filter.indexOf(",") > -1)
+ return filter.split(",").reduce(
+ function (n, f) n + self.removeSheet(system, name, f, index), 0);
+
+ if (filter == undefined)
+ filter = "";
+
+ if (!matches)
+ matches = this.findSheets(system, name, filter, css, index);
+ if (matches.length == 0)
+ return null;
+
+ for (let [, sheet] in Iterator(matches.reverse())) {
+ sheet.enabled = false;
+ if (name)
+ delete names[name];
+ if (sheets.indexOf(sheet) > -1)
+ sheets.splice(sheets.indexOf(sheet), 1);
+
+ /* Re-add if we're only changing the site filter. */
+ if (filter) {
+ let sites = sheet.sites.filter(function (f) f != filter);
+ if (sites.length)
+ this.addSheet(system, name, sites.join(","), css, sheet.agent);
+ }
+ }
+ return matches.length;
+ },
+
+ /**
+ * Register a user style sheet at the given URI.
+ *
+ * @param {string} url The URI of the sheet to register.
+ * @param {boolean} agent If true, sheet is registered as an agent sheet.
+ * @param {boolean} reload Whether to reload any sheets that are
+ * already registered.
+ */
+ registerSheet: function registerSheet(url, agent, reload) {
+ let uri = services.get("io").newURI(url, null, null);
+ if (reload)
+ this.unregisterSheet(url, agent);
+ if (reload || !sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
+ sss.loadAndRegisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
+ },
+
+ /**
+ * Unregister a sheet at the given URI.
+ *
+ * @param {string} url The URI of the sheet to unregister.
+ * @param {boolean} agent If true, sheet is registered as an agent sheet.
+ */
+ unregisterSheet: function unregisterSheet(url, agent) {
+ let uri = services.get("io").newURI(url, null, null);
+ if (sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
+ sss.unregisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
+ },
+}, {
+ completeSite: function (context, content) {
+ context.anchored = false;
+ try {
+ context.fork("current", 0, this, function (context) {
+ context.title = ["Current Site"];
+ context.completions = [
+ [content.location.host, "Current Host"],
+ [content.location.href, "Current URL"]
+ ];
+ });
+ }
+ catch (e) {}
+ context.fork("others", 0, this, function (context) {
+ context.title = ["Site"];
+ context.completions = [[s, ""] for ([, s] in Iterator(styles.sites))];
+ });
+ }
+}, {
+ commands: function (dactyl, modules, window) {
+ const commands = modules.commands;
+ commands.add(["sty[le]"],
+ "Add or list user styles",
+ function (args) {
+ let [filter, css] = args;
+ let name = args["-name"];
+
+ if (!css) {
+ let list = Array.concat([i for (i in Iterator(styles.userNames))],
+ [i for (i in Iterator(styles.userSheets)) if (!i[1].name)]);
+ modules.commandline.commandOutput(
+ template.tabular(["", "Name", "Filter", "CSS"],
+ ["min-width: 1em; text-align: center; color: red; font-weight: bold;",
+ "padding: 0 1em 0 1ex; vertical-align: top;",
+ "padding: 0 1em 0 0; vertical-align: top;"],
+ ([sheet.enabled ? "" : "\u00d7",
+ key,
+ sheet.sites.join(","),
+ sheet.css]
+ for ([i, [key, sheet]] in Iterator(list))
+ if ((!filter || sheet.sites.indexOf(filter) >= 0) && (!name || sheet.name == name)))));
+ }
+ else {
+ if ("-append" in args) {
+ let sheet = styles.get(false, name);
+ if (sheet) {
+ filter = sheet.sites.concat(filter).join(",");
+ css = sheet.css + " " + css;
+ }
+ }
+ let err = styles.addSheet(false, name, filter, css);
+ if (err)
+ dactyl.echoerr(err);
+ }
+ },
+ {
+ bang: true,
+ completer: function (context, args) {
+ let compl = [];
+ if (args.completeArg == 0)
+ Styles.completeSite(context, window.content);
+ else if (args.completeArg == 1) {
+ let sheet = styles.get(false, args["-name"]);
+ if (sheet)
+ context.completions = [[sheet.css, "Current Value"]];
+ }
+ },
+ hereDoc: true,
+ literal: 1,
+ options: [
+ {
+ names: ["-name", "-n"],
+ description: "The name of this stylesheet",
+ completer: function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))],
+ type: modules.CommandOption.STRING
+ },
+ { names: ["-append", "-a"], description: "Append site filter and css to an existing, matching sheet" }
+ ],
+ serialize: function () [
+ {
+ command: this.name,
+ arguments: [sty.sites.join(",")],
+ bang: true,
+ literalArg: sty.css,
+ options: sty.name ? { "-name": sty.name } : {}
+ } for ([k, sty] in Iterator(styles.userSheets))
+ ]
+ });
+
+ [
+ {
+ name: ["stylee[nable]", "stye[nable]"],
+ desc: "Enable a user style sheet",
+ action: function (sheet) sheet.enabled = true,
+ filter: function (sheet) !sheet.enabled
+ },
+ {
+ name: ["styled[isable]", "styd[isable]"],
+ desc: "Disable a user style sheet",
+ action: function (sheet) sheet.enabled = false,
+ filter: function (sheet) sheet.enabled
+ },
+ {
+ name: ["stylet[oggle]", "styt[oggle]"],
+ desc: "Toggle a user style sheet",
+ action: function (sheet) sheet.enabled = !sheet.enabled
+ },
+ {
+ name: ["dels[tyle]"],
+ desc: "Remove a user style sheet",
+ action: function (sheet) styles.removeSheet(sheet)
+ }
+ ].forEach(function (cmd) {
+ commands.add(cmd.name, cmd.desc,
+ function (args) {
+ styles.findSheets(false, args["-name"], args[0], args.literalArg, args["-index"])
+ .forEach(cmd.action);
+ },
+ {
+ completer: function (context) { context.completions = styles.sites.map(function (site) [site, ""]); },
+ literal: 1,
+ options: [
+ {
+ names: ["-index", "-i"],
+ type: modules.CommandOption.INT,
+ completer: function (context) {
+ context.compare = CompletionContext.Sort.number;
+ return [[i, <>{sheet.sites.join(",")}: {sheet.css.replace("\n", "\\n")}</>]
+ for ([i, sheet] in styles.userSheets)
+ if (!cmd.filter || cmd.filter(sheet))];
+ },
+ }, {
+ names: ["-name", "-n"],
+ type: modules.CommandOption.STRING,
+ completer: function () [[name, sheet.css]
+ for ([name, sheet] in Iterator(styles.userNames))
+ if (!cmd.filter || cmd.filter(sheet))]
+ }
+ ]
+ });
+ });
+ },
+ javascript: function (dactyl, modules, window) {
+ modules.JavaScript.setCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
+ [ // Prototype: (system, name, filter, css, index)
+ null,
+ function (context, obj, args) args[0] ? this.systemNames : this.userNames,
+ function (context, obj, args) Styles.completeSite(context, window.content),
+ null,
+ function (context, obj, args) args[0] ? this.systemSheets : this.userSheets
+ ]);
+ }
+});
+
+endmodule();
+
+// vim:se fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/content/template.js b/common/modules/template.jsm
index ffe9ea1e..f444c184 100644
--- a/common/content/template.js
+++ b/common/modules/template.jsm
@@ -4,9 +4,17 @@
// given in the LICENSE.txt file included with this file.
"use strict";
-/** @scope modules */
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("template", this, {
+ exports: ["Template", "template"],
+ require: ["util"]
+});
+
+default xml namespace = XHTML;
+XML.ignoreWhiteSpace = true;
+XML.prettyPrinting = false;
-const Template = Module("template", {
+const Template = Module("Template", {
add: function add(a, b) a + b,
join: function join(c) function (a, b) a + c + b,
@@ -38,6 +46,23 @@ const Template = Module("template", {
return <>{xml}</>;
},
+ bookmarkDescription: function (item, text)
+ <>
+ <a href={item.item.url} highlight="URL">{text}</a>&#160;
+ {
+ !(item.extra && item.extra.length) ? "" :
+ <span class="extra-info">
+ ({
+ template.map(item.extra, function (e)
+ <>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
+ <>&#xa0;</>/* Non-breaking space */)
+ })
+ </span>
+ }
+ </>,
+
+ filter: function (str) <span highlight="Filter">{str}</span>,
+
completionRow: function completionRow(item, highlightGroup) {
if (typeof icon == "function")
icon = icon();
@@ -63,27 +88,15 @@ const Template = Module("template", {
// </e4x>
},
- bookmarkDescription: function (item, text)
- <>
- <a href={item.item.url} highlight="URL">{text}</a>&#160;
- {
- !(item.extra && item.extra.length) ? "" :
- <span class="extra-info">
- ({
- template.map(item.extra, function (e)
- <>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
- <>&#xa0;</>/* Non-breaking space */)
- })
- </span>
- }
- </>,
-
- icon: function (item, text) {
- return <><span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}</>
+ genericTable: function genericTable(items, format) {
+ completion.listCompleter(function (context) {
+ context.filterFunc = null;
+ if (format)
+ context.format = format;
+ context.completions = items;
+ });
},
- filter: function (str) <span highlight="Filter">{str}</span>,
-
gradient: function (left, right)
<div highlight="Gradient">
<div style="height: 0px">
@@ -192,23 +205,13 @@ const Template = Module("template", {
return str;
},
- commandOutput: function generic(command, xml) {
- return <>:{command}<br/>{xml}</>;
- },
-
- genericTable: function genericTable(items, format) {
- completion.listCompleter(function (context) {
- context.filterFunc = null;
- if (format)
- context.format = format;
- context.completions = items;
- });
- },
+ icon: function (item, text) <>
+ <span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}
+ </>,
jumps: function jumps(index, elems) {
// <e4x>
- return this.commandOutput(
- <table>
+ return <table>
<tr style="text-align: left;" highlight="Title">
<th colspan="2">jump</th><th>title</th><th>URI</th>
</tr>
@@ -221,14 +224,13 @@ const Template = Module("template", {
<td><a href={val.URI.spec} highlight="URL jump-list">{val.URI.spec}</a></td>
</tr>)
}
- </table>);
+ </table>;
// </e4x>
},
options: function options(title, opts) {
// <e4x>
- return this.commandOutput(
- <table>
+ return <table>
<tr highlight="Title" align="left">
<th>--- {title} ---</th>
</tr>
@@ -241,7 +243,7 @@ const Template = Module("template", {
</td>
</tr>)
}
- </table>);
+ </table>;
// </e4x>
},
@@ -269,8 +271,7 @@ const Template = Module("template", {
tabular: function tabular(headings, style, iter) {
// TODO: This might be mind-bogglingly slow. We'll see.
// <e4x>
- return this.commandOutput(
- <table>
+ return <table>
<tr highlight="Title" align="left">
{
this.map(headings, function (h)
@@ -286,14 +287,13 @@ const Template = Module("template", {
}
</tr>)
}
- </table>);
+ </table>;
// </e4x>
},
usage: function usage(iter) {
// <e4x>
- return this.commandOutput(
- <table>
+ return <table>
{
this.map(iter, function (item)
<tr>
@@ -301,9 +301,11 @@ const Template = Module("template", {
<td>{item.description}</td>
</tr>)
}
- </table>);
+ </table>;
// </e4x>
}
});
-// vim: set fdm=marker sw=4 ts=4 et:
+endmodule();
+
+// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/content/util.js b/common/modules/util.jsm
index a0ae92c7..5d854b44 100644
--- a/common/content/util.js
+++ b/common/modules/util.jsm
@@ -6,25 +6,71 @@
// given in the LICENSE.txt file included with this file.
"use strict";
-/** @scope modules */
+Components.utils.import("resource://dactyl/base.jsm");
+defmodule("util", this, {
+ exports: ["Math", "NS", "Util", "XHTML", "XUL", "util"],
+ require: ["services"],
+ use: ["template"]
+});
const XHTML = Namespace("html", "http://www.w3.org/1999/xhtml");
const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
default xml namespace = XHTML;
-const Util = Module("util", {
+const Util = Module("Util", {
init: function () {
this.Array = array;
},
get activeWindow() services.get("windowWatcher").activeWindow,
+ dactyl: {
+ __noSuchMethod__: function (meth, args) {
+ let win = util.activeWindow;
+ if(win && win.dactyl)
+ return win.dactyl[meth].apply(win.dactyl, args);
+ return null;
+ }
+ },
+
callInMainThread: function (callback, self) {
let mainThread = services.get("threadManager").mainThread;
- if (!services.get("threadManager").isMainThread)
- mainThread.dispatch({ run: callback.call(self) }, mainThread.DISPATCH_NORMAL);
- else
+ if (services.get("threadManager").isMainThread)
callback.call(self);
+ else
+ mainThread.dispatch(Runnable(self, callback), mainThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Calls a function asynchronously on a new thread.
+ *
+ * @param {nsIThread} thread The thread to call the function on. If no
+ * thread is specified a new one is created.
+ * @optional
+ * @param {Object} self The 'this' object used when executing the
+ * function.
+ * @param {function} func The function to execute.
+ *
+ */
+ callAsync: function (thread, self, func) {
+ thread = thread || services.get("threadManager").newThread(0);
+ thread.dispatch(Runnable(self, func, Array.slice(arguments, 3)), thread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Calls a function synchronously on a new thread.
+ *
+ * NOTE: Be sure to call GUI related methods like alert() or dump()
+ * ONLY in the main thread.
+ *
+ * @param {nsIThread} thread The thread to call the function on. If no
+ * thread is specified a new one is created.
+ * @optional
+ * @param {function} func The function to execute.
+ */
+ callInThread: function (thread, func) {
+ thread = thread || services.get("threadManager").newThread(0);
+ thread.dispatch(Runnable(null, func, Array.slice(arguments, 2)), thread.DISPATCH_SYNC);
},
/**
@@ -88,7 +134,7 @@ const Util = Module("util", {
clipboardHelper.copyString(str);
if (verbose)
- dactyl.echo("Yanked " + str, commandline.FORCE_SINGLELINE);
+ util.dactyl.echomsg("Yanked " + str);
},
/**
@@ -112,7 +158,10 @@ const Util = Module("util", {
* @param {string} pattern The pattern to deglob.
* @returns [string] The resulting strings.
*/
- debrace: function deglobBrace(pattern) {
+ debrace: function debrace(pattern) {
+ if (pattern.indexOf("{") == -1)
+ return [pattern];
+
function split(pattern, re, fn, dequote) {
let end = 0, match, res = [];
while (match = re.exec(pattern)) {
@@ -132,7 +181,7 @@ const Util = Module("util", {
}, "{}");
function rec(acc) {
if (acc.length == patterns.length)
- res.push(util.Array.zip(substrings, acc).join(""));
+ res.push(array(substrings).zip(acc).flatten().join(""));
else
for (let [, pattern] in Iterator(patterns[acc.length]))
rec(acc.concat(pattern));
@@ -203,7 +252,7 @@ const Util = Module("util", {
*/
evaluateXPath: function (expression, doc, elem, asIterator) {
if (!doc)
- doc = content.document;
+ doc = util.activeWindow.content.document;
if (!elem)
elem = doc;
if (isarray(expression))
@@ -247,20 +296,6 @@ const Util = Module("util", {
},
/**
- * Returns the selection controller for the given window.
- *
- * @param {Window} window
- * @returns {nsISelectionController}
- */
- selectionController: function (win)
- win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsISelectionDisplay)
- .QueryInterface(Ci.nsISelectionController),
-
- /**
* Converts <b>bytes</b> to a pretty printed data size string.
*
* @param {number} bytes The number of bytes.
@@ -326,7 +361,7 @@ const Util = Module("util", {
return xmlhttp;
}
catch (e) {
- dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
+ util.dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
return null;
}
},
@@ -396,6 +431,8 @@ const Util = Module("util", {
*/
memoize: memoize,
+ newThread: function () services.get("threadManager").newThread(0),
+
/**
* Converts a URI string into a URI object.
*
@@ -471,13 +508,11 @@ const Util = Module("util", {
let keys = [];
try { // window.content often does not want to be queried with "var i in object"
- let hasValue = !("__iterator__" in object);
- /*
- if (modules.isPrototypeOf(object)) {
+ let hasValue = !("__iterator__" in object || isinstance(object, ["Generator", "Iterator"]));
+ if (object.dactyl && object.modules && object.modules.modules == object.modules) {
object = Iterator(object);
hasValue = false;
}
- */
for (let i in object) {
let value = <![CDATA[<no value>]]>;
try {
@@ -604,6 +639,28 @@ const Util = Module("util", {
elem.scrollIntoView();
},
+ /**
+ * Returns the selection controller for the given window.
+ *
+ * @param {Window} window
+ * @returns {nsISelectionController}
+ */
+ selectionController: function (win)
+ win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController),
+
+ /**
+ * Suspend execution for at least 'delay' milliseconds. Functions by
+ * yielding execution to the next item in the main event queue, and
+ * so may lead to unexpected call graphs, and long delays if another
+ * handler yields execution while waiting.
+ *
+ * @param {number} delay The time period for which to sleep in milliseconds.
+ */
sleep: function (delay) {
let mainThread = services.get("threadManager").mainThread;
@@ -613,6 +670,44 @@ const Util = Module("util", {
return true;
},
+ highlightFilter: function highlightFilter(str, filter, highlight) {
+ return this.highlightSubstrings(str, (function () {
+ if (filter.length == 0)
+ return;
+ let lcstr = String.toLowerCase(str);
+ let lcfilter = filter.toLowerCase();
+ let start = 0;
+ while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
+ yield [start, filter.length];
+ start += filter.length;
+ }
+ })(), highlight || template.filter);
+ },
+
+ /**
+ * Behaves like String.split, except that when 'limit' is reached,
+ * the trailing element contains the entire trailing portion of the
+ * string.
+ *
+ * util.split("a, b, c, d, e", /, /, 3) -> ["a", "b", "c, d, e"]
+ * @param {string} str The string to split.
+ * @param {RegExp|string} re The regular expression on which to split the string.
+ * @param {number} limit The maximum number of elements to return.
+ * @returns {[string]}
+ */
+ split: function (str, re, limit) {
+ if (!re.global)
+ re = RegExp(re.source || re, "g");
+ let match, start = 0, res = [];
+ while ((match = re.exec(str)) && --limit && match[0].length) {
+ res.push(str.substring(start, match.index));
+ start = match.index + match[0].length;
+ }
+ if (limit)
+ res.push(str.substring(start));
+ return res;
+ },
+
/**
* Split a string on literal occurrences of a marker.
*
@@ -670,7 +765,7 @@ const Util = Module("util", {
if (node.length() != 1) {
let domnode = doc.createDocumentFragment();
for each (let child in node)
- domnode.appendChild(arguments.callee(child, doc, nodes));
+ domnode.appendChild(xmlToDom(child, doc, nodes));
return domnode;
}
switch (node.nodeKind()) {
@@ -681,7 +776,7 @@ const Util = Module("util", {
for each (let attr in node.@*)
domnode.setAttributeNS(attr.name() == "highlight" ? NS.uri : attr.namespace(), attr.name(), String(attr));
for each (let child in node.*)
- domnode.appendChild(arguments.callee(child, doc, nodes));
+ domnode.appendChild(xmlToDom(child, doc, nodes));
if (nodes && node.@key)
nodes[node.@key] = domnode;
return domnode;
@@ -697,8 +792,9 @@ const Util = Module("util", {
* Math utility methods.
* @singleton
*/
+const GlobalMath = Math;
var Math = {
- __proto__: window.Math,
+ __proto__: GlobalMath,
/**
* Returns the specified <b>value</b> constrained to the range <b>min</b> -
@@ -712,4 +808,8 @@ var Math = {
constrain: function constrain(value, min, max) Math.min(Math.max(min, value), max)
};
-// vim: set fdm=marker sw=4 ts=4 et:
+// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n");}
+
+endmodule();
+
+// vim: set fdm=marker sw=4 ts=4 et ft=javascript: