mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-21 07:17:58 +01:00
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
This commit is contained in:
@@ -14,8 +14,6 @@ const AutoCommand = Struct("event", "pattern", "command");
|
|||||||
* @instance autocommands
|
* @instance autocommands
|
||||||
*/
|
*/
|
||||||
const AutoCommands = Module("autocommands", {
|
const AutoCommands = Module("autocommands", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._store = [];
|
this._store = [];
|
||||||
},
|
},
|
||||||
@@ -83,7 +81,7 @@ const AutoCommands = Module("autocommands", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let list = template.commandOutput(commandline.command,
|
commandline.commandOutput(
|
||||||
<table>
|
<table>
|
||||||
<tr highlight="Title">
|
<tr highlight="Title">
|
||||||
<td colspan="2">----- Auto Commands -----</td>
|
<td colspan="2">----- Auto Commands -----</td>
|
||||||
@@ -101,8 +99,6 @@ const AutoCommands = Module("autocommands", {
|
|||||||
</tr>))
|
</tr>))
|
||||||
}
|
}
|
||||||
</table>);
|
</table>);
|
||||||
|
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +133,6 @@ const AutoCommands = Module("autocommands", {
|
|||||||
autoCmd.command.call(autoCmd, args);
|
autoCmd.command.call(autoCmd, args);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
dactyl.reportError(e);
|
|
||||||
dactyl.echoerr(e);
|
dactyl.echoerr(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +198,12 @@ const AutoCommands = Module("autocommands", {
|
|||||||
return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
|
return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
|
||||||
},
|
},
|
||||||
literal: 2,
|
literal: 2,
|
||||||
options: [[["-javascript", "-js"], commands.OPTION_NOARG]]
|
options: [
|
||||||
|
{
|
||||||
|
names: ["-javascript", "-js"],
|
||||||
|
description: "Interperate the action as JavaScript code rather than an ex command",
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -10,193 +10,13 @@ const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
|
|||||||
|
|
||||||
// also includes methods for dealing with keywords and search engines
|
// also includes methods for dealing with keywords and search engines
|
||||||
const Bookmarks = Module("bookmarks", {
|
const Bookmarks = Module("bookmarks", {
|
||||||
requires: ["autocommands", "config", "dactyl", "storage", "services"],
|
|
||||||
|
|
||||||
init: function () {
|
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) {
|
let bookmarkObserver = function (key, event, arg) {
|
||||||
if (event == "add")
|
if (event == "add")
|
||||||
autocommands.trigger("BookmarkAdd", arg);
|
autocommands.trigger("BookmarkAdd", arg);
|
||||||
statusline.updateUrl();
|
statusline.updateUrl();
|
||||||
};
|
};
|
||||||
|
|
||||||
this._cache = storage.newObject("bookmark-cache", Cache, { store: false });
|
|
||||||
storage.addObserver("bookmark-cache", bookmarkObserver, window);
|
storage.addObserver("bookmark-cache", bookmarkObserver, window);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -217,7 +37,7 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
try {
|
try {
|
||||||
let uri = util.createURI(url);
|
let uri = util.createURI(url);
|
||||||
if (!force) {
|
if (!force) {
|
||||||
for (let bmark in this._cache) {
|
for (let bmark in bookmarkcache) {
|
||||||
if (bmark[0] == uri.spec) {
|
if (bmark[0] == uri.spec) {
|
||||||
var id = bmark[5];
|
var id = bmark[5];
|
||||||
if (title)
|
if (title)
|
||||||
@@ -268,7 +88,7 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
isBookmarked: function isBookmarked(url) {
|
isBookmarked: function isBookmarked(url) {
|
||||||
try {
|
try {
|
||||||
return services.get("bookmarks").getBookmarkIdsForURI(makeURI(url), {})
|
return services.get("bookmarks").getBookmarkIdsForURI(makeURI(url), {})
|
||||||
.some(this._cache.isRegularBookmark);
|
.some(bookmarkcache.isRegularBookmark);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return false;
|
return false;
|
||||||
@@ -280,7 +100,7 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
try {
|
try {
|
||||||
let uri = util.newURI(url);
|
let uri = util.newURI(url);
|
||||||
let bmarks = services.get("bookmarks").getBookmarkIdsForURI(uri, {})
|
let bmarks = services.get("bookmarks").getBookmarkIdsForURI(uri, {})
|
||||||
.filter(this._cache.isRegularBookmark);
|
.filter(bookmarkcache.isRegularBookmark);
|
||||||
bmarks.forEach(services.get("bookmarks").removeItem);
|
bmarks.forEach(services.get("bookmarks").removeItem);
|
||||||
return bmarks.length;
|
return bmarks.length;
|
||||||
}
|
}
|
||||||
@@ -350,7 +170,7 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
// format of returned array:
|
// format of returned array:
|
||||||
// [keyword, helptext, url]
|
// [keyword, helptext, url]
|
||||||
getKeywords: function getKeywords() {
|
getKeywords: function getKeywords() {
|
||||||
return this._cache.keywords;
|
return bookmarkcache.keywords;
|
||||||
},
|
},
|
||||||
|
|
||||||
// full search string including engine name as first word in @param text
|
// full search string including engine name as first word in @param text
|
||||||
@@ -451,38 +271,53 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
"Show jumplist",
|
"Show jumplist",
|
||||||
function () {
|
function () {
|
||||||
let sh = history.session;
|
let sh = history.session;
|
||||||
let list = template.jumps(sh.index, sh);
|
commandline.commandOutput(template.jumps(sh.index, sh));
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
},
|
},
|
||||||
{ argCount: "0" });
|
{ argCount: "0" });
|
||||||
|
|
||||||
// TODO: Clean this up.
|
// TODO: Clean this up.
|
||||||
function tags(context, args) {
|
const tags = {
|
||||||
|
names: ["-tags", "-T"],
|
||||||
|
description: "A comma-separated list of tags",
|
||||||
|
completer: function tags(context, args) {
|
||||||
let filter = context.filter;
|
let filter = context.filter;
|
||||||
let have = filter.split(",");
|
let have = filter.split(",");
|
||||||
|
|
||||||
args.completeFilter = have.pop();
|
args.completeFilter = have.pop();
|
||||||
|
|
||||||
let prefix = filter.substr(0, filter.length - args.completeFilter.length);
|
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 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) {
|
const title = {
|
||||||
|
names: ["-title", "-t"],
|
||||||
|
description: "Bookmark page title or description",
|
||||||
|
completer: function title(context, args) {
|
||||||
if (!args.bang)
|
if (!args.bang)
|
||||||
return [[content.document.title, "Current Page Title"]];
|
return [[content.document.title, "Current Page Title"]];
|
||||||
context.keys.text = "title";
|
context.keys.text = "title";
|
||||||
context.keys.description = "url";
|
context.keys.description = "url";
|
||||||
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
|
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
|
||||||
}
|
},
|
||||||
|
type: CommandOption.STRING
|
||||||
|
};
|
||||||
|
|
||||||
function keyword(context, args) {
|
const keyword = {
|
||||||
|
names: ["-keyword", "-k"],
|
||||||
|
description: "Keyword by which this bookmark may be opened (:open {keyword})",
|
||||||
|
completer: function keyword(context, args) {
|
||||||
if (!args.bang)
|
if (!args.bang)
|
||||||
return [];
|
return [];
|
||||||
context.keys.text = "keyword";
|
context.keys.text = "keyword";
|
||||||
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
|
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]"],
|
commands.add(["bma[rk]"],
|
||||||
"Add a bookmark",
|
"Add a bookmark",
|
||||||
@@ -503,14 +338,13 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
bang: true,
|
bang: true,
|
||||||
completer: function (context, args) {
|
completer: function (context, args) {
|
||||||
if (!args.bang) {
|
if (!args.bang) {
|
||||||
|
context.title = ["Page URL"];
|
||||||
context.completions = [[content.document.documentURI, "Current Location"]];
|
context.completions = [[content.document.documentURI, "Current Location"]];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
completion.bookmark(context, args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
|
completion.bookmark(context, args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
|
||||||
},
|
},
|
||||||
options: [[["-title", "-t"], commands.OPTION_STRING, null, title],
|
options: [title, tags, keyword]
|
||||||
[["-tags", "-T"], commands.OPTION_LIST, null, tags],
|
|
||||||
[["-keyword", "-k"], commands.OPTION_STRING, function (arg) /\w/.test(arg)]]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.add(["bmarks"],
|
commands.add(["bmarks"],
|
||||||
@@ -525,22 +359,26 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
context.filter = args.join(" ");
|
context.filter = args.join(" ");
|
||||||
completion.bookmark(context, args["-tags"]);
|
completion.bookmark(context, args["-tags"]);
|
||||||
},
|
},
|
||||||
options: [[["-tags", "-T"], commands.OPTION_LIST, null, tags],
|
options: [tags,
|
||||||
[["-max", "-m"], commands.OPTION_INT]]
|
{
|
||||||
|
names: ["-max", "-m"],
|
||||||
|
description: "The maximum number of items to list or open",
|
||||||
|
type: CommandOption.INT
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.add(["delbm[arks]"],
|
commands.add(["delbm[arks]"],
|
||||||
"Delete a bookmark",
|
"Delete a bookmark",
|
||||||
function (args) {
|
function (args) {
|
||||||
if (args.bang) {
|
if (args.bang)
|
||||||
commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
|
commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
|
||||||
function (resp) {
|
function (resp) {
|
||||||
if (resp && resp.match(/^y(es)?$/i)) {
|
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);
|
dactyl.echomsg("All bookmarks deleted", 1, commandline.FORCE_SINGLELINE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
let url = args.string || buffer.URL;
|
let url = args.string || buffer.URL;
|
||||||
let deletedCount = bookmarks.remove(url);
|
let deletedCount = bookmarks.remove(url);
|
||||||
@@ -621,9 +459,7 @@ const Bookmarks = Module("bookmarks", {
|
|||||||
if (v)
|
if (v)
|
||||||
context.filters.push(function (item) this._match(v, item[k]));
|
context.filters.push(function (item) this._match(v, item[k]));
|
||||||
}
|
}
|
||||||
// Need to make a copy because set completions() checks instanceof Array,
|
context.completions = bookmarkcache.bookmarks;
|
||||||
// and this may be an Array from another window.
|
|
||||||
context.completions = Array.slice(storage["bookmark-cache"].bookmarks);
|
|
||||||
completion.urls(context, tags);
|
completion.urls(context, tags);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
/** @scope modules */
|
/** @scope modules */
|
||||||
|
|
||||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", modules);
|
|
||||||
|
|
||||||
const Point = Struct("x", "y");
|
const Point = Struct("x", "y");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,8 +17,6 @@ const Point = Struct("x", "y");
|
|||||||
* @instance buffer
|
* @instance buffer
|
||||||
*/
|
*/
|
||||||
const Buffer = Module("buffer", {
|
const Buffer = Module("buffer", {
|
||||||
requires: ["config", "util"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this.evaluateXPath = util.evaluateXPath;
|
this.evaluateXPath = util.evaluateXPath;
|
||||||
this.pageInfo = {};
|
this.pageInfo = {};
|
||||||
@@ -146,10 +142,6 @@ const Buffer = Module("buffer", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
try {
|
|
||||||
config.browser.removeProgressListener(this.progressListener);
|
|
||||||
}
|
|
||||||
catch (e) {} // Why? --djk
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc) {
|
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc) {
|
||||||
@@ -214,14 +206,10 @@ const Buffer = Module("buffer", {
|
|||||||
/**
|
/**
|
||||||
* @property {Object} The document loading progress listener.
|
* @property {Object} The document loading progress listener.
|
||||||
*/
|
*/
|
||||||
progressListener: {
|
progressListener: update({ __proto__: window.XULBrowserWindow }, {
|
||||||
QueryInterface: XPCOMUtils.generateQI([
|
|
||||||
Ci.nsIWebProgressListener,
|
|
||||||
Ci.nsIXULBrowserWindow
|
|
||||||
]),
|
|
||||||
|
|
||||||
// XXX: function may later be needed to detect a canceled synchronous openURL()
|
// 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
|
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
|
||||||
// receive statechange events for loading images and other parts of the web page
|
// 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)) {
|
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
|
// 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
|
// TODO: do something useful with STATE_SECURE_MED and STATE_SECURE_LOW
|
||||||
if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
|
if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
|
||||||
statusline.setClass("insecure");
|
statusline.setClass("insecure");
|
||||||
@@ -258,14 +247,17 @@ const Buffer = Module("buffer", {
|
|||||||
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
|
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
|
||||||
statusline.setClass("secure");
|
statusline.setClass("secure");
|
||||||
},
|
},
|
||||||
onStatusChange: function (webProgress, request, status, message) {
|
onStatusChange: function onStatusChange(webProgress, request, status, message) {
|
||||||
|
onStatusChange.superapply(this, arguments);
|
||||||
statusline.updateUrl(message);
|
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);
|
statusline.updateProgress(curTotalProgress/maxTotalProgress);
|
||||||
},
|
},
|
||||||
// happens when the users switches tabs
|
// happens when the users switches tabs
|
||||||
onLocationChange: function () {
|
onLocationChange: function onLocationChange() {
|
||||||
|
onLocationChange.superapply(this, arguments);
|
||||||
statusline.updateUrl();
|
statusline.updateUrl();
|
||||||
statusline.updateProgress();
|
statusline.updateProgress();
|
||||||
|
|
||||||
@@ -279,10 +271,12 @@ const Buffer = Module("buffer", {
|
|||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
// called at the very end of a page load
|
// called at the very end of a page load
|
||||||
asyncUpdateUI: function () {
|
asyncUpdateUI: function asyncUpdateUI() {
|
||||||
|
asyncUpdateUI.superapply(this, arguments);
|
||||||
setTimeout(function () { statusline.updateUrl(); }, 100);
|
setTimeout(function () { statusline.updateUrl(); }, 100);
|
||||||
},
|
},
|
||||||
setOverLink: function (link, b) {
|
setOverLink: function setOverLink(link, b) {
|
||||||
|
setOverLink.superapply(this, arguments);
|
||||||
let ssli = options["showstatuslinks"];
|
let ssli = options["showstatuslinks"];
|
||||||
if (link && ssli) {
|
if (link && ssli) {
|
||||||
if (ssli == 1)
|
if (ssli == 1)
|
||||||
@@ -298,15 +292,7 @@ const Buffer = Module("buffer", {
|
|||||||
modes.show();
|
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
|
* @property {Array} The alternative style sheets for the current
|
||||||
@@ -320,19 +306,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
|
* @property {Object} A map of page info sections to their
|
||||||
* content generating functions.
|
* content generating functions.
|
||||||
@@ -447,6 +420,19 @@ const Buffer = Module("buffer", {
|
|||||||
this.pageInfo[option] = [func, title];
|
this.pageInfo[option] = [func, title];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Returns the currently selected word. If the selection is
|
||||||
* null, it tries to guess the word that the caret is
|
* null, it tries to guess the word that the caret is
|
||||||
@@ -581,7 +567,7 @@ const Buffer = Module("buffer", {
|
|||||||
let ret = followFrame(window.content);
|
let ret = followFrame(window.content);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
// only loop through frames if the main content didn't match
|
// 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)
|
if (!ret)
|
||||||
dactyl.beep();
|
dactyl.beep();
|
||||||
@@ -805,7 +791,7 @@ const Buffer = Module("buffer", {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
count = Math.max(count, 1);
|
count = Math.max(count, 1);
|
||||||
let frames = buffer.allFrames;
|
let frames = buffer.allFrames();
|
||||||
|
|
||||||
if (frames.length == 0) // currently top is always included
|
if (frames.length == 0) // currently top is always included
|
||||||
return;
|
return;
|
||||||
@@ -1367,7 +1353,11 @@ const Buffer = Module("buffer", {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
events: function () {
|
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.XULBrowserWindow = this.progressListener;
|
||||||
window.QueryInterface(Ci.nsIInterfaceRequestor)
|
window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIWebNavigation)
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
@@ -1376,12 +1366,6 @@ const Buffer = Module("buffer", {
|
|||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIXULWindow)
|
.getInterface(Ci.nsIXULWindow)
|
||||||
.XULBrowserWindow = this.progressListener;
|
.XULBrowserWindow = this.progressListener;
|
||||||
*/
|
|
||||||
|
|
||||||
try {
|
|
||||||
config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
|
|
||||||
}
|
|
||||||
catch (e) {} // Why? --djk
|
|
||||||
|
|
||||||
let appContent = document.getElementById("appcontent");
|
let appContent = document.getElementById("appcontent");
|
||||||
if (appContent) {
|
if (appContent) {
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
* this class when the chrome is ready.
|
* this class when the chrome is ready.
|
||||||
*/
|
*/
|
||||||
const CommandLine = Module("commandline", {
|
const CommandLine = Module("commandline", {
|
||||||
requires: ["config", "dactyl", "modes", "services", "storage", "template", "util"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ const CommandLine = Module("commandline", {
|
|||||||
////////////////////// TIMERS //////////////////////////////////////////////////
|
////////////////////// TIMERS //////////////////////////////////////////////////
|
||||||
/////////////////////////////////////////////////////////////////////////////{{{
|
/////////////////////////////////////////////////////////////////////////////{{{
|
||||||
|
|
||||||
this._statusTimer = new Timer(5, 100, function statusTell() {
|
this._statusTimer = Timer(5, 100, function statusTell() {
|
||||||
if (self._completions == null)
|
if (self._completions == null)
|
||||||
return;
|
return;
|
||||||
if (self._completions.selected == null)
|
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);
|
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) {
|
if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) {
|
||||||
self._completions.complete(true, false);
|
self._completions.complete(true, false);
|
||||||
self._completions.itemList.show();
|
self._completions.itemList.show();
|
||||||
@@ -114,8 +112,8 @@ const CommandLine = Module("commandline", {
|
|||||||
// This timer just prevents <Tab>s from queueing up when the
|
// This timer just prevents <Tab>s from queueing up when the
|
||||||
// system is under load (and, thus, giving us several minutes of
|
// system is under load (and, thus, giving us several minutes of
|
||||||
// the completion list scrolling). Multiple <Tab> presses are
|
// the completion list scrolling). Multiple <Tab> presses are
|
||||||
// still processed normally, as the time is flushed on "keyup".
|
// still processed normally, as the timer is flushed on "keyup".
|
||||||
this._tabTimer = new Timer(0, 0, function tabTell(event) {
|
this._tabTimer = Timer(0, 0, function tabTell(event) {
|
||||||
if (self._completions)
|
if (self._completions)
|
||||||
self._completions.tab(event.shiftKey);
|
self._completions.tab(event.shiftKey);
|
||||||
});
|
});
|
||||||
@@ -463,6 +461,18 @@ const CommandLine = Module("commandline", {
|
|||||||
this._keepCommand = false;
|
this._keepCommand = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Hides the command line, and shows any status messages that
|
||||||
* are under it.
|
* are under it.
|
||||||
@@ -492,7 +502,7 @@ const CommandLine = Module("commandline", {
|
|||||||
* commandline.FORCE_MULTILINE - Forces the message to appear in
|
* commandline.FORCE_MULTILINE - Forces the message to appear in
|
||||||
* the MOW.
|
* 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
|
// dactyl.echo uses different order of flags as it omits the highlight group, change commandline.echo argument order? --mst
|
||||||
if (this._silent)
|
if (this._silent)
|
||||||
return;
|
return;
|
||||||
@@ -506,8 +516,6 @@ const CommandLine = Module("commandline", {
|
|||||||
services.get("windowWatcher").activeWindow.dactyl)
|
services.get("windowWatcher").activeWindow.dactyl)
|
||||||
return;
|
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)
|
if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -535,8 +543,7 @@ const CommandLine = Module("commandline", {
|
|||||||
|
|
||||||
if (action)
|
if (action)
|
||||||
action.call(this, str, highlightGroup, single);
|
action.call(this, str, highlightGroup, single);
|
||||||
}, this);
|
}),
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt the user. Sets modes.main to COMMAND_LINE, which the user may
|
* Prompt the user. Sets modes.main to COMMAND_LINE, which the user may
|
||||||
@@ -675,7 +682,7 @@ const CommandLine = Module("commandline", {
|
|||||||
modes.pop();
|
modes.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // any other key
|
else {
|
||||||
//this.resetCompletions();
|
//this.resetCompletions();
|
||||||
}
|
}
|
||||||
// allow this event to be handled by the host app
|
// 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);
|
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)
|
if (!hist)
|
||||||
hist = this.original;
|
hist = this.original;
|
||||||
else
|
else
|
||||||
@@ -1165,13 +1172,6 @@ const CommandLine = Module("commandline", {
|
|||||||
|
|
||||||
get wildtype() this.wildtypes[this.wildIndex] || "",
|
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) {
|
complete: function complete(show, tabPressed) {
|
||||||
this.context.reset();
|
this.context.reset();
|
||||||
this.context.tabPressed = tabPressed;
|
this.context.tabPressed = tabPressed;
|
||||||
@@ -1181,6 +1181,9 @@ const CommandLine = Module("commandline", {
|
|||||||
this.wildIndex = 0;
|
this.wildIndex = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
haveType: function (type)
|
||||||
|
this.wildmode.checkHas(this.wildtype, type == "first" ? "" : type),
|
||||||
|
|
||||||
preview: function preview() {
|
preview: function preview() {
|
||||||
this.previewClear();
|
this.previewClear();
|
||||||
if (this.wildIndex < 0 || this.suffix || !this.items.length)
|
if (this.wildIndex < 0 || this.suffix || !this.items.length)
|
||||||
@@ -1364,7 +1367,7 @@ const CommandLine = Module("commandline", {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type.list)
|
if (this.haveType("list"))
|
||||||
this.itemList.show();
|
this.itemList.show();
|
||||||
|
|
||||||
this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1);
|
this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1);
|
||||||
@@ -1549,17 +1552,14 @@ const ItemList = Class("ItemList", {
|
|||||||
this._completionElements = [];
|
this._completionElements = [];
|
||||||
|
|
||||||
var iframe = document.getElementById(id);
|
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._doc = iframe.contentDocument;
|
||||||
|
this._win = iframe.contentWindow;
|
||||||
this._container = iframe.parentNode;
|
this._container = iframe.parentNode;
|
||||||
|
|
||||||
this._doc.body.id = id + "-content";
|
this._doc.body.id = id + "-content";
|
||||||
this._doc.body.appendChild(this._doc.createTextNode(""));
|
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");
|
this._gradient = template.gradient("GradientLeft", "GradientRight");
|
||||||
|
|
||||||
@@ -1578,7 +1578,8 @@ const ItemList = Class("ItemList", {
|
|||||||
if (this._container.collapsed)
|
if (this._container.collapsed)
|
||||||
this._div.style.minWidth = document.getElementById("dactyl-commandline").scrollWidth + "px";
|
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;
|
this._container.height = this._minHeight;
|
||||||
|
|
||||||
if (this._container.collapsed)
|
if (this._container.collapsed)
|
||||||
|
|||||||
@@ -11,6 +11,75 @@
|
|||||||
// Do NOT create instances of this class yourself, use the helper method
|
// Do NOT create instances of this class yourself, use the helper method
|
||||||
// commands.add() instead
|
// 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
|
* A class representing Ex commands. Instances are created by
|
||||||
* the {@link Commands} class.
|
* the {@link Commands} class.
|
||||||
@@ -35,8 +104,6 @@
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
const Command = Class("Command", {
|
const Command = Class("Command", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function (specs, description, action, extraInfo) {
|
init: function (specs, description, action, extraInfo) {
|
||||||
specs = Array.concat(specs); // XXX
|
specs = Array.concat(specs); // XXX
|
||||||
let parsedSpecs = Command.parseSpecs(specs);
|
let parsedSpecs = Command.parseSpecs(specs);
|
||||||
@@ -51,6 +118,8 @@ const Command = Class("Command", {
|
|||||||
|
|
||||||
if (extraInfo)
|
if (extraInfo)
|
||||||
update(this, 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
|
* invocation which should be restored on subsequent @dactyl
|
||||||
* startups.
|
* startups.
|
||||||
*/
|
*/
|
||||||
serial: null,
|
serialize: null,
|
||||||
/**
|
/**
|
||||||
* @property {boolean} When true, invocations of this command
|
* @property {boolean} When true, invocations of this command
|
||||||
* may contain private data which should be purged from
|
* may contain private data which should be purged from
|
||||||
@@ -240,47 +309,6 @@ const Commands = Module("commands", {
|
|||||||
this._exMap = {};
|
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
|
* @property Indicates that no count was specified for this
|
||||||
* command invocation.
|
* command invocation.
|
||||||
@@ -442,29 +470,8 @@ const Commands = Module("commands", {
|
|||||||
*
|
*
|
||||||
* @param {string} str The Ex command-line string to parse. E.g.
|
* @param {string} str The Ex command-line string to parse. E.g.
|
||||||
* "-x=foo -opt=bar arg1 arg2"
|
* "-x=foo -opt=bar arg1 arg2"
|
||||||
* @param {Array} options The options accepted. These are specified as
|
* @param {[CommandOption]} options The options accepted. These are specified
|
||||||
* an array [names, type, validator, completions, multiple].
|
* as an array of {@link CommandOption} structures.
|
||||||
* 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 {string} argCount The number of arguments accepted.
|
* @param {string} argCount The number of arguments accepted.
|
||||||
* "0": no arguments
|
* "0": no arguments
|
||||||
* "1": exactly one argument
|
* "1": exactly one argument
|
||||||
@@ -518,7 +525,7 @@ const Commands = Module("commands", {
|
|||||||
function matchOpts(arg) {
|
function matchOpts(arg) {
|
||||||
// Push possible option matches into completions
|
// Push possible option matches into completions
|
||||||
if (complete && !onlyArgumentsRemaining)
|
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() {
|
function resetCompletions() {
|
||||||
completeOpts = null;
|
completeOpts = null;
|
||||||
@@ -562,14 +569,14 @@ const Commands = Module("commands", {
|
|||||||
var optname = "";
|
var optname = "";
|
||||||
if (!onlyArgumentsRemaining) {
|
if (!onlyArgumentsRemaining) {
|
||||||
for (let [, opt] in Iterator(options)) {
|
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) {
|
if (sub.indexOf(optname) == 0) {
|
||||||
invalid = false;
|
invalid = false;
|
||||||
arg = null;
|
arg = null;
|
||||||
quote = null;
|
quote = null;
|
||||||
count = 0;
|
count = 0;
|
||||||
let sep = sub[optname.length];
|
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));
|
[count, arg, quote, error] = getNextArg(sub.substr(optname.length + 1));
|
||||||
dactyl.assert(!error, error);
|
dactyl.assert(!error, error);
|
||||||
|
|
||||||
@@ -595,7 +602,7 @@ const Commands = Module("commands", {
|
|||||||
args.completeFilter = arg;
|
args.completeFilter = arg;
|
||||||
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
|
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
|
||||||
}
|
}
|
||||||
let type = Commands.argTypes[opt[1]];
|
let type = Commands.argTypes[opt.type];
|
||||||
if (type && (!complete || arg != null)) {
|
if (type && (!complete || arg != null)) {
|
||||||
let orig = arg;
|
let orig = arg;
|
||||||
arg = type.parse(arg);
|
arg = type.parse(arg);
|
||||||
@@ -610,8 +617,8 @@ const Commands = Module("commands", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we have a validator function
|
// we have a validator function
|
||||||
if (typeof opt[2] == "function") {
|
if (typeof opt.validator == "function") {
|
||||||
if (opt[2].call(this, arg) == false) {
|
if (opt.validator.call(this, arg) == false) {
|
||||||
echoerr("Invalid argument for option: " + optname);
|
echoerr("Invalid argument for option: " + optname);
|
||||||
if (complete)
|
if (complete)
|
||||||
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
|
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
|
||||||
@@ -620,11 +627,13 @@ const Commands = Module("commands", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matchOpts(sub);
|
||||||
|
|
||||||
// option allowed multiple times
|
// option allowed multiple times
|
||||||
if (!!opt[4])
|
if (opt.multiple)
|
||||||
args[opt[0][0]] = (args[opt[0][0]] || []).concat(arg);
|
args[opt.names[0]] = (args[opt.names[0]] || []).concat(arg);
|
||||||
else
|
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;
|
i += optname.length + count;
|
||||||
if (i == str.length)
|
if (i == str.length)
|
||||||
@@ -683,17 +692,18 @@ const Commands = Module("commands", {
|
|||||||
if (complete) {
|
if (complete) {
|
||||||
if (args.completeOpt) {
|
if (args.completeOpt) {
|
||||||
let opt = 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;
|
context.filter = args.completeFilter;
|
||||||
if (typeof opt[3] == "function")
|
if (typeof opt.completer == "function")
|
||||||
var compl = opt[3](context, args);
|
var compl = opt.completer(context, args);
|
||||||
else
|
else
|
||||||
compl = opt[3] || [];
|
compl = opt.completer || [];
|
||||||
context.title = [opt[0][0]];
|
context.title = [opt.names[0]];
|
||||||
context.quote = args.quote;
|
context.quote = args.quote;
|
||||||
context.completions = compl;
|
context.completions = compl;
|
||||||
}
|
}
|
||||||
complete.advance(args.completeStart);
|
complete.advance(args.completeStart);
|
||||||
|
complete.keys = { text: "names", description: "description" };
|
||||||
complete.title = ["Options"];
|
complete.title = ["Options"];
|
||||||
if (completeOpts)
|
if (completeOpts)
|
||||||
complete.completions = completeOpts;
|
complete.completions = completeOpts;
|
||||||
@@ -1012,8 +1022,9 @@ const Commands = Module("commands", {
|
|||||||
// : No, array comprehensions are fine, generator statements aren't. --Kris
|
// : No, array comprehensions are fine, generator statements aren't. --Kris
|
||||||
let cmds = this._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd)));
|
let cmds = this._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd)));
|
||||||
|
|
||||||
if (cmds.length > 0) {
|
if (cmds.length > 0)
|
||||||
let str = template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"],
|
commandline.commandOutput(
|
||||||
|
template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"]),
|
||||||
([cmd.bang ? "!" : " ",
|
([cmd.bang ? "!" : " ",
|
||||||
cmd.name,
|
cmd.name,
|
||||||
cmd.argCount,
|
cmd.argCount,
|
||||||
@@ -1021,9 +1032,6 @@ const Commands = Module("commands", {
|
|||||||
completerToString(cmd.completer),
|
completerToString(cmd.completer),
|
||||||
cmd.replacementText || "function () { ... }"]
|
cmd.replacementText || "function () { ... }"]
|
||||||
for ([, cmd] in Iterator(cmds))));
|
for ([, cmd] in Iterator(cmds))));
|
||||||
|
|
||||||
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
dactyl.echomsg("No user-defined commands found");
|
dactyl.echomsg("No user-defined commands found");
|
||||||
}
|
}
|
||||||
@@ -1036,23 +1044,33 @@ const Commands = Module("commands", {
|
|||||||
completion.ex(context);
|
completion.ex(context);
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
[["-nargs"], commands.OPTION_STRING,
|
{ names: ["-bang"], description: "Command may be proceeded by a !" },
|
||||||
function (arg) /^[01*?+]$/.test(arg),
|
{ names: ["-count"], description: "Command may be preceeded by a count" },
|
||||||
[["0", "No arguments are allowed (default)"],
|
{
|
||||||
|
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"],
|
["1", "One argument is allowed"],
|
||||||
["*", "Zero or more arguments are allowed"],
|
["*", "Zero or more arguments are allowed"],
|
||||||
["?", "Zero or one argument is allowed"],
|
["?", "Zero or one argument is allowed"],
|
||||||
["+", "One or more arguments is allowed"]]],
|
["+", "One or more arguments are allowed"]],
|
||||||
[["-bang"], commands.OPTION_NOARG],
|
type: CommandOption.STRING,
|
||||||
[["-count"], commands.OPTION_NOARG],
|
validator: function (arg) /^[01*?+]$/.test(arg)
|
||||||
[["-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))]]
|
|
||||||
],
|
],
|
||||||
literal: 1,
|
literal: 1,
|
||||||
serial: function () [ {
|
serialize: function () [ {
|
||||||
command: this.name,
|
command: this.name,
|
||||||
bang: true,
|
bang: true,
|
||||||
options: util.Array.toObject(
|
options: util.Array.toObject(
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ const CompletionContext = Class("CompletionContext", {
|
|||||||
let [k, v] = i;
|
let [k, v] = i;
|
||||||
let _k = "_" + k;
|
let _k = "_" + k;
|
||||||
if (typeof v == "string" && /^[.[]/.test(v))
|
if (typeof v == "string" && /^[.[]/.test(v))
|
||||||
v = eval("(function (i) i" + v + ")");
|
v = Function("i", "return i" + v);
|
||||||
if (typeof v == "function")
|
if (typeof v == "function")
|
||||||
res.__defineGetter__(k, function () _k in this ? this[_k] : (this[_k] = v(this.item)));
|
res.__defineGetter__(k, function () _k in this ? this[_k] : (this[_k] = v(this.item)));
|
||||||
else
|
else
|
||||||
@@ -325,7 +325,7 @@ const CompletionContext = Class("CompletionContext", {
|
|||||||
let lock = {};
|
let lock = {};
|
||||||
this.cache.backgroundLock = lock;
|
this.cache.backgroundLock = lock;
|
||||||
this.incomplete = true;
|
this.incomplete = true;
|
||||||
let thread = this.getCache("backgroundThread", dactyl.newThread);
|
let thread = this.getCache("backgroundThread", util.newThread);
|
||||||
util.callAsync(thread, this, function () {
|
util.callAsync(thread, this, function () {
|
||||||
if (this.cache.backgroundLock != lock)
|
if (this.cache.backgroundLock != lock)
|
||||||
return;
|
return;
|
||||||
@@ -510,7 +510,7 @@ const CompletionContext = Class("CompletionContext", {
|
|||||||
if (!context.autoComplete && !context.tabPressed && context.editor)
|
if (!context.autoComplete && !context.tabPressed && context.editor)
|
||||||
context.waitingForTab = true;
|
context.waitingForTab = true;
|
||||||
else if (completer)
|
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)
|
if (completer)
|
||||||
return null;
|
return null;
|
||||||
@@ -668,14 +668,13 @@ const Completion = Module("completion", {
|
|||||||
context = context.contexts["/list"];
|
context = context.contexts["/list"];
|
||||||
context.wait();
|
context.wait();
|
||||||
|
|
||||||
let list = template.commandOutput(commandline.command,
|
commandline.commandOutput(
|
||||||
<div highlight="Completions">
|
<div highlight="Completions">
|
||||||
{ template.map(context.contextList.filter(function (c) c.hasItems),
|
{ template.map(context.contextList.filter(function (c) c.hasItems),
|
||||||
function (context)
|
function (context)
|
||||||
template.completionRow(context.title, "CompTitle") +
|
template.completionRow(context.title, "CompTitle") +
|
||||||
template.map(context.items, function (item) context.createRow(item), null, 100)) }
|
template.map(context.items, function (item) context.createRow(item), null, 100)) }
|
||||||
</div>);
|
</div>);
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -765,13 +764,11 @@ const Completion = Module("completion", {
|
|||||||
commands.add(["contexts"],
|
commands.add(["contexts"],
|
||||||
"List the completion contexts used during completion of an ex command",
|
"List the completion contexts used during completion of an ex command",
|
||||||
function (args) {
|
function (args) {
|
||||||
commandline.echo(template.commandOutput(commandline.command,
|
commandline.commandOutput(
|
||||||
<div highlight="Completions">
|
<div highlight="Completions">
|
||||||
{ template.completionRow(["Context", "Title"], "CompTitle") }
|
{ template.completionRow(["Context", "Title"], "CompTitle") }
|
||||||
{ template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
|
{ template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
|
||||||
</div>),
|
</div>);
|
||||||
null, commandline.FORCE_MULTILINE);
|
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
argCount: "1",
|
argCount: "1",
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ const ConfigBase = Class(ModuleBase, {
|
|||||||
|
|
||||||
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;
|
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 display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
|
||||||
HelpString::before content: '"';
|
HelpString::before content: '"';
|
||||||
|
|||||||
@@ -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
|
// This work is licensed for reuse under an MIT license. Details are
|
||||||
// given in the LICENSE.txt file included with this file.
|
// given in the LICENSE.txt file included with this file.
|
||||||
@@ -12,54 +12,60 @@
|
|||||||
|
|
||||||
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
||||||
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||||
function load(script) {
|
|
||||||
|
modules.load = function load(script) {
|
||||||
for (let [i, base] in Iterator(prefix)) {
|
for (let [i, base] in Iterator(prefix)) {
|
||||||
try {
|
try {
|
||||||
loader.loadSubScript(base + script, modules);
|
loader.loadSubScript(base + script + ".js", modules);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (i + 1 < prefix.length)
|
if (e !== "Error opening input stream (invalid filename?)")
|
||||||
continue;
|
dump("dactyl: Trying: " + (base + script + ".js") + ": " + e + "\n" + e.stack);
|
||||||
if (Components.utils.reportError)
|
|
||||||
Components.utils.reportError(e);
|
|
||||||
dump("dactyl: Loading script " + script + ": " + e + "\n");
|
|
||||||
dump(e.stack + "\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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];
|
let prefix = [BASE];
|
||||||
|
|
||||||
["base.js",
|
["base",
|
||||||
"modules.js",
|
"modules",
|
||||||
"autocommands.js",
|
"storage",
|
||||||
"buffer.js",
|
"util",
|
||||||
"commandline.js",
|
"autocommands",
|
||||||
"commands.js",
|
"buffer",
|
||||||
"completion.js",
|
"commandline",
|
||||||
"configbase.js",
|
"commands",
|
||||||
"config.js",
|
"completion",
|
||||||
"dactyl.js",
|
"configbase",
|
||||||
"editor.js",
|
"config",
|
||||||
"events.js",
|
"dactyl",
|
||||||
"finder.js",
|
"editor",
|
||||||
"hints.js",
|
"events",
|
||||||
"io.js",
|
"finder",
|
||||||
"javascript.js",
|
"highlight",
|
||||||
"mappings.js",
|
"hints",
|
||||||
"marks.js",
|
"io",
|
||||||
"modes.js",
|
"javascript",
|
||||||
"options.js",
|
"mappings",
|
||||||
"services.js",
|
"marks",
|
||||||
"statusline.js",
|
"modes",
|
||||||
"style.js",
|
"options",
|
||||||
"template.js",
|
"services",
|
||||||
"util.js",
|
"statusline",
|
||||||
].forEach(load);
|
"styles",
|
||||||
|
"template",
|
||||||
|
].forEach(modules.load);
|
||||||
|
|
||||||
prefix.unshift("chrome://" + modules.Config.prototype.name.toLowerCase() + "/content/");
|
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:
|
// vim: set fdm=marker sw=4 ts=4 et:
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
|
|
||||||
/** @scope modules */
|
/** @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 plugins = { __proto__: modules };
|
||||||
const userContext = { __proto__: modules };
|
const userContext = { __proto__: modules };
|
||||||
@@ -17,27 +19,6 @@ const EVAL_ERROR = "__dactyl_eval_error";
|
|||||||
const EVAL_RESULT = "__dactyl_eval_result";
|
const EVAL_RESULT = "__dactyl_eval_result";
|
||||||
const EVAL_STRING = "__dactyl_eval_string";
|
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, {
|
const FailedAssertion = Class("FailedAssertion", Error, {
|
||||||
init: function (message) {
|
init: function (message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
@@ -45,8 +26,6 @@ const FailedAssertion = Class("FailedAssertion", Error, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Dactyl = Module("dactyl", {
|
const Dactyl = Module("dactyl", {
|
||||||
requires: ["config", "services"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
window.dactyl = this;
|
window.dactyl = this;
|
||||||
window.liberator = this;
|
window.liberator = this;
|
||||||
@@ -61,8 +40,6 @@ const Dactyl = Module("dactyl", {
|
|||||||
// without explicitly selecting a profile.
|
// without explicitly selecting a profile.
|
||||||
/** @property {string} The name of the current user profile. */
|
/** @property {string} The name of the current user profile. */
|
||||||
this.profileName = services.get("directory").get("ProfD", Ci.nsIFile).leafName.replace(/^.+?\./, "");
|
this.profileName = services.get("directory").get("ProfD", Ci.nsIFile).leafName.replace(/^.+?\./, "");
|
||||||
|
|
||||||
config.features.push(Dactyl.getPlatformFeature());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
@@ -139,10 +116,9 @@ const Dactyl = Module("dactyl", {
|
|||||||
* bell may be either audible or visual depending on the value of the
|
* bell may be either audible or visual depending on the value of the
|
||||||
* 'visualbell' option.
|
* 'visualbell' option.
|
||||||
*/
|
*/
|
||||||
beep: function () {
|
beep: requiresMainThread(function () {
|
||||||
// FIXME: popups clear the command line
|
// FIXME: popups clear the command line
|
||||||
if (options["visualbell"]) {
|
if (options["visualbell"]) {
|
||||||
util.callInMainThread(function () {
|
|
||||||
// flash the visual bell
|
// flash the visual bell
|
||||||
let popup = document.getElementById("dactyl-visualbell");
|
let popup = document.getElementById("dactyl-visualbell");
|
||||||
let win = config.visualbellWindow;
|
let win = config.visualbellWindow;
|
||||||
@@ -154,14 +130,12 @@ const Dactyl = Module("dactyl", {
|
|||||||
popup.openPopup(win, "overlap", 1, 1, false, false);
|
popup.openPopup(win, "overlap", 1, 1, false, false);
|
||||||
popup.sizeTo(width - 2, height - 2);
|
popup.sizeTo(width - 2, height - 2);
|
||||||
setTimeout(function () { popup.hidePopup(); }, 20);
|
setTimeout(function () { popup.hidePopup(); }, 20);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
|
let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
|
||||||
soundService.beep();
|
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
|
* 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) {
|
echoerr: function (str, flags) {
|
||||||
flags |= commandline.APPEND_TO_MESSAGES;
|
flags |= commandline.APPEND_TO_MESSAGES;
|
||||||
|
|
||||||
|
if (isinstance(str, ["Error", "Exception"]))
|
||||||
|
dactyl.reportError(str);
|
||||||
if (typeof str == "object" && "echoerr" in str)
|
if (typeof str == "object" && "echoerr" in str)
|
||||||
str = str.echoerr;
|
str = str.echoerr;
|
||||||
else if (str instanceof Error)
|
else if (isinstance(str, ["Error"]))
|
||||||
str = str.fileName + ":" + str.lineNumber + ": " + str;
|
str = str.fileName + ":" + str.lineNumber + ": " + str;
|
||||||
|
|
||||||
if (options["errorbells"])
|
if (options["errorbells"])
|
||||||
@@ -504,7 +480,7 @@ const Dactyl = Module("dactyl", {
|
|||||||
|
|
||||||
// Process plugin help entries.
|
// Process plugin help entries.
|
||||||
XML.ignoreWhiteSpace = false;
|
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;
|
XML.prettyIndent = 4;
|
||||||
|
|
||||||
let body = XML();
|
let body = XML();
|
||||||
@@ -594,7 +570,7 @@ const Dactyl = Module("dactyl", {
|
|||||||
data.push(">");
|
data.push(">");
|
||||||
if (node instanceof HTMLHeadElement)
|
if (node instanceof HTMLHeadElement)
|
||||||
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
|
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(">");
|
data.push("</"); data.push(node.localName); data.push(">");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1105,6 +1081,9 @@ const Dactyl = Module("dactyl", {
|
|||||||
dactyl.help(tag);
|
dactyl.help(tag);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
config: function () {
|
||||||
|
config.features.push(Dactyl.getPlatformFeature());
|
||||||
|
},
|
||||||
|
|
||||||
// Only general options are added here, which are valid for all Dactyl extensions
|
// Only general options are added here, which are valid for all Dactyl extensions
|
||||||
options: function () {
|
options: function () {
|
||||||
@@ -1502,9 +1481,9 @@ const Dactyl = Module("dactyl", {
|
|||||||
extensions = extensions.filter(function (extension) extension.name.indexOf(args[0]) >= 0);
|
extensions = extensions.filter(function (extension) extension.name.indexOf(args[0]) >= 0);
|
||||||
extensions.sort(function (a, b) String.localeCompare(a.name, b.name));
|
extensions.sort(function (a, b) String.localeCompare(a.name, b.name));
|
||||||
|
|
||||||
if (extensions.length > 0) {
|
if (extensions.length > 0)
|
||||||
let list = template.tabular(
|
commandline.commandOutput(
|
||||||
["Name", "Version", "Status", "Description"], [],
|
template.tabular(["Name", "Version", "Status", "Description"], [],
|
||||||
([template.icon({ icon: e.iconURL }, e.name),
|
([template.icon({ icon: e.iconURL }, e.name),
|
||||||
e.version,
|
e.version,
|
||||||
(e.isActive ? <span highlight="Enabled">enabled</span>
|
(e.isActive ? <span highlight="Enabled">enabled</span>
|
||||||
@@ -1515,17 +1494,12 @@ const Dactyl = Module("dactyl", {
|
|||||||
: <span highlight="Enabled">enabled</span>}
|
: <span highlight="Enabled">enabled</span>}
|
||||||
on restart)
|
on restart)
|
||||||
</>),
|
</>),
|
||||||
e.description] for ([, e] in Iterator(extensions)))
|
e.description]
|
||||||
);
|
for ([, e] in Iterator(extensions)))));
|
||||||
|
else if (filter)
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (filter)
|
|
||||||
dactyl.echoerr("Exxx: No extension matching " + filter.quote());
|
dactyl.echoerr("Exxx: No extension matching " + filter.quote());
|
||||||
else
|
else
|
||||||
dactyl.echoerr("No extensions installed");
|
dactyl.echoerr("No extensions installed");
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ argCount: "?" });
|
{ argCount: "?" });
|
||||||
@@ -1696,7 +1670,7 @@ const Dactyl = Module("dactyl", {
|
|||||||
else
|
else
|
||||||
totalUnits = "msec";
|
totalUnits = "msec";
|
||||||
|
|
||||||
let str = template.commandOutput(commandline.command,
|
commandline.commandOutput(
|
||||||
<table>
|
<table>
|
||||||
<tr highlight="Title" align="left">
|
<tr highlight="Title" align="left">
|
||||||
<th colspan="3">Code execution summary</th>
|
<th colspan="3">Code execution summary</th>
|
||||||
@@ -1705,7 +1679,6 @@ const Dactyl = Module("dactyl", {
|
|||||||
<tr><td>  Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
|
<tr><td>  Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
|
||||||
<tr><td>  Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
|
<tr><td>  Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
|
||||||
</table>);
|
</table>);
|
||||||
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let beforeTime = Date.now();
|
let beforeTime = Date.now();
|
||||||
@@ -1767,8 +1740,9 @@ const Dactyl = Module("dactyl", {
|
|||||||
if (args.bang)
|
if (args.bang)
|
||||||
dactyl.open("about:");
|
dactyl.open("about:");
|
||||||
else
|
else
|
||||||
dactyl.echo(template.commandOutput(commandline.command,
|
commandline.commandOutput(<>
|
||||||
<>{config.name} {dactyl.version} running on:<br/>{navigator.userAgent}</>));
|
{config.name} {dactyl.version} running on:<br/>{navigator.userAgent}
|
||||||
|
</>);
|
||||||
}, {
|
}, {
|
||||||
argCount: "0",
|
argCount: "0",
|
||||||
bang: true
|
bang: true
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
/** @instance editor */
|
/** @instance editor */
|
||||||
const Editor = Module("editor", {
|
const Editor = Module("editor", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
// store our last search with f, F, t or T
|
// store our last search with f, F, t or T
|
||||||
//
|
//
|
||||||
@@ -408,10 +406,10 @@ const Editor = Module("editor", {
|
|||||||
// blink the textbox after returning
|
// blink the textbox after returning
|
||||||
if (textBox) {
|
if (textBox) {
|
||||||
let colors = [tmpBg, oldBg, tmpBg, oldBg];
|
let colors = [tmpBg, oldBg, tmpBg, oldBg];
|
||||||
(function () {
|
(function next() {
|
||||||
textBox.style.backgroundColor = colors.shift();
|
textBox.style.backgroundColor = colors.shift();
|
||||||
if (colors.length > 0)
|
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
|
dactyl.echo(mode + " " + lhs + " " + rhs, commandline.FORCE_SINGLELINE); // 2 spaces, 3 spaces
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
list = template.tabular(["", "LHS", "RHS"], [], list);
|
commandline.commandOutput(template.tabular(["", "LHS", "RHS"], [], list));
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -670,7 +666,7 @@ const Editor = Module("editor", {
|
|||||||
}, {
|
}, {
|
||||||
completer: function (context, args) completion.abbreviation(context, args, mode),
|
completer: function (context, args) completion.abbreviation(context, args, mode),
|
||||||
literal: 0,
|
literal: 0,
|
||||||
serial: function () [ {
|
serialize: function () [ {
|
||||||
command: this.name,
|
command: this.name,
|
||||||
arguments: [lhs],
|
arguments: [lhs],
|
||||||
literalArg: abbr[1]
|
literalArg: abbr[1]
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
* @instance events
|
* @instance events
|
||||||
*/
|
*/
|
||||||
const Events = Module("events", {
|
const Events = Module("events", {
|
||||||
requires: ["autocommands", "config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
@@ -737,12 +735,10 @@ const Events = Module("events", {
|
|||||||
|
|
||||||
// TODO: Merge with onFocusChange
|
// TODO: Merge with onFocusChange
|
||||||
onFocus: function (event) {
|
onFocus: function (event) {
|
||||||
function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
|
|
||||||
|
|
||||||
let elem = event.originalTarget;
|
let elem = event.originalTarget;
|
||||||
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
|
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]))
|
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement]))
|
||||||
elem.blur();
|
elem.blur();
|
||||||
},
|
},
|
||||||
@@ -1107,6 +1103,13 @@ const Events = Module("events", {
|
|||||||
editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
|
editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
|
||||||
"month", "number", "password", "range", "search",
|
"month", "number", "password", "range", "search",
|
||||||
"tel", "text", "time", "url", "week"]),
|
"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 () {
|
isInputElemFocused: function () {
|
||||||
let elem = dactyl.focus;
|
let elem = dactyl.focus;
|
||||||
return elem instanceof HTMLInputElement && set.has(Events.editableInputs, elem.type) ||
|
return elem instanceof HTMLInputElement && set.has(Events.editableInputs, elem.type) ||
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
/** @instance rangefinder */
|
/** @instance rangefinder */
|
||||||
const RangeFinder = Module("rangefinder", {
|
const RangeFinder = Module("rangefinder", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this.lastSearchPattern = "";
|
this.lastSearchPattern = "";
|
||||||
},
|
},
|
||||||
@@ -128,7 +126,7 @@ const RangeFinder = Module("rangefinder", {
|
|||||||
set rangeFind(val) buffer.localStore.rangeFind = val,
|
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.
|
* @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 () {
|
commandline: function () {
|
||||||
// Event handlers for search - closure is needed
|
// Event handlers for search - closure is needed
|
||||||
commandline.registerCallback("change", modes.FIND_FORWARD, this.closure.onKeyPress);
|
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: function () {
|
||||||
options.safeSetPref("accessibility.typeaheadfind.autostart", false);
|
options.safeSetPref("accessibility.typeaheadfind.autostart", false);
|
||||||
// The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187
|
// 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
|
* implementation will begin searching from the position of the
|
||||||
* caret in the last active frame. This is contrary to the behavior
|
* caret in the last active frame. This is contrary to the behavior
|
||||||
* of the builtin component, which always starts a search from the
|
* of the builtin component, which always starts a search from the
|
||||||
* begining of the first frame in the case of frameset documents,
|
* beginning of the first frame in the case of frameset documents,
|
||||||
* and cycles through all frames from begining to end. This makes it
|
* and cycles through all frames from beginning to end. This makes it
|
||||||
* impossible to choose the starting point of a search for such
|
* impossible to choose the starting point of a search for such
|
||||||
* documents, and represents a major detriment to productivity where
|
* documents, and represents a major detriment to productivity where
|
||||||
* large amounts of data are concerned (e.g., for API documents).
|
* large amounts of data are concerned (e.g., for API documents).
|
||||||
@@ -302,8 +301,8 @@ const RangeFind = Class("RangeFind", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
compareRanges: function (r1, r2)
|
compareRanges: function (r1, r2)
|
||||||
this.backward ? r1.compareBoundaryPoints(Range.END_TO_START, r2)
|
this.backward ? r1.compareBoundaryPoints(r1.END_TO_START, r2)
|
||||||
: -r1.compareBoundaryPoints(Range.START_TO_END, r2),
|
: -r1.compareBoundaryPoints(r1.START_TO_END, r2),
|
||||||
|
|
||||||
findRange: function (range) {
|
findRange: function (range) {
|
||||||
let doc = range.startContainer.ownerDocument;
|
let doc = range.startContainer.ownerDocument;
|
||||||
@@ -331,7 +330,7 @@ const RangeFind = Class("RangeFind", {
|
|||||||
this.lastRange.commonAncestorContainer).snapshotItem(0);
|
this.lastRange.commonAncestorContainer).snapshotItem(0);
|
||||||
if (node) {
|
if (node) {
|
||||||
node.focus();
|
node.focus();
|
||||||
// Rehighlight collapsed selection
|
// Re-highlight collapsed selection
|
||||||
this.selectedRange = this.lastRange;
|
this.selectedRange = this.lastRange;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -370,11 +369,30 @@ const RangeFind = Class("RangeFind", {
|
|||||||
this.selections.push(sel);
|
this.selections.push(sel);
|
||||||
}
|
}
|
||||||
this.highlighted = this.lastString;
|
this.highlighted = this.lastString;
|
||||||
|
if (this.lastRange)
|
||||||
this.selectedRange = this.lastRange;
|
this.selectedRange = this.lastRange;
|
||||||
this.addListeners();
|
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) {
|
iter: function (word) {
|
||||||
let saved = ["range", "lastRange", "lastString"].map(function (s) [s, this[s]], this);
|
let saved = ["range", "lastRange", "lastString"].map(function (s) [s, this[s]], this);
|
||||||
try {
|
try {
|
||||||
@@ -382,7 +400,7 @@ const RangeFind = Class("RangeFind", {
|
|||||||
this.lastRange = null;
|
this.lastRange = null;
|
||||||
this.lastString = word;
|
this.lastString = word;
|
||||||
var res;
|
var res;
|
||||||
while ((res = this.search(null, this.reverse, true)))
|
while (res = this.search(null, this.reverse, true))
|
||||||
yield res;
|
yield res;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -504,25 +522,8 @@ const RangeFind = Class("RangeFind", {
|
|||||||
|
|
||||||
if (word == "")
|
if (word == "")
|
||||||
var range = this.startRange;
|
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
|
else
|
||||||
var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)];
|
for (let i in this.indexIter(private_)) {
|
||||||
|
|
||||||
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)) {
|
|
||||||
if (!private_ && this.range.window != this.ranges[i].window && this.range.window != this.ranges[i].window.parent) {
|
if (!private_ && this.range.window != this.ranges[i].window && this.range.window != this.ranges[i].window.parent) {
|
||||||
this.range.descroll();
|
this.range.descroll();
|
||||||
this.range.deselect();
|
this.range.deselect();
|
||||||
@@ -540,7 +541,6 @@ const RangeFind = Class("RangeFind", {
|
|||||||
if (range)
|
if (range)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (range)
|
if (range)
|
||||||
this.lastRange = range.cloneRange();
|
this.lastRange = range.cloneRange();
|
||||||
@@ -559,11 +559,11 @@ const RangeFind = Class("RangeFind", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addListeners: function () {
|
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);
|
range.window.addEventListener("unload", this.closure.onUnload, true);
|
||||||
},
|
},
|
||||||
purgeListeners: function () {
|
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);
|
range.window.removeEventListener("unload", this.closure.onUnload, true);
|
||||||
},
|
},
|
||||||
onUnload: function (event) {
|
onUnload: function (event) {
|
||||||
@@ -591,8 +591,8 @@ const RangeFind = Class("RangeFind", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
intersects: function (range)
|
intersects: function (range)
|
||||||
this.range.compareBoundaryPoints(Range.START_TO_END, range) >= 0 &&
|
this.range.compareBoundaryPoints(range.START_TO_END, range) >= 0 &&
|
||||||
this.range.compareBoundaryPoints(Range.END_TO_START, range) <= 0,
|
this.range.compareBoundaryPoints(range.END_TO_START, range) <= 0,
|
||||||
|
|
||||||
save: function () {
|
save: function () {
|
||||||
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
|
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
|
||||||
@@ -625,8 +625,8 @@ const RangeFind = Class("RangeFind", {
|
|||||||
|
|
||||||
}),
|
}),
|
||||||
contains: function (range, r)
|
contains: function (range, r)
|
||||||
range.compareBoundaryPoints(Range.START_TO_END, r) >= 0 &&
|
range.compareBoundaryPoints(range.START_TO_END, r) >= 0 &&
|
||||||
range.compareBoundaryPoints(Range.END_TO_START, r) <= 0,
|
range.compareBoundaryPoints(range.END_TO_START, r) <= 0,
|
||||||
endpoint: function (range, before) {
|
endpoint: function (range, before) {
|
||||||
range = range.cloneRange();
|
range = range.cloneRange();
|
||||||
range.collapse(before);
|
range.collapse(before);
|
||||||
@@ -634,7 +634,7 @@ const RangeFind = Class("RangeFind", {
|
|||||||
},
|
},
|
||||||
equal: function (r1, r2) {
|
equal: function (r1, r2) {
|
||||||
try {
|
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) {}
|
catch (e) {}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
/** @instance hints */
|
/** @instance hints */
|
||||||
|
|
||||||
const Hints = Module("hints", {
|
const Hints = Module("hints", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
|
|
||||||
this._hintMode = null;
|
this._hintMode = null;
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const History = Module("history", {
|
const History = Module("history", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
get format() bookmarks.format,
|
get format() bookmarks.format,
|
||||||
|
|
||||||
get service() services.get("history"),
|
get service() services.get("history"),
|
||||||
@@ -196,7 +194,7 @@ const History = Module("history", {
|
|||||||
bang: true,
|
bang: true,
|
||||||
completer: function (context) { context.quote = null; completion.history(context); },
|
completer: function (context) { context.quote = null; completion.history(context); },
|
||||||
// completer: function (filter) completion.history(filter)
|
// 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 () {
|
completion: function () {
|
||||||
|
|||||||
@@ -10,320 +10,34 @@
|
|||||||
/** @scope modules */
|
/** @scope modules */
|
||||||
|
|
||||||
plugins.contexts = {};
|
plugins.contexts = {};
|
||||||
const Script = Class("Script", {
|
function Script(file) {
|
||||||
init: function (file) {
|
|
||||||
let self = plugins.contexts[file.path];
|
let self = plugins.contexts[file.path];
|
||||||
if (self) {
|
if (self) {
|
||||||
if (self.onUnload)
|
if (self.onUnload)
|
||||||
self.onUnload();
|
self.onUnload();
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
plugins.contexts[file.path] = this;
|
self = { __proto__: plugins };
|
||||||
this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
|
plugins.contexts[file.path] = self;
|
||||||
this.PATH = file.path;
|
self.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
|
||||||
this.toString = this.toString;
|
self.PATH = file.path;
|
||||||
this.__context__ = this;
|
self.__context__ = self;
|
||||||
this.__proto__ = plugins;
|
self.__proto__ = plugins;
|
||||||
|
|
||||||
// This belongs elsewhere
|
// This belongs elsewhere
|
||||||
for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
|
for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
|
||||||
if (dir.contains(file, false))
|
if (dir.contains(file, false))
|
||||||
plugins[this.NAME] = this;
|
plugins[self.NAME] = self;
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
return self;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: why are we passing around strings rather than file objects?
|
// TODO: why are we passing around strings rather than file objects?
|
||||||
/**
|
/**
|
||||||
* Provides a basic interface to common system I/O operations.
|
* Provides a basic interface to common system I/O operations.
|
||||||
* @instance io
|
* @instance io
|
||||||
*/
|
*/
|
||||||
const IO = Module("io", {
|
const IO = Module("io", {
|
||||||
requires: ["config", "services"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._processDir = services.get("directory").get("CurWorkD", Ci.nsIFile);
|
this._processDir = services.get("directory").get("CurWorkD", Ci.nsIFile);
|
||||||
this._cwd = this._processDir;
|
this._cwd = this._processDir;
|
||||||
@@ -363,7 +77,10 @@ const IO = Module("io", {
|
|||||||
* @property {function} File class.
|
* @property {function} File class.
|
||||||
* @final
|
* @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
|
* @property {Object} The current file sourcing context. As a file is
|
||||||
@@ -423,7 +140,7 @@ const IO = Module("io", {
|
|||||||
if (newDir == "-")
|
if (newDir == "-")
|
||||||
[this._cwd, this._oldcwd] = [this._oldcwd, this.getCurrentDirectory()];
|
[this._cwd, this._oldcwd] = [this._oldcwd, this.getCurrentDirectory()];
|
||||||
else {
|
else {
|
||||||
let dir = File(newDir);
|
let dir = io.File(newDir);
|
||||||
|
|
||||||
if (!dir.exists() || !dir.isDirectory()) {
|
if (!dir.exists() || !dir.isDirectory()) {
|
||||||
dactyl.echoerr("E344: Can't find directory " + dir.path.quote());
|
dactyl.echoerr("E344: Can't find directory " + dir.path.quote());
|
||||||
@@ -489,7 +206,7 @@ const IO = Module("io", {
|
|||||||
file.append(config.tempFile);
|
file.append(config.tempFile);
|
||||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt('0600', 8));
|
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;
|
let file;
|
||||||
|
|
||||||
if (File.isAbsolutePath(program))
|
if (File.isAbsolutePath(program))
|
||||||
file = File(program, true);
|
file = io.File(program, true);
|
||||||
else {
|
else {
|
||||||
let dirs = services.get("environment").get("PATH").split(dactyl.has("Win32") ? ";" : ":");
|
let dirs = services.get("environment").get("PATH").split(dactyl.has("Win32") ? ";" : ":");
|
||||||
// Windows tries the CWD first TODO: desirable?
|
// Windows tries the CWD first TODO: desirable?
|
||||||
@@ -600,7 +317,7 @@ lookup:
|
|||||||
dactyl.dump("sourcing " + filename);
|
dactyl.dump("sourcing " + filename);
|
||||||
let time = Date.now();
|
let time = Date.now();
|
||||||
try {
|
try {
|
||||||
var file = File(filename);
|
var file = io.File(filename);
|
||||||
this.sourcing = {
|
this.sourcing = {
|
||||||
file: file.path,
|
file: file.path,
|
||||||
line: 0
|
line: 0
|
||||||
@@ -803,12 +520,7 @@ lookup:
|
|||||||
/**
|
/**
|
||||||
* @property {string} The current platform's path seperator.
|
* @property {string} The current platform's path seperator.
|
||||||
*/
|
*/
|
||||||
get PATH_SEP() {
|
PATH_SEP: File.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);
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
commands: function () {
|
commands: function () {
|
||||||
commands.add(["cd", "chd[ir]"],
|
commands.add(["cd", "chd[ir]"],
|
||||||
@@ -876,13 +588,13 @@ lookup:
|
|||||||
dactyl.assert(args.length <= 1, "E172: Only one file name allowed");
|
dactyl.assert(args.length <= 1, "E172: Only one file name allowed");
|
||||||
|
|
||||||
let filename = args[0] || io.getRCFile(null, true).path;
|
let filename = args[0] || io.getRCFile(null, true).path;
|
||||||
let file = File(filename);
|
let file = io.File(filename);
|
||||||
|
|
||||||
dactyl.assert(!file.exists() || args.bang,
|
dactyl.assert(!file.exists() || args.bang,
|
||||||
"E189: " + filename.quote() + " exists (add ! to override)");
|
"E189: " + filename.quote() + " exists (add ! to override)");
|
||||||
|
|
||||||
// TODO: Use a set/specifiable list here:
|
// 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);
|
lines = util.Array.flatten(lines);
|
||||||
|
|
||||||
// source a user .pentadactylrc file
|
// source a user .pentadactylrc file
|
||||||
@@ -922,10 +634,9 @@ lookup:
|
|||||||
commands.add(["scrip[tnames]"],
|
commands.add(["scrip[tnames]"],
|
||||||
"List all sourced script names",
|
"List all sourced script names",
|
||||||
function () {
|
function () {
|
||||||
let list = template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
|
commandline.commandOutput(
|
||||||
([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))); // TODO: add colon and remove column titles for pedantic Vim compatibility?
|
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);
|
|
||||||
},
|
},
|
||||||
{ argCount: "0" });
|
{ argCount: "0" });
|
||||||
|
|
||||||
@@ -967,8 +678,7 @@ lookup:
|
|||||||
let output = io.system(arg);
|
let output = io.system(arg);
|
||||||
|
|
||||||
commandline.command = "!" + arg;
|
commandline.command = "!" + arg;
|
||||||
commandline.echo(template.commandOutput(commandline.command,
|
commandline.commandOutput(<span highlight="CmdOutput">{output}</span>);
|
||||||
<span highlight="CmdOutput">{output}</span>));
|
|
||||||
|
|
||||||
autocommands.trigger("ShellCmdPost", {});
|
autocommands.trigger("ShellCmdPost", {});
|
||||||
}, {
|
}, {
|
||||||
@@ -1034,7 +744,7 @@ lookup:
|
|||||||
context.key = dir;
|
context.key = dir;
|
||||||
context.generate = function generate_file() {
|
context.generate = function generate_file() {
|
||||||
try {
|
try {
|
||||||
return File(dir).readDirectory();
|
return io.File(dir).readDirectory();
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e) {}
|
||||||
return [];
|
return [];
|
||||||
@@ -1065,7 +775,7 @@ lookup:
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
javascript: function () {
|
javascript: function () {
|
||||||
JavaScript.setCompleter([this.File, File.expandPath],
|
JavaScript.setCompleter([File, File.expandPath],
|
||||||
[function (context, obj, args) {
|
[function (context, obj, args) {
|
||||||
context.quote[2] = "";
|
context.quote[2] = "";
|
||||||
completion.file(context, true);
|
completion.file(context, true);
|
||||||
@@ -1088,7 +798,9 @@ lookup:
|
|||||||
options.add(["fileencoding", "fenc"],
|
options.add(["fileencoding", "fenc"],
|
||||||
"Sets the character encoding of read and written files",
|
"Sets the character encoding of read and written files",
|
||||||
"string", "UTF-8", {
|
"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"],
|
options.add(["cdpath", "cd"],
|
||||||
"List of directories searched when executing :cd",
|
"List of directories searched when executing :cd",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const JavaScript = Module("javascript", {
|
|||||||
// Some object members are only accessible as function calls
|
// Some object members are only accessible as function calls
|
||||||
getKey: function (obj, key) {
|
getKey: function (obj, key) {
|
||||||
try {
|
try {
|
||||||
// if (!Object.prototype.__lookupGetter__.call(obj, key))
|
|
||||||
return obj[key];
|
return obj[key];
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e) {}
|
||||||
@@ -35,20 +34,18 @@ const JavaScript = Module("javascript", {
|
|||||||
|
|
||||||
iter: function iter(obj, toplevel) {
|
iter: function iter(obj, toplevel) {
|
||||||
"use strict";
|
"use strict";
|
||||||
const self = this;
|
|
||||||
|
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let orig = obj;
|
let seen = {};
|
||||||
if(!options["jsdebugger"])
|
for (let key in properties(obj, !toplevel)) {
|
||||||
function value(key) self.getKey(orig, key);
|
set.add(seen, key);
|
||||||
else {
|
yield [key, this.getKey(obj, key)];
|
||||||
let top = services.get("debugger").wrapValue(obj);
|
|
||||||
function value(key) top.getProperty(key).value.getWrappedValue();
|
|
||||||
}
|
}
|
||||||
for (let key in properties(obj, !toplevel))
|
for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel))
|
||||||
yield [key, value(key)];
|
if (!set.has(seen, key))
|
||||||
|
yield [key, this.getKey(obj, key)];
|
||||||
},
|
},
|
||||||
|
|
||||||
objectKeys: function objectKeys(obj, toplevel) {
|
objectKeys: function objectKeys(obj, toplevel) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const Map = Class("Map", {
|
|||||||
|
|
||||||
this.modes = modes;
|
this.modes = modes;
|
||||||
this.names = keys.map(events.canonicalKeys);
|
this.names = keys.map(events.canonicalKeys);
|
||||||
|
this.name = this.names[0];
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
@@ -122,8 +123,6 @@ const Map = Class("Map", {
|
|||||||
* @instance mappings
|
* @instance mappings
|
||||||
*/
|
*/
|
||||||
const Mappings = Module("mappings", {
|
const Mappings = Module("mappings", {
|
||||||
requires: ["modes"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._main = []; // default mappings
|
this._main = []; // default mappings
|
||||||
this._user = []; // user created mappings
|
this._user = []; // user created mappings
|
||||||
@@ -177,9 +176,9 @@ const Mappings = Module("mappings", {
|
|||||||
// Return all mappings present in all @modes
|
// Return all mappings present in all @modes
|
||||||
_mappingsIterator: function (modes, stack) {
|
_mappingsIterator: function (modes, stack) {
|
||||||
modes = modes.slice();
|
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(
|
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
|
// NOTE: just normal mode for now
|
||||||
@@ -363,11 +362,10 @@ const Mappings = Module("mappings", {
|
|||||||
</table>;
|
</table>;
|
||||||
|
|
||||||
// TODO: Move this to an ItemList to show this automatically
|
// 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");
|
dactyl.echomsg("No mapping found");
|
||||||
return;
|
else
|
||||||
}
|
commandline.commandOutput(list);
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
}, {
|
}, {
|
||||||
@@ -413,11 +411,9 @@ const Mappings = Module("mappings", {
|
|||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
completer: function (context, args) completion.userMapping(context, args, modes),
|
completer: function (context, args) completion.userMapping(context, args, modes),
|
||||||
options: [
|
|
||||||
[["<silent>", "<Silent>"], commands.OPTION_NOARG]
|
|
||||||
],
|
|
||||||
literal: 1,
|
literal: 1,
|
||||||
serial: function () {
|
options: [{ names: ["<silent>", "<Silent>"] }],
|
||||||
|
serialize: function () {
|
||||||
let noremap = this.name.indexOf("noremap") > -1;
|
let noremap = this.name.indexOf("noremap") > -1;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
* @instance marks
|
* @instance marks
|
||||||
*/
|
*/
|
||||||
const Marks = Module("marks", {
|
const Marks = Module("marks", {
|
||||||
requires: ["config", "storage"],
|
|
||||||
|
|
||||||
init: function init() {
|
init: function init() {
|
||||||
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
|
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
|
||||||
this._urlMarks = storage.newMap("url-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());
|
dactyl.assert(marks.length > 0, "E283: No marks matching " + filter.quote());
|
||||||
}
|
}
|
||||||
|
|
||||||
let list = template.tabular(
|
commandline.commandOutput(
|
||||||
|
template.tabular(
|
||||||
["Mark", "Line", "Column", "File"],
|
["Mark", "Line", "Column", "File"],
|
||||||
["", "text-align: right", "text-align: right", "color: green"],
|
["", "text-align: right", "text-align: right", "color: green"],
|
||||||
([mark[0],
|
([mark[0],
|
||||||
Math.round(mark[1].position.x * 100) + "%",
|
Math.round(mark[1].position.x * 100) + "%",
|
||||||
Math.round(mark[1].position.y * 100) + "%",
|
Math.round(mark[1].position.y * 100) + "%",
|
||||||
mark[1].location]
|
mark[1].location]
|
||||||
for ([, mark] in Iterator(marks))));
|
for ([, mark] in Iterator(marks)))));
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPageLoad: function _onPageLoad(event) {
|
_onPageLoad: function _onPageLoad(event) {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
/** @scope modules */
|
/** @scope modules */
|
||||||
|
|
||||||
const Modes = Module("modes", {
|
const Modes = Module("modes", {
|
||||||
requires: ["config", "util"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._main = 1; // NORMAL
|
this._main = 1; // NORMAL
|
||||||
this._extended = 0; // NONE
|
this._extended = 0; // NONE
|
||||||
@@ -49,8 +47,6 @@ const Modes = Module("modes", {
|
|||||||
this.addMode("MENU", true); // a popupmenu is active
|
this.addMode("MENU", true); // a popupmenu is active
|
||||||
this.addMode("LINE", true); // linewise visual mode
|
this.addMode("LINE", true); // linewise visual mode
|
||||||
this.addMode("PROMPT", true);
|
this.addMode("PROMPT", true);
|
||||||
|
|
||||||
config.modes.forEach(function (mode) { this.addMode.apply(this, mode); }, this);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getModeMessage: function () {
|
_getModeMessage: function () {
|
||||||
|
|||||||
@@ -53,12 +53,17 @@ const ModuleBase = Class("ModuleBase", {
|
|||||||
*
|
*
|
||||||
* @returns {function} The constructor for the resulting module.
|
* @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;
|
var base = ModuleBase;
|
||||||
if (callable(prototype))
|
if (callable(args[1]))
|
||||||
base = Array.splice(arguments, 1, 1)[0];
|
base = args.splice(1, 1)[0];
|
||||||
|
let [, prototype, classProperties, moduleInit] = args;
|
||||||
const module = Class(name, base, prototype, classProperties);
|
const module = Class(name, base, prototype, classProperties);
|
||||||
|
|
||||||
module.INIT = moduleInit || {};
|
module.INIT = moduleInit || {};
|
||||||
|
module.prototype.INIT = module.INIT;
|
||||||
module.requires = prototype.requires || [];
|
module.requires = prototype.requires || [];
|
||||||
Module.list.push(module);
|
Module.list.push(module);
|
||||||
Module.constructors[name] = module;
|
Module.constructors[name] = module;
|
||||||
@@ -67,67 +72,82 @@ function Module(name, prototype, classProperties, moduleInit) {
|
|||||||
Module.list = [];
|
Module.list = [];
|
||||||
Module.constructors = {};
|
Module.constructors = {};
|
||||||
|
|
||||||
window.addEventListener("load", function () {
|
window.addEventListener("load", function onLoad() {
|
||||||
window.removeEventListener("load", arguments.callee, false);
|
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() + ": "));
|
function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, Config.prototype.name.toLowerCase() + ": "));
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const deferredInit = { load: [] };
|
const deferredInit = { load: [] };
|
||||||
const seen = set();
|
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 {
|
try {
|
||||||
if (module.name in modules)
|
if (module.name in loaded)
|
||||||
return;
|
return;
|
||||||
if (module.name in seen)
|
if (module.name in seen)
|
||||||
throw Error("Module dependency loop.");
|
throw Error("Module dependency loop.");
|
||||||
set.add(seen, module.name);
|
set.add(seen, module.name);
|
||||||
|
|
||||||
for (let dep in values(module.requires))
|
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);
|
dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name);
|
||||||
modules[module.name] = module();
|
if (frame && frame.filename)
|
||||||
loaded.push(module.name);
|
dump(" from: " + frame.filename + ":" + frame.lineNumber);
|
||||||
|
|
||||||
function init(mod, module)
|
delete modules[module.name];
|
||||||
function () module.INIT[mod].call(modules[module.name], modules[mod]);
|
modules[module.name] = module();
|
||||||
for (let mod in values(loaded)) {
|
|
||||||
try {
|
init(modules[module.name]);
|
||||||
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));
|
|
||||||
}
|
|
||||||
for (let [, fn] in iter(deferredInit[module.name] || []))
|
for (let [, fn] in iter(deferredInit[module.name] || []))
|
||||||
fn();
|
fn();
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
dump("Loading " + (module && module.name) + ": " + e + "\n");
|
dump("Loading " + (module && module.name) + ": " + e);
|
||||||
if (e.stack)
|
if (e.stack)
|
||||||
dump(e.stack);
|
dump(e.stack);
|
||||||
}
|
}
|
||||||
|
return modules[module.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.list.forEach(load);
|
Module.list.forEach(load);
|
||||||
deferredInit["load"].forEach(call);
|
deferredInit["load"].forEach(call);
|
||||||
|
|
||||||
for (let module in values(Module.list))
|
dump("Loaded in " + (Date.now() - start) + "ms");
|
||||||
delete module.INIT;
|
|
||||||
|
|
||||||
dump("Loaded in " + (Date.now() - start) + "ms\n");
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
window.addEventListener("unload", function () {
|
window.addEventListener("unload", function onUnload() {
|
||||||
window.removeEventListener("unload", arguments.callee, false);
|
window.removeEventListener("unload", onUnload, false);
|
||||||
for (let [, mod] in iter(modules))
|
for (let [, mod] in iter(modules))
|
||||||
if (mod instanceof ModuleBase && "destroy" in mod)
|
if (mod instanceof ModuleBase && "destroy" in mod)
|
||||||
mod.destroy();
|
mod.destroy();
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ const Option = Class("Option", {
|
|||||||
* "string" - String, e.g., "Pentadactyl"
|
* "string" - String, e.g., "Pentadactyl"
|
||||||
* "charlist" - Character list, e.g., "rb"
|
* "charlist" - Character list, e.g., "rb"
|
||||||
* "regexlist" - Regex list, e.g., "^foo,bar$"
|
* "regexlist" - Regex list, e.g., "^foo,bar$"
|
||||||
* "stringmap" - String 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"
|
* "regexmap" - Regex map, e.g., "^key:v,foo$:bar"
|
||||||
*/
|
*/
|
||||||
type: null,
|
type: null,
|
||||||
|
|
||||||
@@ -351,9 +351,9 @@ const Option = Class("Option", {
|
|||||||
boolean: function (value) value == "true" || value == true ? true : false,
|
boolean: function (value) value == "true" || value == true ? true : false,
|
||||||
charlist: function (value) Array.slice(value),
|
charlist: function (value) Array.slice(value),
|
||||||
stringlist: function (value) (value === "") ? [] : value.split(","),
|
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),
|
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))
|
.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
|
* @instance options
|
||||||
*/
|
*/
|
||||||
const Options = Module("options", {
|
const Options = Module("options", {
|
||||||
requires: ["config", "highlight", "storage"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._optionHash = {};
|
this._optionHash = {};
|
||||||
this._prefContexts = [];
|
this._prefContexts = [];
|
||||||
@@ -661,8 +659,7 @@ const Options = Module("options", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = template.options("Options", opts());
|
commandline.commandOutput(template.options("Options", opts()));
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -687,7 +684,7 @@ const Options = Module("options", {
|
|||||||
if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
|
if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
value = options.getPref(pref);
|
let value = options.getPref(pref);
|
||||||
|
|
||||||
let option = {
|
let option = {
|
||||||
isDefault: !userValue,
|
isDefault: !userValue,
|
||||||
@@ -701,8 +698,8 @@ const Options = Module("options", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = template.options(config.hostApplication + " Options", prefs());
|
commandline.commandOutput(
|
||||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
template.options(config.hostApplication + " Options", prefs()));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -783,15 +780,7 @@ const Options = Module("options", {
|
|||||||
return this._loadPreference(name, forcedDefault);
|
return this._loadPreference(name, forcedDefault);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_checkPrefSafe: function (name, message) {
|
||||||
* 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) {
|
|
||||||
let curval = this._loadPreference(name, null, false);
|
let curval = this._loadPreference(name, null, false);
|
||||||
let defval = this._loadPreference(name, null, true);
|
let defval = this._loadPreference(name, null, true);
|
||||||
let saved = this._loadPreference(Options.SAVED + name);
|
let saved = this._loadPreference(Options.SAVED + name);
|
||||||
@@ -802,6 +791,30 @@ const Options = Module("options", {
|
|||||||
msg += " " + message;
|
msg += " " + message;
|
||||||
dactyl.echomsg(msg);
|
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(name, value);
|
||||||
this._storePreference(Options.SAVED + name, value);
|
this._storePreference(Options.SAVED + name, value);
|
||||||
},
|
},
|
||||||
@@ -825,9 +838,7 @@ const Options = Module("options", {
|
|||||||
try {
|
try {
|
||||||
services.get("pref").clearUserPref(name);
|
services.get("pref").clearUserPref(name);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {} // ignore - thrown if not a user set value
|
||||||
// ignore - thrown if not a user set value
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1238,7 +1249,7 @@ const Options = Module("options", {
|
|||||||
completer: function (context, args) {
|
completer: function (context, args) {
|
||||||
return setCompleter(context, args);
|
return setCompleter(context, args);
|
||||||
},
|
},
|
||||||
serial: function () [
|
serialize: function () [
|
||||||
{
|
{
|
||||||
command: this.name,
|
command: this.name,
|
||||||
arguments: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
|
arguments: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
* @instance quickmarks
|
* @instance quickmarks
|
||||||
*/
|
*/
|
||||||
const QuickMarks = Module("quickmarks", {
|
const QuickMarks = Module("quickmarks", {
|
||||||
requires: ["config", "storage"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._qmarks = storage.newMap("quickmarks", { store: true });
|
this._qmarks = storage.newMap("quickmarks", { store: true });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
|
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
|
||||||
|
|
||||||
const Sanitizer = Module("sanitizer", {
|
const Sanitizer = Module("sanitizer", {
|
||||||
requires: ["dactyl"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
const self = this;
|
const self = this;
|
||||||
dactyl.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
|
dactyl.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
|
||||||
@@ -144,10 +142,13 @@ const Sanitizer = Module("sanitizer", {
|
|||||||
context.completions = options.get("sanitizeitems").completer();
|
context.completions = options.get("sanitizeitems").completer();
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
[["-timespan", "-t"],
|
{
|
||||||
commands.OPTION_INT,
|
names: ["-timespan", "-t"],
|
||||||
function (arg) /^[0-4]$/.test(arg),
|
description: "Timespan for which to sanitize items",
|
||||||
function () options.get("sanitizetimespan").completer()]
|
completer: function () options.get("sanitizetimespan").completer(),
|
||||||
|
type: CommandOption.INT,
|
||||||
|
validator: function (arg) /^[0-4]$/.test(arg)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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:
|
|
||||||
@@ -14,8 +14,6 @@
|
|||||||
* @instance tabs
|
* @instance tabs
|
||||||
*/
|
*/
|
||||||
const Tabs = Module("tabs", {
|
const Tabs = Module("tabs", {
|
||||||
requires: ["config"],
|
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this._alternates = [config.tabbrowser.mCurrentTab, null];
|
this._alternates = [config.tabbrowser.mCurrentTab, null];
|
||||||
|
|
||||||
@@ -187,7 +185,7 @@ const Tabs = Module("tabs", {
|
|||||||
this._groups = this._groups = iframe ? iframe.contentWindow : null;
|
this._groups = this._groups = iframe ? iframe.contentWindow : null;
|
||||||
if (this._groups)
|
if (this._groups)
|
||||||
while (!this._groups.TabItems)
|
while (!this._groups.TabItems)
|
||||||
dactyl.threadYield(false, true);
|
util.threadYield(false, true);
|
||||||
return this._groups;
|
return this._groups;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -196,13 +194,14 @@ const Tabs = Module("tabs", {
|
|||||||
* selected tab if <b>index</b> is not specified. This is a 0-based
|
* selected tab if <b>index</b> is not specified. This is a 0-based
|
||||||
* index.
|
* 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}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
getTab: function (index) {
|
getTab: function (index) {
|
||||||
|
if (index instanceof Node)
|
||||||
|
return index;
|
||||||
if (index != null)
|
if (index != null)
|
||||||
return config.tabbrowser.mTabs[index];
|
return config.tabbrowser.mTabs[index];
|
||||||
else
|
|
||||||
return config.tabbrowser.mCurrentTab;
|
return config.tabbrowser.mCurrentTab;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,67 @@ const Ci = Components.interfaces;
|
|||||||
const Cr = Components.results;
|
const Cr = Components.results;
|
||||||
const Cu = Components.utils;
|
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) {
|
function allkeys(obj) {
|
||||||
let ret = {};
|
let ret = {};
|
||||||
try {
|
try {
|
||||||
@@ -37,7 +98,7 @@ function allkeys(obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function debuggerProperties(obj) {
|
function debuggerProperties(obj) {
|
||||||
if (modules.services && services.get("debugger").isOn) {
|
if (loaded.services && services.get("debugger").isOn) {
|
||||||
let ret = {};
|
let ret = {};
|
||||||
services.get("debugger").wrapValue(obj).getProperties(ret, {});
|
services.get("debugger").wrapValue(obj).getProperties(ret, {});
|
||||||
return ret.value;
|
return ret.value;
|
||||||
@@ -58,11 +119,18 @@ if (!Object.getOwnPropertyNames)
|
|||||||
function properties(obj, prototypes) {
|
function properties(obj, prototypes) {
|
||||||
let orig = obj;
|
let orig = obj;
|
||||||
let seen = {};
|
let seen = {};
|
||||||
for (; obj; obj = prototypes && obj.__proto__)
|
for (; obj; obj = prototypes && obj.__proto__) {
|
||||||
for (let key in values(Object.getOwnPropertyNames(obj)))
|
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)
|
if (!prototypes || !set.add(seen, key) && obj != orig)
|
||||||
yield key
|
yield key
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function values(obj) {
|
function values(obj) {
|
||||||
for (var k in obj)
|
for (var k in obj)
|
||||||
@@ -141,14 +209,18 @@ function isinstance(targ, src) {
|
|||||||
}
|
}
|
||||||
src = Array.concat(src);
|
src = Array.concat(src);
|
||||||
for (var i = 0; i < src.length; i++) {
|
for (var i = 0; i < src.length; i++) {
|
||||||
|
if (typeof src[i] == "string") {
|
||||||
|
if (Object.prototype.toString.call(targ) == "[object " + src[i] + "]")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (targ instanceof src[i])
|
if (targ instanceof src[i])
|
||||||
return true;
|
return true;
|
||||||
if (typeof src[i] == "string")
|
|
||||||
return Object.prototype.toString(targ) == "[object " + src[i] + "]";
|
|
||||||
var type = types[typeof targ];
|
var type = types[typeof targ];
|
||||||
if (type && issubclass(src[i], type))
|
if (type && issubclass(src[i], type))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,12 +312,12 @@ function curry(fn, length, self, acc) {
|
|||||||
if (acc == null)
|
if (acc == null)
|
||||||
acc = [];
|
acc = [];
|
||||||
|
|
||||||
return function () {
|
return function curried() {
|
||||||
let args = acc.concat(Array.slice(arguments));
|
let args = acc.concat(Array.slice(arguments));
|
||||||
|
|
||||||
// The curried result should preserve 'this'
|
// The curried result should preserve 'this'
|
||||||
if (arguments.length == 0)
|
if (arguments.length == 0)
|
||||||
return close(self || this, arguments.callee);
|
return close(self || this, curried);
|
||||||
|
|
||||||
if (args.length >= length)
|
if (args.length >= length)
|
||||||
return fn.apply(self || this, args);
|
return fn.apply(self || this, args);
|
||||||
@@ -254,6 +326,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
|
* Updates an object with the properties of another object. Getters
|
||||||
* and setters are copied as expected. Moreover, any function
|
* and setters are copied as expected. Moreover, any function
|
||||||
@@ -263,7 +351,7 @@ function curry(fn, length, self, acc) {
|
|||||||
*
|
*
|
||||||
* let a = { foo: function (arg) "bar " + arg }
|
* let a = { foo: function (arg) "bar " + arg }
|
||||||
* let b = { __proto__: a }
|
* 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"
|
* a.foo("foo") -> "bar foo"
|
||||||
* b.foo() -> "bar baz"
|
* b.foo() -> "bar baz"
|
||||||
@@ -393,7 +481,18 @@ function Class() {
|
|||||||
});
|
});
|
||||||
return Constructor;
|
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 = {
|
Class.prototype = {
|
||||||
/**
|
/**
|
||||||
* Initializes new instances of this class. Called automatically
|
* Initializes new instances of this class. Called automatically
|
||||||
@@ -422,6 +521,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
|
* @class Struct
|
||||||
*
|
*
|
||||||
@@ -439,7 +557,7 @@ Class.prototype = {
|
|||||||
*/
|
*/
|
||||||
function Struct() {
|
function Struct() {
|
||||||
let args = Array.slice(arguments);
|
let args = Array.slice(arguments);
|
||||||
const Struct = Class("Struct", StructBase, {
|
const Struct = Class("Struct", Struct_Base, {
|
||||||
length: args.length,
|
length: args.length,
|
||||||
members: args
|
members: args
|
||||||
});
|
});
|
||||||
@@ -449,7 +567,7 @@ function Struct() {
|
|||||||
});
|
});
|
||||||
return Struct;
|
return Struct;
|
||||||
}
|
}
|
||||||
const StructBase = Class("StructBase", {
|
let Struct_Base = Class("StructBase", Array, {
|
||||||
init: function () {
|
init: function () {
|
||||||
for (let i = 0; i < arguments.length; i++)
|
for (let i = 0; i < arguments.length; i++)
|
||||||
if (arguments[i] != undefined)
|
if (arguments[i] != undefined)
|
||||||
@@ -464,6 +582,11 @@ const StructBase = Class("StructBase", {
|
|||||||
return ([k, self[k]] for (k in values(self.members)))
|
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
|
* Sets a lazily constructed default value for a member of
|
||||||
* the struct. The value is constructed once, the first time
|
* the struct. The value is constructed once, the first time
|
||||||
@@ -477,17 +600,65 @@ const StructBase = Class("StructBase", {
|
|||||||
defaultValue: function (key, val) {
|
defaultValue: function (key, val) {
|
||||||
let i = this.prototype.members.indexOf(key);
|
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.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
|
||||||
this.prototype.__defineSetter__(i, function (value) {
|
this.prototype.__defineSetter__(i, function (value)
|
||||||
this.__defineGetter__(i, function () value);
|
Class.replaceProperty(this, i, value));
|
||||||
this.__defineSetter__(i, function (val) { value = val; });
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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.
|
* 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
|
* Zips the contents of two arrays. The resulting array is the length of
|
||||||
* length of ary1, with any shortcomings of ary2 replaced with null
|
* ary1, with any shortcomings of ary2 replaced with null strings.
|
||||||
* strings.
|
|
||||||
*
|
*
|
||||||
* @param {Array} ary1
|
* @param {Array} ary1
|
||||||
* @param {Array} ary2
|
* @param {Array} ary2
|
||||||
@@ -616,9 +786,13 @@ const array = Class("util.Array", Array, {
|
|||||||
zip: function zip(ary1, ary2) {
|
zip: function zip(ary1, ary2) {
|
||||||
let res = []
|
let res = []
|
||||||
for(let [i, item] in Iterator(ary1))
|
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;
|
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:
|
||||||
162
common/modules/bookmarkcache.jsm
Normal file
162
common/modules/bookmarkcache.jsm
Normal file
@@ -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:
|
||||||
241
common/modules/highlight.jsm
Normal file
241
common/modules/highlight.jsm
Normal file
@@ -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:
|
||||||
@@ -4,14 +4,12 @@
|
|||||||
// given in the LICENSE.txt file included with this file.
|
// given in the LICENSE.txt file included with this file.
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/** @scope modules */
|
Components.utils.import("resource://dactyl/base.jsm");
|
||||||
|
defmodule("services", this, {
|
||||||
|
exports: ["Services", "services"]
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
const Services = Module("Services", {
|
||||||
* Cached XPCOM services and classes.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
const Services = Module("services", {
|
|
||||||
init: function () {
|
init: function () {
|
||||||
this.classes = {};
|
this.classes = {};
|
||||||
this.services = {};
|
this.services = {};
|
||||||
@@ -54,9 +52,6 @@ const Services = Module("services", {
|
|||||||
this.addClass("timer", "@mozilla.org/timer;1", Ci.nsITimer);
|
this.addClass("timer", "@mozilla.org/timer;1", Ci.nsITimer);
|
||||||
this.addClass("xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
|
this.addClass("xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
|
||||||
this.addClass("zipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter);
|
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) {
|
_create: function (classes, ifaces, meth) {
|
||||||
@@ -120,10 +115,19 @@ const Services = Module("services", {
|
|||||||
*/
|
*/
|
||||||
create: function (name) this.classes[name]()
|
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: function (dactyl, modules) {
|
||||||
JavaScript.setCompleter(this.get, [function () services.services]);
|
modules.JavaScript.setCompleter(this.get, [function () services.services]);
|
||||||
JavaScript.setCompleter(this.create, [function () [[c, ""] for (c in services.classes)]]);
|
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:
|
||||||
@@ -21,107 +21,22 @@
|
|||||||
}}} ***** END LICENSE BLOCK *****/
|
}}} ***** END LICENSE BLOCK *****/
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["storage", "Timer"];
|
Components.utils.import("resource://dactyl/base.jsm");
|
||||||
|
defmodule("storage", this, {
|
||||||
|
exports: ["File", "storage"],
|
||||||
|
require: ["services", "util"]
|
||||||
|
});
|
||||||
|
|
||||||
const Cc = Components.classes;
|
var prefService = services.get("pref").getBranch("extensions.dactyl.datastore.");
|
||||||
const Ci = Components.interfaces;
|
|
||||||
const Cu = Components.utils;
|
|
||||||
|
|
||||||
// XXX: does not belong here
|
const win32 = services.get("runtime").OS == "Win32";
|
||||||
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;
|
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFile(name) {
|
function getFile(name) {
|
||||||
let file = storage.infoPath.clone();
|
let file = storage.infoPath.clone();
|
||||||
file.append(name);
|
file.append(name);
|
||||||
return file;
|
return File(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
function getCharPref(name) {
|
||||||
try {
|
try {
|
||||||
return prefService.getComplexValue(name, Ci.nsISupportsString).data;
|
return prefService.getComplexValue(name, Ci.nsISupportsString).data;
|
||||||
@@ -140,9 +55,9 @@ function loadPref(name, store, type) {
|
|||||||
if (store)
|
if (store)
|
||||||
var pref = getCharPref(name);
|
var pref = getCharPref(name);
|
||||||
if (!pref && storage.infoPath)
|
if (!pref && storage.infoPath)
|
||||||
var file = readFile(getFile(name));
|
var file = getFile(name).read();
|
||||||
if (pref || file)
|
if (pref || file)
|
||||||
var result = json.decode(pref || file);
|
var result = services.get("json").decode(pref || file);
|
||||||
if (pref) {
|
if (pref) {
|
||||||
prefService.clearUserPref(name);
|
prefService.clearUserPref(name);
|
||||||
savePref({ name: name, store: true, serial: pref });
|
savePref({ name: name, store: true, serial: pref });
|
||||||
@@ -157,122 +72,110 @@ function savePref(obj) {
|
|||||||
if (obj.privateData && storage.privateMode)
|
if (obj.privateData && storage.privateMode)
|
||||||
return;
|
return;
|
||||||
if (obj.store && storage.infoPath)
|
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"],
|
OPTIONS: ["privateData"],
|
||||||
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg); },
|
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg); },
|
||||||
|
get serial() services.get("json").encode(this._object),
|
||||||
save: function () { savePref(this); },
|
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__("store", function () store);
|
||||||
this.__defineGetter__("name", function () name);
|
this.__defineGetter__("name", function () name);
|
||||||
for (let [k, v] in Iterator(options))
|
for (let [k, v] in Iterator(options))
|
||||||
if (this.OPTIONS.indexOf(k) >= 0)
|
if (this.OPTIONS.indexOf(k) >= 0)
|
||||||
this[k] = v;
|
this[k] = v;
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
},
|
||||||
};
|
reload: function reload() {
|
||||||
|
this._object = this._load() || this._constructor();
|
||||||
function ObjectStore(name, store, load, options) {
|
|
||||||
var object = {};
|
|
||||||
|
|
||||||
this.reload = function reload() {
|
|
||||||
object = load() || {};
|
|
||||||
this.fireEvent("change", null);
|
this.fireEvent("change", null);
|
||||||
};
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.init.apply(this, arguments);
|
const ObjectStore = Class("ObjectStore", StoreBase, {
|
||||||
this.__defineGetter__("serial", function () json.encode(object));
|
_constructor: Object,
|
||||||
|
|
||||||
this.set = function set(key, val) {
|
set: function set(key, val) {
|
||||||
var defined = key in object;
|
var defined = key in this._object;
|
||||||
var orig = object[key];
|
var orig = this._object[key];
|
||||||
object[key] = val;
|
this._object[key] = val;
|
||||||
if (!defined)
|
if (!defined)
|
||||||
this.fireEvent("add", key);
|
this.fireEvent("add", key);
|
||||||
else if (orig != val)
|
else if (orig != val)
|
||||||
this.fireEvent("change", key);
|
this.fireEvent("change", key);
|
||||||
};
|
},
|
||||||
|
|
||||||
this.remove = function remove(key) {
|
remove: function remove(key) {
|
||||||
var ret = object[key];
|
var ret = this._object[key];
|
||||||
delete object[key];
|
delete this._object[key];
|
||||||
this.fireEvent("remove", key);
|
this.fireEvent("remove", key);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
},
|
||||||
|
|
||||||
this.get = function get(val, default_) val in object ? object[val] : default_;
|
get: function get(val, default_) val in this._object ? this._object[val] : default_,
|
||||||
|
|
||||||
this.clear = function () {
|
clear: function () {
|
||||||
object = {};
|
this._object = {};
|
||||||
};
|
},
|
||||||
|
|
||||||
this.__iterator__ = function () Iterator(object);
|
__iterator__: function () Iterator(this._object),
|
||||||
}
|
});
|
||||||
ObjectStore.prototype = prototype;
|
|
||||||
|
|
||||||
function ArrayStore(name, store, load, options) {
|
const ArrayStore = Class("ArrayStore", StoreBase, {
|
||||||
var array = [];
|
_constructor: Array,
|
||||||
|
|
||||||
this.reload = function reload() {
|
get length() this._object.length,
|
||||||
array = load() || [];
|
|
||||||
this.fireEvent("change", null);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init.apply(this, arguments);
|
set: function set(index, value) {
|
||||||
this.__defineGetter__("serial", function () json.encode(array));
|
var orig = this._object[index];
|
||||||
this.__defineGetter__("length", function () array.length);
|
this._object[index] = value;
|
||||||
|
|
||||||
this.set = function set(index, value) {
|
|
||||||
var orig = array[index];
|
|
||||||
array[index] = value;
|
|
||||||
this.fireEvent("change", index);
|
this.fireEvent("change", index);
|
||||||
};
|
},
|
||||||
|
|
||||||
this.push = function push(value) {
|
push: function push(value) {
|
||||||
array.push(value);
|
this._object.push(value);
|
||||||
this.fireEvent("push", array.length);
|
this.fireEvent("push", this._object.length);
|
||||||
};
|
},
|
||||||
|
|
||||||
this.pop = function pop(value) {
|
pop: function pop(value) {
|
||||||
var ret = array.pop();
|
var ret = this._object.pop();
|
||||||
this.fireEvent("pop", array.length);
|
this.fireEvent("pop", this._object.length);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
},
|
||||||
|
|
||||||
this.truncate = function truncate(length, fromEnd) {
|
truncate: function truncate(length, fromEnd) {
|
||||||
var ret = array.length;
|
var ret = this._object.length;
|
||||||
if (array.length > length) {
|
if (this._object.length > length) {
|
||||||
if (fromEnd)
|
if (fromEnd)
|
||||||
array.splice(0, array.length - length);
|
this._object.splice(0, this._object.length - length);
|
||||||
array.length = length;
|
this._object.length = length;
|
||||||
this.fireEvent("truncate", length);
|
this.fireEvent("truncate", length);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
},
|
||||||
|
|
||||||
// XXX: Awkward.
|
// XXX: Awkward.
|
||||||
this.mutate = function mutate(aFuncName) {
|
mutate: function mutate(funcName) {
|
||||||
var funcName = aFuncName;
|
var _funcName = funcName;
|
||||||
arguments[0] = array;
|
arguments[0] = this._object;
|
||||||
array = Array[funcName].apply(Array, arguments);
|
this._object = Array[_funcName].apply(Array, arguments);
|
||||||
this.fireEvent("change", null);
|
this.fireEvent("change", null);
|
||||||
};
|
},
|
||||||
|
|
||||||
this.get = function get(index) {
|
get: function get(index) index >= 0 ? this._object[index] : this._object[this._object.length + index],
|
||||||
return index >= 0 ? array[index] : array[array.length + index];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.__iterator__ = function () Iterator(array);
|
__iterator__: function () Iterator(this._object),
|
||||||
}
|
});
|
||||||
ArrayStore.prototype = prototype;
|
|
||||||
|
|
||||||
var keys = {};
|
var keys = {};
|
||||||
var observers = {};
|
var observers = {};
|
||||||
var timers = {};
|
var timers = {};
|
||||||
|
|
||||||
var storage = {
|
const Storage = Module("Storage", {
|
||||||
alwaysReload: {},
|
alwaysReload: {},
|
||||||
newObject: function newObject(key, constructor, params) {
|
newObject: function newObject(key, constructor, params) {
|
||||||
if (!(key in keys) || params.reload || this.alwaysReload[key]) {
|
if (!(key in keys) || params.reload || this.alwaysReload[key]) {
|
||||||
@@ -363,6 +266,304 @@ var storage = {
|
|||||||
this.load(key);
|
this.load(key);
|
||||||
return this._privateMode = Boolean(val);
|
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:
|
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
|
||||||
|
|||||||
373
common/modules/styles.jsm
Normal file
373
common/modules/styles.jsm
Normal file
@@ -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:
|
||||||
@@ -4,9 +4,17 @@
|
|||||||
// given in the LICENSE.txt file included with this file.
|
// given in the LICENSE.txt file included with this file.
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/** @scope modules */
|
Components.utils.import("resource://dactyl/base.jsm");
|
||||||
|
defmodule("template", this, {
|
||||||
|
exports: ["Template", "template"],
|
||||||
|
require: ["util"]
|
||||||
|
});
|
||||||
|
|
||||||
const Template = Module("template", {
|
default xml namespace = XHTML;
|
||||||
|
XML.ignoreWhiteSpace = true;
|
||||||
|
XML.prettyPrinting = false;
|
||||||
|
|
||||||
|
const Template = Module("Template", {
|
||||||
add: function add(a, b) a + b,
|
add: function add(a, b) a + b,
|
||||||
join: function join(c) function (a, b) a + c + b,
|
join: function join(c) function (a, b) a + c + b,
|
||||||
|
|
||||||
@@ -38,6 +46,23 @@ const Template = Module("template", {
|
|||||||
return <>{xml}</>;
|
return <>{xml}</>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
bookmarkDescription: function (item, text)
|
||||||
|
<>
|
||||||
|
<a href={item.item.url} highlight="URL">{text}</a> 
|
||||||
|
{
|
||||||
|
!(item.extra && item.extra.length) ? "" :
|
||||||
|
<span class="extra-info">
|
||||||
|
({
|
||||||
|
template.map(item.extra, function (e)
|
||||||
|
<>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
|
||||||
|
<> </>/* Non-breaking space */)
|
||||||
|
})
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</>,
|
||||||
|
|
||||||
|
filter: function (str) <span highlight="Filter">{str}</span>,
|
||||||
|
|
||||||
completionRow: function completionRow(item, highlightGroup) {
|
completionRow: function completionRow(item, highlightGroup) {
|
||||||
if (typeof icon == "function")
|
if (typeof icon == "function")
|
||||||
icon = icon();
|
icon = icon();
|
||||||
@@ -63,27 +88,15 @@ const Template = Module("template", {
|
|||||||
// </e4x>
|
// </e4x>
|
||||||
},
|
},
|
||||||
|
|
||||||
bookmarkDescription: function (item, text)
|
genericTable: function genericTable(items, format) {
|
||||||
<>
|
completion.listCompleter(function (context) {
|
||||||
<a href={item.item.url} highlight="URL">{text}</a> 
|
context.filterFunc = null;
|
||||||
{
|
if (format)
|
||||||
!(item.extra && item.extra.length) ? "" :
|
context.format = format;
|
||||||
<span class="extra-info">
|
context.completions = items;
|
||||||
({
|
});
|
||||||
template.map(item.extra, function (e)
|
|
||||||
<>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
|
|
||||||
<> </>/* Non-breaking space */)
|
|
||||||
})
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</>,
|
|
||||||
|
|
||||||
icon: function (item, text) {
|
|
||||||
return <><span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}</>
|
|
||||||
},
|
},
|
||||||
|
|
||||||
filter: function (str) <span highlight="Filter">{str}</span>,
|
|
||||||
|
|
||||||
gradient: function (left, right)
|
gradient: function (left, right)
|
||||||
<div highlight="Gradient">
|
<div highlight="Gradient">
|
||||||
<div style="height: 0px">
|
<div style="height: 0px">
|
||||||
@@ -192,23 +205,13 @@ const Template = Module("template", {
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
commandOutput: function generic(command, xml) {
|
icon: function (item, text) <>
|
||||||
return <>:{command}<br/>{xml}</>;
|
<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;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
jumps: function jumps(index, elems) {
|
jumps: function jumps(index, elems) {
|
||||||
// <e4x>
|
// <e4x>
|
||||||
return this.commandOutput(
|
return <table>
|
||||||
<table>
|
|
||||||
<tr style="text-align: left;" highlight="Title">
|
<tr style="text-align: left;" highlight="Title">
|
||||||
<th colspan="2">jump</th><th>title</th><th>URI</th>
|
<th colspan="2">jump</th><th>title</th><th>URI</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -221,14 +224,13 @@ const Template = Module("template", {
|
|||||||
<td><a href={val.URI.spec} highlight="URL jump-list">{val.URI.spec}</a></td>
|
<td><a href={val.URI.spec} highlight="URL jump-list">{val.URI.spec}</a></td>
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</table>);
|
</table>;
|
||||||
// </e4x>
|
// </e4x>
|
||||||
},
|
},
|
||||||
|
|
||||||
options: function options(title, opts) {
|
options: function options(title, opts) {
|
||||||
// <e4x>
|
// <e4x>
|
||||||
return this.commandOutput(
|
return <table>
|
||||||
<table>
|
|
||||||
<tr highlight="Title" align="left">
|
<tr highlight="Title" align="left">
|
||||||
<th>--- {title} ---</th>
|
<th>--- {title} ---</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -241,7 +243,7 @@ const Template = Module("template", {
|
|||||||
</td>
|
</td>
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</table>);
|
</table>;
|
||||||
// </e4x>
|
// </e4x>
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -269,8 +271,7 @@ const Template = Module("template", {
|
|||||||
tabular: function tabular(headings, style, iter) {
|
tabular: function tabular(headings, style, iter) {
|
||||||
// TODO: This might be mind-bogglingly slow. We'll see.
|
// TODO: This might be mind-bogglingly slow. We'll see.
|
||||||
// <e4x>
|
// <e4x>
|
||||||
return this.commandOutput(
|
return <table>
|
||||||
<table>
|
|
||||||
<tr highlight="Title" align="left">
|
<tr highlight="Title" align="left">
|
||||||
{
|
{
|
||||||
this.map(headings, function (h)
|
this.map(headings, function (h)
|
||||||
@@ -286,14 +287,13 @@ const Template = Module("template", {
|
|||||||
}
|
}
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</table>);
|
</table>;
|
||||||
// </e4x>
|
// </e4x>
|
||||||
},
|
},
|
||||||
|
|
||||||
usage: function usage(iter) {
|
usage: function usage(iter) {
|
||||||
// <e4x>
|
// <e4x>
|
||||||
return this.commandOutput(
|
return <table>
|
||||||
<table>
|
|
||||||
{
|
{
|
||||||
this.map(iter, function (item)
|
this.map(iter, function (item)
|
||||||
<tr>
|
<tr>
|
||||||
@@ -301,9 +301,11 @@ const Template = Module("template", {
|
|||||||
<td>{item.description}</td>
|
<td>{item.description}</td>
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</table>);
|
</table>;
|
||||||
// </e4x>
|
// </e4x>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// vim: set fdm=marker sw=4 ts=4 et:
|
endmodule();
|
||||||
|
|
||||||
|
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
|
||||||
@@ -6,25 +6,71 @@
|
|||||||
// given in the LICENSE.txt file included with this file.
|
// given in the LICENSE.txt file included with this file.
|
||||||
"use strict";
|
"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 XHTML = Namespace("html", "http://www.w3.org/1999/xhtml");
|
||||||
const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||||
const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
|
const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
|
||||||
default xml namespace = XHTML;
|
default xml namespace = XHTML;
|
||||||
|
|
||||||
const Util = Module("util", {
|
const Util = Module("Util", {
|
||||||
init: function () {
|
init: function () {
|
||||||
this.Array = array;
|
this.Array = array;
|
||||||
},
|
},
|
||||||
|
|
||||||
get activeWindow() services.get("windowWatcher").activeWindow,
|
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) {
|
callInMainThread: function (callback, self) {
|
||||||
let mainThread = services.get("threadManager").mainThread;
|
let mainThread = services.get("threadManager").mainThread;
|
||||||
if (!services.get("threadManager").isMainThread)
|
if (services.get("threadManager").isMainThread)
|
||||||
mainThread.dispatch({ run: callback.call(self) }, mainThread.DISPATCH_NORMAL);
|
|
||||||
else
|
|
||||||
callback.call(self);
|
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);
|
clipboardHelper.copyString(str);
|
||||||
|
|
||||||
if (verbose)
|
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.
|
* @param {string} pattern The pattern to deglob.
|
||||||
* @returns [string] The resulting strings.
|
* @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) {
|
function split(pattern, re, fn, dequote) {
|
||||||
let end = 0, match, res = [];
|
let end = 0, match, res = [];
|
||||||
while (match = re.exec(pattern)) {
|
while (match = re.exec(pattern)) {
|
||||||
@@ -132,7 +181,7 @@ const Util = Module("util", {
|
|||||||
}, "{}");
|
}, "{}");
|
||||||
function rec(acc) {
|
function rec(acc) {
|
||||||
if (acc.length == patterns.length)
|
if (acc.length == patterns.length)
|
||||||
res.push(util.Array.zip(substrings, acc).join(""));
|
res.push(array(substrings).zip(acc).flatten().join(""));
|
||||||
else
|
else
|
||||||
for (let [, pattern] in Iterator(patterns[acc.length]))
|
for (let [, pattern] in Iterator(patterns[acc.length]))
|
||||||
rec(acc.concat(pattern));
|
rec(acc.concat(pattern));
|
||||||
@@ -203,7 +252,7 @@ const Util = Module("util", {
|
|||||||
*/
|
*/
|
||||||
evaluateXPath: function (expression, doc, elem, asIterator) {
|
evaluateXPath: function (expression, doc, elem, asIterator) {
|
||||||
if (!doc)
|
if (!doc)
|
||||||
doc = content.document;
|
doc = util.activeWindow.content.document;
|
||||||
if (!elem)
|
if (!elem)
|
||||||
elem = doc;
|
elem = doc;
|
||||||
if (isarray(expression))
|
if (isarray(expression))
|
||||||
@@ -246,20 +295,6 @@ const Util = Module("util", {
|
|||||||
return dest;
|
return dest;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Converts <b>bytes</b> to a pretty printed data size string.
|
||||||
*
|
*
|
||||||
@@ -326,7 +361,7 @@ const Util = Module("util", {
|
|||||||
return xmlhttp;
|
return xmlhttp;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
|
util.dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -396,6 +431,8 @@ const Util = Module("util", {
|
|||||||
*/
|
*/
|
||||||
memoize: memoize,
|
memoize: memoize,
|
||||||
|
|
||||||
|
newThread: function () services.get("threadManager").newThread(0),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a URI string into a URI object.
|
* Converts a URI string into a URI object.
|
||||||
*
|
*
|
||||||
@@ -471,13 +508,11 @@ const Util = Module("util", {
|
|||||||
|
|
||||||
let keys = [];
|
let keys = [];
|
||||||
try { // window.content often does not want to be queried with "var i in object"
|
try { // window.content often does not want to be queried with "var i in object"
|
||||||
let hasValue = !("__iterator__" in object);
|
let hasValue = !("__iterator__" in object || isinstance(object, ["Generator", "Iterator"]));
|
||||||
/*
|
if (object.dactyl && object.modules && object.modules.modules == object.modules) {
|
||||||
if (modules.isPrototypeOf(object)) {
|
|
||||||
object = Iterator(object);
|
object = Iterator(object);
|
||||||
hasValue = false;
|
hasValue = false;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
for (let i in object) {
|
for (let i in object) {
|
||||||
let value = <![CDATA[<no value>]]>;
|
let value = <![CDATA[<no value>]]>;
|
||||||
try {
|
try {
|
||||||
@@ -604,6 +639,28 @@ const Util = Module("util", {
|
|||||||
elem.scrollIntoView();
|
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) {
|
sleep: function (delay) {
|
||||||
let mainThread = services.get("threadManager").mainThread;
|
let mainThread = services.get("threadManager").mainThread;
|
||||||
|
|
||||||
@@ -613,6 +670,44 @@ const Util = Module("util", {
|
|||||||
return true;
|
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.
|
* Split a string on literal occurrences of a marker.
|
||||||
*
|
*
|
||||||
@@ -670,7 +765,7 @@ const Util = Module("util", {
|
|||||||
if (node.length() != 1) {
|
if (node.length() != 1) {
|
||||||
let domnode = doc.createDocumentFragment();
|
let domnode = doc.createDocumentFragment();
|
||||||
for each (let child in node)
|
for each (let child in node)
|
||||||
domnode.appendChild(arguments.callee(child, doc, nodes));
|
domnode.appendChild(xmlToDom(child, doc, nodes));
|
||||||
return domnode;
|
return domnode;
|
||||||
}
|
}
|
||||||
switch (node.nodeKind()) {
|
switch (node.nodeKind()) {
|
||||||
@@ -681,7 +776,7 @@ const Util = Module("util", {
|
|||||||
for each (let attr in node.@*)
|
for each (let attr in node.@*)
|
||||||
domnode.setAttributeNS(attr.name() == "highlight" ? NS.uri : attr.namespace(), attr.name(), String(attr));
|
domnode.setAttributeNS(attr.name() == "highlight" ? NS.uri : attr.namespace(), attr.name(), String(attr));
|
||||||
for each (let child in node.*)
|
for each (let child in node.*)
|
||||||
domnode.appendChild(arguments.callee(child, doc, nodes));
|
domnode.appendChild(xmlToDom(child, doc, nodes));
|
||||||
if (nodes && node.@key)
|
if (nodes && node.@key)
|
||||||
nodes[node.@key] = domnode;
|
nodes[node.@key] = domnode;
|
||||||
return domnode;
|
return domnode;
|
||||||
@@ -697,8 +792,9 @@ const Util = Module("util", {
|
|||||||
* Math utility methods.
|
* Math utility methods.
|
||||||
* @singleton
|
* @singleton
|
||||||
*/
|
*/
|
||||||
|
const GlobalMath = Math;
|
||||||
var Math = {
|
var Math = {
|
||||||
__proto__: window.Math,
|
__proto__: GlobalMath,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the specified <b>value</b> constrained to the range <b>min</b> -
|
* 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)
|
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:
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#### configuration
|
#### configuration
|
||||||
|
|
||||||
VERSION = 2.3a1pre
|
VERSION = 1.0pre
|
||||||
NAME = pentadactyl
|
NAME = pentadactyl
|
||||||
|
|
||||||
include ../common/Makefile
|
include ../common/Makefile
|
||||||
|
|||||||
@@ -41,11 +41,10 @@ BUGS:
|
|||||||
- The MOW shouldn't close when executing hints and ;F isn't working.
|
- The MOW shouldn't close when executing hints and ;F isn't working.
|
||||||
|
|
||||||
FEATURES:
|
FEATURES:
|
||||||
|
9 Support multiple bookmarks, -keyword, -tags in :delbmarks
|
||||||
8 Document Textarea, Caret and Visual modes.
|
8 Document Textarea, Caret and Visual modes.
|
||||||
8 Replace config.name tests in dactyl with more specific feature
|
8 Replace config.name tests in dactyl with more specific feature
|
||||||
tests or overridable APIs where at all feasible.
|
tests or overridable APIs where at all feasible.
|
||||||
8 change the extension ID to pentadactyl@vimperator.org rather than
|
|
||||||
pentadactyl@mozdev.org
|
|
||||||
8 finish :help TODOs
|
8 finish :help TODOs
|
||||||
8 fix local options
|
8 fix local options
|
||||||
8 adaptive timeout for auto-completions, :set completions can be updated more often than
|
8 adaptive timeout for auto-completions, :set completions can be updated more often than
|
||||||
|
|||||||
@@ -16,11 +16,9 @@
|
|||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<img src="chrome://pentadactyl/content/logo.png" alt="Pentadactyl" />
|
<img src="chrome://pentadactyl/content/logo.png" alt="Pentadactyl" />
|
||||||
version ###VERSION###
|
version ###VERSION###
|
||||||
by Martin Stubenschrott et al.
|
by Kris Maglione et al.
|
||||||
Pentadactyl is open source and freely distributable
|
Pentadactyl is open source and freely distributable
|
||||||
|
|
||||||
Sponsor Pentadactyl development!</div><div style="padding-left: 5em;">type :help sponsor<<span class="key">Enter</span>> for information
|
|
||||||
|
|
||||||
type :q<<span class="key">Enter</span>> to exit
|
type :q<<span class="key">Enter</span>> to exit
|
||||||
type :help<<span class="key">Enter</span>> or <<span class="key">F1</span>> for on-line help
|
type :help<<span class="key">Enter</span>> or <<span class="key">F1</span>> for on-line help
|
||||||
type :help version-2.1<<span class="key">Enter</span>> for version info
|
type :help version-2.1<<span class="key">Enter</span>> for version info
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
// given in the LICENSE.txt file included with this file.
|
// given in the LICENSE.txt file included with this file.
|
||||||
|
|
||||||
const Config = Module("config", ConfigBase, {
|
const Config = Module("config", ConfigBase, {
|
||||||
init: function () {
|
|
||||||
},
|
|
||||||
|
|
||||||
/*** required options, no checks done if they really exist, so be careful ***/
|
|
||||||
name: "Pentadactyl",
|
name: "Pentadactyl",
|
||||||
hostApplication: "Firefox",
|
hostApplication: "Firefox",
|
||||||
|
|
||||||
@@ -122,12 +118,13 @@ const Config = Module("config", ConfigBase, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
scripts: [
|
scripts: [
|
||||||
"browser.js",
|
"browser",
|
||||||
"bookmarks.js",
|
"bookmarkcache",
|
||||||
"history.js",
|
"bookmarks",
|
||||||
"quickmarks.js",
|
"history",
|
||||||
"sanitizer.js",
|
"quickmarks",
|
||||||
"tabs.js"
|
"sanitizer",
|
||||||
|
"tabs"
|
||||||
],
|
],
|
||||||
|
|
||||||
get tempFile() {
|
get tempFile() {
|
||||||
@@ -299,6 +296,7 @@ const Config = Module("config", ConfigBase, {
|
|||||||
"<Up>": modes.NORMAL | modes.INSERT,
|
"<Up>": modes.NORMAL | modes.INSERT,
|
||||||
"<Down>": modes.NORMAL | modes.INSERT
|
"<Down>": modes.NORMAL | modes.INSERT
|
||||||
};
|
};
|
||||||
|
config.modes.forEach(function (mode) { modes.addMode.apply(this, mode); });
|
||||||
},
|
},
|
||||||
options: function () {
|
options: function () {
|
||||||
options.add(["online"],
|
options.add(["online"],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<em:version>@VERSION@</em:version>
|
<em:version>@VERSION@</em:version>
|
||||||
<em:description>Make Firefox behave like Vim</em:description>
|
<em:description>Make Firefox behave like Vim</em:description>
|
||||||
<em:creator>Kris Maglione</em:creator>
|
<em:creator>Kris Maglione</em:creator>
|
||||||
<em:homepageURL>http://pentadactyl.sf.net/</em:homepageURL>
|
<em:homepageURL>http://dactyl.sf.net/</em:homepageURL>
|
||||||
<em:iconURL>chrome://pentadactyl/skin/icon.png</em:iconURL>
|
<em:iconURL>chrome://pentadactyl/skin/icon.png</em:iconURL>
|
||||||
<em:optionsURL>chrome://dactyl/content/preferences.xul</em:optionsURL>
|
<em:optionsURL>chrome://dactyl/content/preferences.xul</em:optionsURL>
|
||||||
<em:file>
|
<em:file>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<Description>
|
<Description>
|
||||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||||
<em:minVersion>3.5</em:minVersion>
|
<em:minVersion>3.5</em:minVersion>
|
||||||
<em:maxVersion>4.0</em:maxVersion>
|
<em:maxVersion>4.0b5pre</em:maxVersion>
|
||||||
</Description>
|
</Description>
|
||||||
</em:targetApplication>
|
</em:targetApplication>
|
||||||
</Description>
|
</Description>
|
||||||
|
|||||||
Reference in New Issue
Block a user