1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-02-04 22:15:45 +01:00

Merge groups branch.

This commit is contained in:
Kris Maglione
2011-02-11 04:20:27 -05:00
41 changed files with 2416 additions and 1459 deletions

View File

@@ -32,7 +32,7 @@ CHROME = $(MANGLE)/
JAR = $(CHROME)$(NAME).jar
XPI_BASES = $(JAR_BASES) $(TOP)/..
XPI_FILES = bootstrap.js install.rdf TODO AUTHORS Donors NEWS LICENSE.txt
XPI_FILES = bootstrap.js TODO AUTHORS Donors NEWS LICENSE.txt
XPI_DIRS = components $(MANGLE) defaults
XPI_TEXTS = js jsm $(JAR_TEXTS)
XPI_BINS = $(JAR_BINS)
@@ -49,6 +49,9 @@ BUILD_DIR = build.$(VERSION).$(OS)
AWK ?= awk
B64ENCODE ?= base64
CURL ?= curl
SED := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)/\1/' 2>/dev/null) ]; \
then echo sed -E; else echo sed -r; \
fi)
.SILENT:
@@ -174,11 +177,19 @@ test: xpi
xpi: $(CHROME)
@echo "Building XPI..."
mkdir -p "$(XPI_PATH)"
$(AWK) -v 'name=$(NAME)' -v 'suffix=$(MANGLE)' \
-f $(BASE)/process_manifest.awk \
$(TOP)/chrome.manifest >"$(XPI_PATH)/chrome.manifest"
"$(TOP)/chrome.manifest" >"$(XPI_PATH)/chrome.manifest"
version="$(VERSION)"; \
hg root >/dev/null 2>&1 && \
case "$$version" in \
*pre) version="$$version-hg$$(hg log -r . --template '{rev}')-$$(hg branch)";; \
esac; \
$(SED) -e 's/(em:version(>|="))([^"<]+)/\1'"$$version/" \
<"$(TOP)/install.rdf" >"$(XPI_PATH)/install.rdf"
$(MAKE_JAR) "$(XPI)" "$(XPI_BASES)" "$(XPI_DIRS)" "$(XPI_TEXTS)" "$(XPI_BINS)" "$(XPI_FILES)"
rm -r -- $(CHROME)
@echo "Built XPI: $(XPI)"

View File

@@ -241,7 +241,7 @@ var Abbreviations = Module("abbreviations", {
abbreviations.list(modes, lhs || "");
else {
if (args["-javascript"])
rhs = Command.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]);
rhs = contexts.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]);
abbreviations.add(modes, lhs, rhs);
}
}, {

View File

@@ -8,13 +8,18 @@
/** @scope modules */
var AutoCommand = Struct("event", "patterns", "command");
var AutoCommand = Struct("event", "filter", "command");
update(AutoCommand.prototype, {
eventName: Class.memoize(function () this.event.toLowerCase()),
/**
* @instance autocommands
*/
var AutoCommands = Module("autocommands", {
init: function () {
match: function (event, pattern) {
return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === pattern);
}
});
var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
this._store = [];
},
@@ -26,24 +31,26 @@ var AutoCommands = Module("autocommands", {
*
* @param {Array} events The array of event names for which this
* autocommand should be executed.
* @param {string} regexp The URL pattern to match against the buffer URL.
* @param {string} pattern The URL pattern to match against the buffer URL.
* @param {string} cmd The Ex command to run.
*/
add: function (events, regexp, cmd) {
events.forEach(function (event) {
this._store.push(AutoCommand(event, Option.parse.regexplist(regexp.source || regexp), cmd));
}, this);
add: function (events, pattern, cmd) {
if (!callable(pattern))
pattern = Group.compileFilter(pattern);
for (let event in values(events))
this._store.push(AutoCommand(event, pattern, cmd));
},
/**
* Returns all autocommands with a matching *event* and *regexp*.
*
* @param {string} event The event name filter.
* @param {string} regexp The URL pattern filter.
* @param {string} pattern The URL pattern filter.
* @returns {AutoCommand[]}
*/
get: function (event, regexp) {
return this._store.filter(function (autoCmd) AutoCommands.matchAutoCmd(autoCmd, event, regexp));
get: function (event, pattern) {
return this._store.filter(function (autoCmd) autoCmd.match(event, regexp));
},
/**
@@ -53,8 +60,27 @@ var AutoCommands = Module("autocommands", {
* @param {string} regexp The URL pattern filter.
*/
remove: function (event, regexp) {
this._store = this._store.filter(function (autoCmd) !AutoCommands.matchAutoCmd(autoCmd, event, regexp));
this._store = this._store.filter(function (autoCmd) !autoCmd.match(event, regexp));
},
});
/**
* @instance autocommands
*/
var AutoCommands = Module("autocommands", {
init: function () {
update(this, {
hives: contexts.Hives("autocmd", AutoCmdHive),
user: contexts.hives.autocmd.user
});
},
get activeHives() contexts.initializedGroups("autocmd")
.filter(function (h) h._store.length),
add: deprecated("autocommand.user.add", { get: function add() autocommands.user.closure.add }),
get: deprecated("autocommand.user.get", { get: function get() autocommands.user.closure.get }),
remove: deprecated("autocommand.user.remove", { get: function remove() autocommands.user.closure.remove }),
/**
* Lists all autocommands with a matching *event* and *regexp*.
@@ -63,32 +89,39 @@ var AutoCommands = Module("autocommands", {
* @param {string} regexp The URL pattern filter.
*/
list: function (event, regexp) {
let cmds = {};
// XXX
this._store.forEach(function (autoCmd) {
if (AutoCommands.matchAutoCmd(autoCmd, event, regexp)) {
cmds[autoCmd.event] = cmds[autoCmd.event] || [];
cmds[autoCmd.event].push(autoCmd);
}
});
function cmds(hive) {
let cmds = {};
hive._store.forEach(function (autoCmd) {
if (autoCmd.match(event, regexp)) {
cmds[autoCmd.event] = cmds[autoCmd.event] || [];
cmds[autoCmd.event].push(autoCmd);
}
});
return cmds;
}
commandline.commandOutput(
<table>
<tr highlight="Title">
<td colspan="2">----- Auto Commands -----</td>
<td colspan="3">----- Auto Commands -----</td>
</tr>
{
template.map(cmds, function ([event, items])
template.map(this.activeHives, function (hive)
<tr highlight="Title">
<td colspan="2">{event}</td>
</tr>
+
template.map(items, function (item)
<tr>
<td>&#xa0;{item.patterns}</td>
<td>{item.command}</td>
</tr>))
<td colspan="3">{hive.name}</td>
</tr> +
<tr style="height: .5ex;"/> +
template.map(cmds(hive), function ([event, items])
<tr style="height: .5ex;"/> +
template.map(items, function (item, i)
<tr>
<td highlight="Title" style="padding-right: 1em;">{i == 0 ? event : ""}</td>
<td>{item.filter.toXML ? item.filter.toXML() : item.filter}</td>
<td>{item.command}</td>
</tr>) +
<tr style="height: .5ex;"/>) +
<tr style="height: .5ex;"/>)
}
</table>);
},
@@ -104,29 +137,31 @@ var AutoCommands = Module("autocommands", {
if (options.get("eventignore").has(event))
return;
let autoCmds = this._store.filter(function (autoCmd) autoCmd.event == event);
dactyl.echomsg('Executing ' + event + ' Auto commands for "*"', 8);
let lastPattern = null;
let url = args.url || "";
let uri = args.url ? util.newURI(args.url) : buffer.uri;
for (let [, autoCmd] in Iterator(autoCmds)) {
if (autoCmd.patterns.some(function (re) re.test(url) ^ !re.result)) {
if (!lastPattern || String(lastPattern) != String(autoCmd.patterns))
dactyl.echomsg("Executing " + event + " Auto commands for " + autoCmd.patterns, 8);
event = event.toLowerCase();
for (let hive in this.hives.iterValues()) {
let args = update({},
hive.argsExtra(arguments[1]),
arguments[1]);
lastPattern = autoCmd.patterns;
dactyl.echomsg("autocommand " + autoCmd.command, 9);
for (let autoCmd in values(hive._store))
if (autoCmd.eventName === event && autoCmd.filter(uri)) {
if (!lastPattern || lastPattern !== String(autoCmd.filter))
dactyl.echomsg("Executing " + event + " Auto commands for " + autoCmd.filter, 8);
dactyl.trapErrors(autoCmd.command, autoCmd, args);
}
lastPattern = String(autoCmd.filter);
dactyl.echomsg("autocommand " + autoCmd.command, 9);
dactyl.trapErrors(autoCmd.command, autoCmd, args);
}
}
}
}, {
matchAutoCmd: function (autoCmd, event, regexp) {
return (!event || autoCmd.event == event) && (!regexp || String(autoCmd.patterns) == regexp);
}
}, {
commands: function () {
commands.add(["au[tocmd]"],
@@ -135,29 +170,21 @@ var AutoCommands = Module("autocommands", {
let [event, regexp, cmd] = args;
let events = [];
try {
if (args.length > 1)
Option.parse.regexplist(regexp);
}
catch (e) {
dactyl.assert(false, "E475: Invalid argument: " + regexp);
}
if (event) {
// NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}|
let validEvents = Object.keys(config.autocommands);
let validEvents = Object.keys(config.autocommands).map(String.toLowerCase);
validEvents.push("*");
events = Option.parse.stringlist(event);
dactyl.assert(events.every(function (event) validEvents.indexOf(event) >= 0),
"E216: No such group or event: " + event);
dactyl.assert(events.every(function (event) validEvents.indexOf(event.toLowerCase()) >= 0),
"E216: No such group or event: " + event);
}
if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern
if (args.bang)
autocommands.remove(event, regexp);
cmd = Command.bindMacro(args, "-ex", function (params) params);
autocommands.add(events, regexp, cmd);
args["-group"].remove(event, regexp);
cmd = contexts.bindMacro(args, "-ex", function (params) params);
args["-group"].add(events, regexp, cmd);
}
else {
if (event == "*")
@@ -166,7 +193,7 @@ var AutoCommands = Module("autocommands", {
if (args.bang) {
// TODO: "*" only appears to work in Vim when there is a {group} specified
if (args[0] != "*" || args.length > 1)
autocommands.remove(event, regexp); // remove all
args["-group"].remove(event, regexp); // remove all
}
else
autocommands.list(event, regexp); // list all
@@ -183,6 +210,7 @@ var AutoCommands = Module("autocommands", {
keepQuotes: true,
literal: 2,
options: [
contexts.GroupFlag("autocmd"),
{
names: ["-javascript", "-js"],
description: "Interpret the action as JavaScript code rather than an Ex command"
@@ -245,7 +273,7 @@ var AutoCommands = Module("autocommands", {
};
},
javascript: function () {
JavaScript.setCompleter(autocommands.get, [function () Iterator(config.autocommands)]);
JavaScript.setCompleter(autocommands.user.get, [function () Iterator(config.autocommands)]);
},
options: function () {
options.add(["eventignore", "ei"],

View File

@@ -11,63 +11,206 @@
/**
* @instance browser
*/
var Browser = Module("browser", {
}, {
climbUrlPath: function (count) {
let url = buffer.documentURI.clone();
dactyl.assert(url instanceof Ci.nsIURL);
while (count-- && url.path != "/")
url.path = url.path.replace(/[^\/]+\/*$/, "");
dactyl.assert(!url.equals(buffer.documentURI));
dactyl.open(url.spec);
var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
init: function init() {
this.cleanupProgressListener = util.overlayObject(window.XULBrowserWindow,
this.progressListener);
util.addObserver(this);
},
incrementURL: function (count) {
let matches = buffer.uri.spec.match(/(.*?)(\d+)(\D*)$/);
dactyl.assert(matches);
let oldNum = matches[2];
cleanup: function () {
this.cleanupProgressListener();
this.observe.unregister();
},
// disallow negative numbers as trailing numbers are often proceeded by hyphens
let newNum = String(Math.max(parseInt(oldNum, 10) + count, 0));
if (/^0/.test(oldNum))
while (newNum.length < oldNum.length)
newNum = "0" + newNum;
observers: {
"chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", uri); },
"content-document-global-created": function (win, uri) {
let top = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
matches[2] = newNum;
dactyl.open(matches.slice(1).join(""));
if (top == window)
this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri);
}
},
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc, uri) {
if (!(uri || doc.location))
return;
uri = isObject(uri) ? uri : util.newURI(uri || doc.location.href);
let args = {
url: { toString: function () uri.spec, valueOf: function () uri },
title: doc.title
};
if (dactyl.has("tabs")) {
args.tab = tabs.getContentIndex(doc) + 1;
args.doc = {
valueOf: function () doc,
toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument"
};
}
autocommands.trigger(name, args);
},
events: {
DOMContentLoaded: function onDOMContentLoaded(event) {
let doc = event.originalTarget;
if (doc instanceof HTMLDocument)
this._triggerLoadAutocmd("DOMLoad", doc);
},
// TODO: see what can be moved to onDOMContentLoaded()
// event listener which is is called on each page load, even if the
// page is loaded in a background tab
load: function onLoad(event) {
let doc = event.originalTarget;
if (doc instanceof Document)
dactyl.initDocument(doc);
if (doc instanceof HTMLDocument) {
if (doc.defaultView.frameElement) {
// document is part of a frameset
// hacky way to get rid of "Transferring data from ..." on sites with frames
// when you click on a link inside a frameset, because asyncUpdateUI
// is not triggered there (Gecko bug?)
this.timeout(function () { statusline.updateUrl(); }, 10);
}
else {
// code which should happen for all (also background) newly loaded tabs goes here:
if (doc != config.browser.contentDocument)
dactyl.echomsg({ domains: [util.getHost(doc.location)], message: "Background tab loaded: " + (doc.title || doc.location.href) }, 3);
this._triggerLoadAutocmd("PageLoad", doc);
}
}
}
},
/**
* @property {Object} The document loading progress listener.
*/
progressListener: {
// XXX: function may later be needed to detect a canceled synchronous openURL()
onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) {
onStateChange.superapply(this, arguments);
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
// receive statechange events for loading images and other parts of the web page
if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
// This fires when the load event is initiated
// only thrown for the current tab, not when another tab changes
if (flags & Ci.nsIWebProgressListener.STATE_START) {
statusline.progress = 0;
while (document.commandDispatcher.focusedWindow == webProgress.DOMWindow
&& modes.have(modes.INPUT))
modes.pop();
}
else if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
// Workaround for bugs 591425 and 606877, dactyl bug #81
config.browser.mCurrentBrowser.collapsed = false;
if (!dactyl.focusedElement || dactyl.focusedElement === document.documentElement)
dactyl.focusContent();
statusline.updateUrl();
}
}
}),
// for notifying the user about secure web pages
onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) {
onSecurityChange.superapply(this, arguments);
if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN)
statusline.security = "broken";
else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
statusline.security = "extended";
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
statusline.security = "secure";
else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
statusline.security = "insecure";
if (webProgress && webProgress.DOMWindow)
webProgress.DOMWindow.document.dactylSecurity = statusline.security;
}),
onStatusChange: util.wrapCallback(function onStatusChange(webProgress, request, status, message) {
onStatusChange.superapply(this, arguments);
statusline.updateUrl(message);
}),
onProgressChange: util.wrapCallback(function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
onProgressChange.superapply(this, arguments);
if (webProgress && webProgress.DOMWindow)
webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress;
statusline.progress = curTotalProgress / maxTotalProgress;
}),
// happens when the users switches tabs
onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) {
onLocationChange.superapply(this, arguments);
delete contexts.groups;
statusline.updateUrl();
statusline.progress = "";
let win = webProgress.DOMWindow;
if (win && uri) {
statusline.progress = win.dactylProgress;
let oldURI = win.document.dactylURI;
if (win.document.dactylLoadIdx === webProgress.loadedTransIndex
|| !oldURI || uri.spec.replace(/#.*/, "") !== oldURI.replace(/#.*/, ""))
for (let frame in values(buffer.allFrames(win)))
frame.document.dactylFocusAllowed = false;
win.document.dactylURI = uri.spec;
win.document.dactylLoadIdx = webProgress.loadedTransIndex;
}
// Workaround for bugs 591425 and 606877, dactyl bug #81
let collapse = uri && uri.scheme === "dactyl" && webProgress.isLoadingDocument;
if (collapse)
dactyl.focus(document.documentElement);
config.browser.mCurrentBrowser.collapsed = collapse;
util.timeout(function () {
browser._triggerLoadAutocmd("LocationChange",
(win || content).document,
uri);
});
// if this is not delayed we get the position of the old buffer
util.timeout(function () {
statusline.updateBufferPosition();
statusline.updateZoomLevel();
if (loaded.commandline)
commandline.clear();
}, 500);
}),
// called at the very end of a page load
asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() {
asyncUpdateUI.superapply(this, arguments);
util.timeout(function () { statusline.updateUrl(); }, 100);
}),
setOverLink: util.wrapCallback(function setOverLink(link, b) {
setOverLink.superapply(this, arguments);
switch (options["showstatuslinks"]) {
case "status":
statusline.updateUrl(link ? "Link: " + link : null);
break;
case "command":
if (link)
dactyl.echo("Link: " + link, commandline.DISALLOW_MULTILINE);
else
commandline.clear();
break;
}
}),
}
}, {
options: function () {
options.add(["encoding", "enc"],
"The current buffer's character encoding",
"string", "UTF-8",
{
scope: Option.SCOPE_LOCAL,
getter: function () config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset,
setter: function (val) {
if (options["encoding"] == val)
return val;
// Stolen from browser.jar/content/browser/browser.js, more or less.
try {
config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset = val;
PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val);
getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
}
catch (e) { dactyl.reportError(e); }
return null;
},
completer: function (context) completion.charset(context)
});
}, {
events: function () {
events.listen(config.browser, browser, "events", true);
},
mappings: function () {
mappings.add([modes.NORMAL],
["y", "<yank-location>"], "Yank current location to the clipboard",
function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
// opening websites
mappings.add([modes.NORMAL],
["o"], "Open one or more URLs",
@@ -93,16 +236,6 @@ var Browser = Module("browser", {
"Open one or more URLs in a new window, based on current location",
function () { CommandExMode().open("winopen " + buffer.uri.spec); });
mappings.add([modes.NORMAL],
["<C-a>"], "Increment last number in URL",
function (args) { Browser.incrementURL(Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL],
["<C-x>"], "Decrement last number in URL",
function (args) { Browser.incrementURL(-Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL], ["~"],
"Open home directory",
function () { dactyl.open("~"); });
@@ -118,29 +251,12 @@ var Browser = Module("browser", {
dactyl.open(homepages, { from: "homepage", where: dactyl.NEW_TAB });
});
mappings.add([modes.NORMAL], ["gu"],
"Go to parent directory",
function (args) { Browser.climbUrlPath(Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL], ["gU"],
"Go to the root of the website",
function () { Browser.climbUrlPath(-1); });
mappings.add(modes.all, ["<C-l>"],
"Redraw the screen",
function () { ex.redraw(); });
},
commands: function () {
commands.add(["old-downl[oads]", "old-dl"],
"Show progress of current downloads",
function () {
dactyl.open("chrome://mozapps/content/downloads/downloads.xul",
{ from: "downloads"});
},
{ argCount: "0" });
commands.add(["o[pen]"],
"Open one or more URLs in the current tab",
function (args) { dactyl.open(args[0] || "about:blank"); },

View File

@@ -145,75 +145,6 @@ var Buffer = Module("buffer", {
let elem = event.originalTarget;
buffer.viewSource([elem.getAttribute("href"), Number(elem.getAttribute("line"))]);
};
this.cleanupProgressListener = util.overlayObject(window.XULBrowserWindow,
this.progressListener);
if (dactyl.has("tabs"))
for (let tab in values(tabs.allTabs))
if (tab.linkedBrowser.contentDocument.readyState === "complete")
dactyl.initDocument(tab.linkedBrowser.contentDocument);
},
cleanup: function () {
this.cleanupProgressListener();
},
getDefaultNames: function getDefaultNames(node) {
let url = node.href || node.src || node.documentURI;
let currExt = url.replace(/^.*?(?:\.([a-z0-9]+))?$/i, "$1").toLowerCase();
if (isinstance(node, [Document, HTMLImageElement])) {
let type = node.contentType || node.QueryInterface(Ci.nsIImageLoadingContent)
.getRequest(0).mimeType;
if (type === "text/plain")
var ext = "." + (currExt || "txt");
else
ext = "." + services.mime.getPrimaryExtension(type, currExt);
}
else if (currExt)
ext = "." + currExt;
else
ext = "";
let re = ext ? RegExp("(\\." + currExt + ")?$", "i") : /$/;
var names = [];
if (node.title)
names.push([node.title, "Page Name"]);
if (node.alt)
names.push([node.alt, "Alternate Text"]);
if (!isinstance(node, Document) && node.textContent)
names.push([node.textContent, "Link Text"]);
names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]);
return names.filter(function ([leaf, title]) leaf)
.map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent)
.replace(re, ext), title]);
},
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc, uri) {
if (!(uri || doc.location))
return;
uri = uri || util.newURI(doc.location.href);
let args = {
url: { toString: function () uri.spec, valueOf: function () uri },
title: doc.title
};
if (dactyl.has("tabs")) {
args.tab = tabs.getContentIndex(doc) + 1;
args.doc = {
valueOf: function () doc,
toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument"
};
}
autocommands.trigger(name, args);
},
// called when the active document is scrolled
@@ -222,158 +153,6 @@ var Buffer = Module("buffer", {
commandline.clear();
},
onDOMContentLoaded: function onDOMContentLoaded(event) {
let doc = event.originalTarget;
if (doc instanceof HTMLDocument)
this._triggerLoadAutocmd("DOMLoad", doc);
},
// TODO: see what can be moved to onDOMContentLoaded()
// event listener which is is called on each page load, even if the
// page is loaded in a background tab
onPageLoad: function onPageLoad(event) {
let doc = event.originalTarget;
if (doc instanceof Document)
dactyl.initDocument(doc);
if (doc instanceof HTMLDocument) {
if (doc.defaultView.frameElement) {
// document is part of a frameset
// hacky way to get rid of "Transferring data from ..." on sites with frames
// when you click on a link inside a frameset, because asyncUpdateUI
// is not triggered there (Gecko bug?)
this.timeout(function () { statusline.updateUrl(); }, 10);
}
else {
// code which should happen for all (also background) newly loaded tabs goes here:
if (doc != config.browser.contentDocument)
dactyl.echomsg({ domains: [util.getHost(doc.location)], message: "Background tab loaded: " + (doc.title || doc.location.href) }, 3);
this._triggerLoadAutocmd("PageLoad", doc);
}
}
},
/**
* @property {Object} The document loading progress listener.
*/
progressListener: {
dactylLoadCount: 0,
// XXX: function may later be needed to detect a canceled synchronous openURL()
onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) {
onStateChange.superapply(this, arguments);
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
// receive statechange events for loading images and other parts of the web page
if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
// This fires when the load event is initiated
// only thrown for the current tab, not when another tab changes
if (flags & Ci.nsIWebProgressListener.STATE_START) {
statusline.progress = 0;
buffer._triggerLoadAutocmd("PageLoadPre", webProgress.DOMWindow.document);
if (document.commandDispatcher.focusedWindow == webProgress.DOMWindow && this.dactylLoadCount++)
util.timeout(function () { modes.reset(false); },
modes.main == modes.HINTS ? 500 : 0);
}
else if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
// Workaround for bugs 591425 and 606877, dactyl bug #81
config.browser.mCurrentBrowser.collapsed = false;
if (!dactyl.focusedElement || dactyl.focusedElement === document.documentElement)
dactyl.focusContent();
statusline.updateUrl();
}
}
}),
// for notifying the user about secure web pages
onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) {
onSecurityChange.superapply(this, arguments);
if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN)
statusline.security = "broken";
else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
statusline.security = "extended";
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
statusline.security = "secure";
else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
statusline.security = "insecure";
if (webProgress && webProgress.DOMWindow)
webProgress.DOMWindow.document.dactylSecurity = statusline.security;
}),
onStatusChange: util.wrapCallback(function onStatusChange(webProgress, request, status, message) {
onStatusChange.superapply(this, arguments);
statusline.updateUrl(message);
}),
onProgressChange: util.wrapCallback(function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
onProgressChange.superapply(this, arguments);
if (webProgress && webProgress.DOMWindow)
webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress;
statusline.progress = curTotalProgress / maxTotalProgress;
}),
// happens when the users switches tabs
onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) {
onLocationChange.superapply(this, arguments);
delete mappings.hives;
statusline.updateUrl();
statusline.progress = "";
let win = webProgress.DOMWindow;
if (win && uri) {
statusline.progress = win.dactylProgress;
let oldURI = win.document.dactylURI;
if (win.document.dactylLoadIdx === webProgress.loadedTransIndex
|| !oldURI || uri.spec.replace(/#.*/, "") !== oldURI.replace(/#.*/, ""))
for (let frame in values(buffer.allFrames(win)))
frame.document.dactylFocusAllowed = false;
win.document.dactylURI = uri.spec;
win.document.dactylLoadIdx = webProgress.loadedTransIndex;
}
// Workaround for bugs 591425 and 606877, dactyl bug #81
let collapse = uri && uri.scheme === "dactyl" && webProgress.isLoadingDocument;
if (collapse)
dactyl.focus(document.documentElement);
config.browser.mCurrentBrowser.collapsed = collapse;
util.timeout(function () {
buffer._triggerLoadAutocmd("LocationChange",
(win || content).document,
uri);
});
// if this is not delayed we get the position of the old buffer
util.timeout(function () {
statusline.updateBufferPosition();
statusline.updateZoomLevel();
if (loaded.commandline)
commandline.clear();
}, 500);
}),
// called at the very end of a page load
asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() {
asyncUpdateUI.superapply(this, arguments);
util.timeout(function () { statusline.updateUrl(); }, 100);
}),
setOverLink: util.wrapCallback(function setOverLink(link, b) {
setOverLink.superapply(this, arguments);
switch (options["showstatuslinks"]) {
case "status":
statusline.updateUrl(link ? "Link: " + link : null);
break;
case "command":
if (link)
dactyl.echo("Link: " + link, commandline.DISALLOW_MULTILINE);
else
commandline.clear();
break;
}
}),
},
/**
* @property {Array} The alternative style sheets for the current
* buffer. Only returns style sheets for the 'screen' media type.
@@ -386,6 +165,32 @@ var Buffer = Module("buffer", {
);
},
climbUrlPath: function (count) {
let url = buffer.documentURI.clone();
dactyl.assert(url instanceof Ci.nsIURL);
while (count-- && url.path != "/")
url.path = url.path.replace(/[^\/]+\/*$/, "");
dactyl.assert(!url.equals(buffer.documentURI));
dactyl.open(url.spec);
},
incrementURL: function (count) {
let matches = buffer.uri.spec.match(/(.*?)(\d+)(\D*)$/);
dactyl.assert(matches);
let oldNum = matches[2];
// disallow negative numbers as trailing numbers are often proceeded by hyphens
let newNum = String(Math.max(parseInt(oldNum, 10) + count, 0));
if (/^0/.test(oldNum))
while (newNum.length < oldNum.length)
newNum = "0" + newNum;
matches[2] = newNum;
dactyl.open(matches.slice(1).join(""));
},
/**
* @property {Object} A map of page info sections to their
* content generating functions.
@@ -534,11 +339,8 @@ var Buffer = Module("buffer", {
let range = selection.getRangeAt(0).cloneRange();
if (range.collapsed) {
let re = options.get("iskeyword").regexp;
util.dump(String.quote(range));
Editor.extendRange(range, true, re, true);
util.dump(String.quote(range));
Editor.extendRange(range, false, re, true);
util.dump(String.quote(range) + "\n\n\n");
}
return util.domToString(range);
},
@@ -772,7 +574,7 @@ var Buffer = Module("buffer", {
onSubmit: function (path) {
let file = io.File(path);
if (file.exists() && file.isDirectory())
file.append(buffer.getDefaultNames(elem)[0][0]);
file.append(Buffer.getDefaultNames(elem)[0][0]);
try {
if (!file.exists())
@@ -1242,6 +1044,42 @@ var Buffer = Module("buffer", {
setZoom: deprecated("buffer.setZoom", function setZoom() buffer.setZoom.apply(buffer, arguments)),
bumpZoomLevel: deprecated("buffer.bumpZoomLevel", function bumpZoomLevel() buffer.bumpZoomLevel.apply(buffer, arguments)),
getDefaultNames: function getDefaultNames(node) {
let url = node.href || node.src || node.documentURI;
let currExt = url.replace(/^.*?(?:\.([a-z0-9]+))?$/i, "$1").toLowerCase();
if (isinstance(node, [Document, HTMLImageElement])) {
let type = node.contentType || node.QueryInterface(Ci.nsIImageLoadingContent)
.getRequest(0).mimeType;
if (type === "text/plain")
var ext = "." + (currExt || "txt");
else
ext = "." + services.mime.getPrimaryExtension(type, currExt);
}
else if (currExt)
ext = "." + currExt;
else
ext = "";
let re = ext ? RegExp("(\\." + currExt + ")?$", "i") : /$/;
var names = [];
if (node.title)
names.push([node.title, "Page Name"]);
if (node.alt)
names.push([node.alt, "Alternate Text"]);
if (!isinstance(node, Document) && node.textContent)
names.push([node.textContent, "Link Text"]);
names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]);
return names.filter(function ([leaf, title]) leaf)
.map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent)
.replace(re, ext), title]);
},
findScrollableWindow: deprecated("buffer.findScrollableWindow", function findScrollableWindow() buffer.findScrollableWindow.apply(buffer, arguments)),
findScrollable: deprecated("buffer.findScrollable", function findScrollable() buffer.findScrollable.apply(buffer, arguments)),
@@ -1495,7 +1333,7 @@ var Buffer = Module("buffer", {
let file = io.File(filename.replace(RegExp(File.PATH_SEP + "*$"), ""));
if (filename.substr(-1) === File.PATH_SEP || file.exists() && file.isDirectory())
file.append(buffer.getDefaultNames(doc)[0][0]);
file.append(Buffer.getDefaultNames(doc)[0][0]);
dactyl.assert(args.bang || !file.exists(), "E13: File exists (add ! to override)");
@@ -1647,19 +1485,38 @@ var Buffer = Module("buffer", {
completion.savePage = function savePage(context, node) {
context.fork("generated", context.filter.replace(/[^/]*$/, "").length,
this, function (context) {
context.completions = buffer.getDefaultNames(node);
context.completions = Buffer.getDefaultNames(node);
});
};
},
events: function () {
events.addSessionListener(config.browser, "DOMContentLoaded", buffer.closure.onDOMContentLoaded, true);
events.addSessionListener(config.browser, "load", buffer.closure.onPageLoad, true);
events.addSessionListener(config.browser, "scroll", buffer.closure._updateBufferPosition, false);
events.listen(config.browser, "scroll", buffer.closure._updateBufferPosition, false);
},
mappings: function () {
var myModes = config.browserModes;
mappings.add([modes.NORMAL],
["y", "<yank-location>"], "Yank current location to the clipboard",
function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
mappings.add(myModes, [".", "<repeat-key>"],
mappings.add([modes.NORMAL],
["<C-a>"], "Increment last number in URL",
function (args) { buffer.incrementURL(Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL],
["<C-x>"], "Decrement last number in URL",
function (args) { buffer.incrementURL(-Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL], ["gu"],
"Go to parent directory",
function (args) { buffer.climbUrlPath(Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.NORMAL], ["gU"],
"Go to the root of the website",
function () { buffer.climbUrlPath(-1); });
mappings.add(modes.COMMAND, [".", "<repeat-key>"],
"Repeat the last key event",
function (args) {
if (mappings.repeat) {
@@ -1669,54 +1526,54 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add(myModes, ["i", "<Insert>"],
mappings.add(modes.COMMAND, ["i", "<Insert>"],
"Start caret mode",
function () { modes.push(modes.CARET); });
mappings.add(myModes, ["<C-c>"],
mappings.add(modes.COMMAND, ["<C-c>"],
"Stop loading the current web page",
function () { ex.stop(); });
// scrolling
mappings.add(myModes, ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
mappings.add(modes.COMMAND, ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
"Scroll document down",
function (args) { buffer.scrollVertical("lines", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
mappings.add(modes.COMMAND, ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
"Scroll document up",
function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["h", "<scroll-left-column>"] : ["h", "<Left>", "<scroll-left-column>"],
mappings.add(modes.COMMAND, dactyl.has("mail") ? ["h", "<scroll-left-column>"] : ["h", "<Left>", "<scroll-left-column>"],
"Scroll document to the left",
function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
mappings.add(modes.COMMAND, dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
"Scroll document to the right",
function (args) { buffer.scrollHorizontal("columns", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["0", "^", "<scroll-begin>"],
mappings.add(modes.COMMAND, ["0", "^", "<scroll-begin>"],
"Scroll to the absolute left of the document",
function () { buffer.scrollToPercent(0, null); });
mappings.add(myModes, ["$", "<scroll-end>"],
mappings.add(modes.COMMAND, ["$", "<scroll-end>"],
"Scroll to the absolute right of the document",
function () { buffer.scrollToPercent(100, null); });
mappings.add(myModes, ["gg", "<Home>"],
mappings.add(modes.COMMAND, ["gg", "<Home>"],
"Go to the top of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 0); },
{ count: true });
mappings.add(myModes, ["G", "<End>"],
mappings.add(modes.COMMAND, ["G", "<End>"],
"Go to the end of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
{ count: true });
mappings.add(myModes, ["%", "<scroll-percent>"],
mappings.add(modes.COMMAND, ["%", "<scroll-percent>"],
"Scroll to {count} percent of the document",
function (args) {
dactyl.assert(args.count > 0 && args.count <= 100);
@@ -1724,59 +1581,59 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add(myModes, ["<C-d>", "<scroll-down>"],
mappings.add(modes.COMMAND, ["<C-d>", "<scroll-down>"],
"Scroll window downwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, true); },
{ count: true });
mappings.add(myModes, ["<C-u>", "<scroll-up>"],
mappings.add(modes.COMMAND, ["<C-u>", "<scroll-up>"],
"Scroll window upwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, false); },
{ count: true });
mappings.add(myModes, ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
mappings.add(modes.COMMAND, ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
"Scroll up a full page",
function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
mappings.add(modes.COMMAND, ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
"Scroll down a full page",
function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]f", "<previous-frame>"],
mappings.add(modes.COMMAND, ["]f", "<previous-frame>"],
"Focus next frame",
function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["[f", "<next-frame>"],
mappings.add(modes.COMMAND, ["[f", "<next-frame>"],
"Focus previous frame",
function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); },
{ count: true });
mappings.add(myModes, ["]]", "<next-page>"],
mappings.add(modes.COMMAND, ["]]", "<next-page>"],
"Follow the link labeled 'next' or '>' if it exists",
function (args) {
buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add(myModes, ["[[", "<previous-page>"],
mappings.add(modes.COMMAND, ["[[", "<previous-page>"],
"Follow the link labeled 'prev', 'previous' or '<' if it exists",
function (args) {
buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add(myModes, ["gf", "<view-source>"],
mappings.add(modes.COMMAND, ["gf", "<view-source>"],
"Toggle between rendered and source view",
function () { buffer.viewSource(null, false); });
mappings.add(myModes, ["gF", "<view-source-externally>"],
mappings.add(modes.COMMAND, ["gF", "<view-source-externally>"],
"View source with an external editor",
function () { buffer.viewSource(null, true); });
mappings.add(myModes, ["gi", "<focus-input>"],
mappings.add(modes.COMMAND, ["gi", "<focus-input>"],
"Focus last used input field",
function (args) {
let elem = buffer.lastInputField;
@@ -1808,7 +1665,7 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add(myModes, ["gP"],
mappings.add(modes.COMMAND, ["gP"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1816,7 +1673,7 @@ var Buffer = Module("buffer", {
dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true });
});
mappings.add(myModes, ["p", "<MiddleMouse>", "<open-clipboard-url>"],
mappings.add(modes.COMMAND, ["p", "<MiddleMouse>", "<open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in the current buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1824,7 +1681,7 @@ var Buffer = Module("buffer", {
dactyl.open(url);
});
mappings.add(myModes, ["P", "<tab-open-clipboard-url>"],
mappings.add(modes.COMMAND, ["P", "<tab-open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
let url = dactyl.clipboardRead();
@@ -1833,16 +1690,16 @@ var Buffer = Module("buffer", {
});
// reloading
mappings.add(myModes, ["r", "<reload>"],
mappings.add(modes.COMMAND, ["r", "<reload>"],
"Reload the current web page",
function () { tabs.reload(tabs.getTab(), false); });
mappings.add(myModes, ["R", "<full-reload>"],
mappings.add(modes.COMMAND, ["R", "<full-reload>"],
"Reload while skipping the cache",
function () { tabs.reload(tabs.getTab(), true); });
// yanking
mappings.add(myModes, ["Y", "<yank-word>"],
mappings.add(modes.COMMAND, ["Y", "<yank-word>"],
"Copy selected text or current word",
function () {
let sel = buffer.currentWord;
@@ -1851,66 +1708,88 @@ var Buffer = Module("buffer", {
});
// zooming
mappings.add(myModes, ["zi", "+", "<text-zoom-in>"],
mappings.add(modes.COMMAND, ["zi", "+", "<text-zoom-in>"],
"Enlarge text zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zm", "<text-zoom-more>"],
mappings.add(modes.COMMAND, ["zm", "<text-zoom-more>"],
"Enlarge text zoom of current web page by a larger amount",
function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, false); },
{ count: true });
mappings.add(myModes, ["zo", "-", "<text-zoom-out>"],
mappings.add(modes.COMMAND, ["zo", "-", "<text-zoom-out>"],
"Reduce text zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), false); },
{ count: true });
mappings.add(myModes, ["zr", "<text-zoom-reduce>"],
mappings.add(modes.COMMAND, ["zr", "<text-zoom-reduce>"],
"Reduce text zoom of current web page by a larger amount",
function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, false); },
{ count: true });
mappings.add(myModes, ["zz", "<text-zoom>"],
mappings.add(modes.COMMAND, ["zz", "<text-zoom>"],
"Set text zoom value of current web page",
function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, false); },
{ count: true });
mappings.add(myModes, ["ZI", "zI", "<full-zoom-in>"],
mappings.add(modes.COMMAND, ["ZI", "zI", "<full-zoom-in>"],
"Enlarge full zoom of current web page",
function (args) { buffer.zoomIn(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZM", "zM", "<full-zoom-more>"],
mappings.add(modes.COMMAND, ["ZM", "zM", "<full-zoom-more>"],
"Enlarge full zoom of current web page by a larger amount",
function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, true); },
{ count: true });
mappings.add(myModes, ["ZO", "zO", "<full-zoom-out>"],
mappings.add(modes.COMMAND, ["ZO", "zO", "<full-zoom-out>"],
"Reduce full zoom of current web page",
function (args) { buffer.zoomOut(Math.max(args.count, 1), true); },
{ count: true });
mappings.add(myModes, ["ZR", "zR", "<full-zoom-reduce>"],
mappings.add(modes.COMMAND, ["ZR", "zR", "<full-zoom-reduce>"],
"Reduce full zoom of current web page by a larger amount",
function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, true); },
{ count: true });
mappings.add(myModes, ["zZ", "<full-zoom>"],
mappings.add(modes.COMMAND, ["zZ", "<full-zoom>"],
"Set full zoom value of current web page",
function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, true); },
{ count: true });
// page info
mappings.add(myModes, ["<C-g>", "<page-info>"],
mappings.add(modes.COMMAND, ["<C-g>", "<page-info>"],
"Print the current file name",
function () { buffer.showPageInfo(false); });
mappings.add(myModes, ["g<C-g>", "<more-page-info>"],
mappings.add(modes.COMMAND, ["g<C-g>", "<more-page-info>"],
"Print file information",
function () { buffer.showPageInfo(true); });
},
options: function () {
options.add(["encoding", "enc"],
"The current buffer's character encoding",
"string", "UTF-8",
{
scope: Option.SCOPE_LOCAL,
getter: function () config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset,
setter: function (val) {
if (options["encoding"] == val)
return val;
// Stolen from browser.jar/content/browser/browser.js, more or less.
try {
config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset = val;
PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val);
getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
}
catch (e) { dactyl.reportError(e); }
return null;
},
completer: function (context) completion.charset(context)
});
options.add(["iskeyword", "isk"],
"Regular expression defining which characters constitute word characters",
"string", '[^\\s.,!?:;/"\'^$%&?()[\\]{}<>#*+|=~_-]',

View File

@@ -424,11 +424,13 @@ var CommandExMode = Class("CommandExMode", CommandMode, {
},
onSubmit: function onSubmit(command) {
io.withSavedValues(["readHeredoc", "sourcing"], function () {
this.sourcing = { file: "[Command Line]", line: 1 };
this.readHeredoc = commandline.readHeredoc;
commands.repeat = command;
dactyl.execute(command);
contexts.withContext({ file: "[Command Line]", line: 1 },
function () {
io.withSavedValues(["readHeredoc"], function () {
this.readHeredoc = commandline.readHeredoc;
commands.repeat = command;
dactyl.execute(command);
});
});
}
});
@@ -985,7 +987,9 @@ var CommandLine = Module("commandline", {
dactyl.registerObserver("events.doneFeeding", this.closure.onDoneFeeding, true);
this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
if (!events.feedingKeys && options["autocomplete"].length) {
if (events.feedingKeys)
this.ignoredCount++;
if (options["autocomplete"].length) {
this.complete(true, false);
this.itemList.visible = true;
}
@@ -1003,8 +1007,11 @@ var CommandLine = Module("commandline", {
this.itemList.visible = false;
},
ignoredCount: 0,
onDoneFeeding: function onDoneFeeding() {
this.autocompleteTimer.flush(true);
if (this.ignoredCount)
this.autocompleteTimer.flush(true);
this.ignoredCount = 0;
},
UP: {},

View File

@@ -397,9 +397,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (jsmodules.__proto__ != window)
str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
let info = contexts.context;
if (fileName == null)
if (io.sourcing && io.sourcing.file[0] !== "[")
({ file: fileName, line: lineNumber, context: ctxt }) = io.sourcing;
if (info && info.file[0] !== "[")
({ file: fileName, line: lineNumber, context: ctxt }) = info;
else try {
if (!context)
context = userContext || ctxt;
@@ -410,8 +411,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
this.loadScript("resource://dactyl-content/eval.js", context);
if (context[EVAL_ERROR]) {
try {
context[EVAL_ERROR].fileName = io.sourcing.file;
context[EVAL_ERROR].lineNumber += io.sourcing.line;
context[EVAL_ERROR].fileName = info.file;
context[EVAL_ERROR].lineNumber += info.line;
}
catch (e) {}
throw context[EVAL_ERROR];
@@ -677,7 +678,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
fileMap["versions"] = function () {
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec).responseText;
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
{ mimeType: "text/plain;charset=UTF-8" })
.responseText;
let re = util.regexp(<![CDATA[
^ (?P<space> \s*)
@@ -725,8 +728,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}
list = null;
if (level == 0 && /^.*:\n$/.test())
var elem = <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
if (level == 0 && /^.*:\n$/.test(match.par))
res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
else {
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
res += <p highlight={group + " HelpNews"}>{
@@ -756,7 +759,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
unescape(encodeURI( // UTF-8 handling hack.
<document xmlns={NS} xmlns:dactyl={NS}
name="versions" title={config.appName + " Versions"}>
<h1 tag="versions news">{config.appName} Versions</h1>
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
<toc start="2"/>
{rec(NEWS, 0)}
@@ -1030,7 +1033,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
get: function globalVariables() this._globalVariables
}),
loadPlugins: function (args) {
loadPlugins: function (args, force) {
function sourceDirectory(dir) {
dactyl.assert(dir.isReadable(), "E484: Can't open file " + dir.path);
@@ -1041,9 +1044,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) }
dir.readDirectory(true).forEach(function (file) {
if (file.isFile() && loadplugins.getKey(file.path) && !(file.path in dactyl.pluginFiles)) {
if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) {
try {
io.source(file.path, false);
io.source(file.path);
dactyl.pluginFiles[file.path] = true;
}
catch (e) {
@@ -1356,8 +1359,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
*/
reportError: function reportError(error, echo) {
if (error instanceof FailedAssertion || error.message === "Interrupted") {
let prefix = io.sourcing ? io.sourcing.file + ":" + io.sourcing.line + ": " : "";
let context = contexts.context;
let prefix = context ? context.file + ":" + context.line + ": " : "";
if (error.message && error.message.indexOf(prefix) !== 0)
error.message = prefix + error.message;
@@ -1456,8 +1459,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}
}, {
events: function () {
events.addSessionListener(window, "click", dactyl.closure.onClick, true);
events.addSessionListener(window, "dactyl.execute", dactyl.closure.onExecute, true);
events.listen(window, "click", dactyl.closure.onClick, true);
events.listen(window, "dactyl.execute", dactyl.closure.onExecute, true);
},
// Only general options are added here, which are valid for all Dactyl extensions
options: function () {
@@ -1753,10 +1756,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
commands.add(["loadplugins", "lpl"],
"Load all plugins immediately",
function (args) {
dactyl.loadPlugins(args.length ? args : null);
dactyl.loadPlugins(args.length ? args : null, args.bang);
},
{
argCount: "*",
bang: true,
keepQuotes: true,
serialGroup: 10,
serialize: function () [
@@ -1792,7 +1796,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
commands.add(["reh[ash]"],
"Reload the " + config.appName + " add-on",
function (args) { util.rehash(args); },
function (args) {
if (args.trailing)
JSMLoader.rehashCmd = args.trailing; // Hack.
args.break = true;
util.rehash(args);
},
{
argCount: "0",
options: [
@@ -1825,8 +1834,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
function () { dactyl.restart(); });
function findToolbar(name) util.evaluateXPath(
"./*[@toolbarname=" + util.escapeString(name, "'") + "]",
toolbox).snapshotItem(0);
"//*[@toolbarname=" + util.escapeString(name, "'") + "]",
document).snapshotItem(0);
var toolbox = document.getElementById("navigator-toolbox");
if (toolbox) {
@@ -2010,7 +2019,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
completion.toolbar = function toolbar(context) {
context.title = ["Toolbar"];
context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
context.completions = util.evaluateXPath("./*[@toolbarname]", toolbox);
context.completions = util.evaluateXPath("//*[@toolbarname]", document);
};
completion.window = function window(context) {
@@ -2076,14 +2085,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (dactyl.commandLineOptions.rcFile) {
let filename = dactyl.commandLineOptions.rcFile;
if (!/^(NONE|NORC)$/.test(filename))
io.source(io.File(filename).path, false); // let io.source handle any read failure like Vim
io.source(io.File(filename).path, { group: contexts.user });
}
else {
if (init)
dactyl.execute(init);
else {
if (rcFile) {
io.source(rcFile.path, false);
io.source(rcFile.path, { group: contexts.user });
services.environment.set("MY_" + config.idName + "RC", rcFile.path);
}
else
@@ -2093,7 +2102,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (options["exrc"] && !dactyl.commandLineOptions.rcFile) {
let localRCFile = io.getRCFile(io.cwd);
if (localRCFile && !localRCFile.equals(rcFile))
io.source(localRCFile.path, false);
io.source(localRCFile.path, { group: contexts.user });
}
}
@@ -2118,6 +2127,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
dactyl.execute(cmd);
});
if (JSMLoader.rehashCmd)
dactyl.execute(JSMLoader.rehashCmd);
JSMLoader.rehashCmd = null;
dactyl.fullyInitialized = true;
dactyl.triggerObserver("enter", null);
autocommands.trigger("Enter", {});

View File

@@ -36,6 +36,8 @@ var Editor = Module("editor", {
let text = dactyl.clipboardRead(clipboard);
if (!text)
return;
if (isinstance(elem, [HTMLInputElement, XULTextBoxElement]))
text = text.replace(/\n+/g, "");
// This is a hacky fix - but it works.
// <s-insert> in the bottom of a long textarea bounces up
@@ -46,7 +48,9 @@ var Editor = Module("editor", {
let end = elem.selectionEnd;
let value = elem.value.substring(0, start) + text + elem.value.substring(end);
elem.value = value;
Editor.getEditor(elem).rootElement.firstChild.textContent = value;
elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length);
elem.selectionEnd = elem.selectionStart;

View File

@@ -38,8 +38,6 @@ var ProcessorStack = Class("ProcessorStack", {
},
execute: function execute(result, force) {
function dbg() {}
if (force && this.actions.length)
this.processors.length = 0;
@@ -59,7 +57,7 @@ var ProcessorStack = Class("ProcessorStack", {
for (var res = this.actions[0]; callable(res);) {
res = dactyl.trapErrors(res);
dbg("ACTION RES: " + res);
events.dbg("ACTION RES: " + res);
}
result = res === Events.PASS ? Events.PASS : Events.KILL;
}
@@ -73,17 +71,19 @@ var ProcessorStack = Class("ProcessorStack", {
else if (result === undefined)
result = Events.PASS;
dbg("RESULT: " + (result === Events.KILL ? "KILL" :
result === Events.PASS ? "PASS" :
result === Events.ABORT ? "ABORT" : result));
events.dbg("RESULT: " + (result === Events.KILL ? "KILL" :
result === Events.PASS ? "PASS" :
result === Events.ABORT ? "ABORT" : result));
if (result !== Events.PASS)
Events.kill(this.events[this.events.length - 1]);
if (result === Events.PASS || result === Events.ABORT) {
dbg("REFEED: " + this.events.filter(function (e) e.getPreventDefault()).map(events.closure.toString).join(""));
this.events.filter(function (e) e.getPreventDefault())
.forEach(function (event, i) {
let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented);
if (list.length)
events.dbg("REFEED: " + list.map(events.closure.toString).join(""));
list.forEach(function (event, i) {
let elem = event.originalTarget;
if (event.originalTarget) {
let doc = elem.ownerDocument || elem.document || elem;
@@ -102,8 +102,6 @@ var ProcessorStack = Class("ProcessorStack", {
},
process: function process(event) {
function dbg() {}
if (this.timer)
this.timer.cancel();
@@ -115,15 +113,15 @@ var ProcessorStack = Class("ProcessorStack", {
let actions = [];
let processors = [];
dbg("\n\n");
dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro);
events.dbg("\n\n");
events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro);
for (let [i, input] in Iterator(this.processors)) {
let res = input.process(event);
if (res !== Events.ABORT)
var result = res;
dbg("RES: " + input + " " + (callable(res) ? {}.toString.call(res) : res));
events.dbg("RES: " + input + " " + (callable(res) ? {}.toString.call(res) : res));
if (res === Events.KILL)
break;
@@ -139,9 +137,9 @@ var ProcessorStack = Class("ProcessorStack", {
processors.push(input);
}
dbg("RESULT: " + (callable(result) ? {}.toString.call(result) : result) + " " + event.getPreventDefault());
dbg("ACTIONS: " + actions.length + " " + this.actions.length);
dbg("PROCESSORS:", processors);
events.dbg("RESULT: " + (callable(result) ? {}.toString.call(result) : result) + " " + event.getPreventDefault());
events.dbg("ACTIONS: " + actions.length + " " + this.actions.length);
events.dbg("PROCESSORS:", processors);
this._actions = actions;
this.actions = actions.concat(this.actions);
@@ -257,13 +255,80 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, {
}
});
var EventHive = Class("EventHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
this.sessionListeners = [];
},
cleanup: function cleanup() {
this.unlisten(null);
},
/**
* Adds an event listener for this session and removes it on
* dactyl shutdown.
*
* @param {Element} target The element on which to listen.
* @param {string} event The event to listen for.
* @param {function} callback The function to call when the event is received.
* @param {boolean} capture When true, listen during the capture
* phase, otherwise during the bubbling phase.
*/
listen: function (target, event, callback, capture) {
if (isObject(event))
var [self, events] = [event, event[callback]];
else
[self, events] = [null, array.toObject([[event, callback]])];
for (let [event, callback] in Iterator(events)) {
let args = [Cu.getWeakReference(target),
event,
this.wrapListener(callback, self),
capture];
target.addEventListener.apply(target, args.slice(1));
this.sessionListeners.push(args);
}
},
/**
* Remove an event listener.
*
* @param {Element} target The element on which to listen.
* @param {string} event The event to listen for.
* @param {function} callback The function to call when the event is received.
* @param {boolean} capture When true, listen during the capture
* phase, otherwise during the bubbling phase.
*/
unlisten: function (target, event, callback, capture) {
this.sessionListeners = this.sessionListeners.filter(function (args) {
if (target == null || args[0].get() == target && args[1] == event && args[2] == callback && args[3] == capture) {
args[0].get().removeEventListener.apply(args[0].get(), args.slice(1));
return true;
}
return !args[0].get();
});
}
});
/**
* @instance events
*/
var Events = Module("events", {
dbg: function () {},
init: function () {
const self = this;
update(this, {
hives: contexts.Hives("events", EventHive),
user: contexts.hives.events.user,
builtin: contexts.hives.events.builtin
});
EventHive.prototype.wrapListener = this.closure.wrapListener;
XML.ignoreWhitespace = true;
util.overlayWindow(window, {
append: <e4x xmlns={XUL}>
@@ -285,8 +350,6 @@ var Events = Module("events", {
this._macroKeys = [];
this._lastMacro = "";
this.sessionListeners = [];
this._macros = storage.newMap("macros", { privateData: true, store: true });
for (let [k, m] in this._macros)
if (isString(m))
@@ -345,21 +408,13 @@ var Events = Module("events", {
}
this._activeMenubar = false;
for (let [event, callback] in Iterator(this.events))
this.addSessionListener(window, event, callback, true);
this.listen(window, this, "events", true);
dactyl.registerObserver("modeChange", function () {
delete self.processor;
});
},
destroy: function () {
util.dump("Removing all event listeners");
for (let args in values(this.sessionListeners))
if (args[0].get())
args[0].get().removeEventListener.apply(args[0].get(), args.slice(1));
},
/**
* Adds an event listener for this session and removes it on
* dactyl shutdown.
@@ -370,13 +425,8 @@ var Events = Module("events", {
* @param {boolean} capture When true, listen during the capture
* phase, otherwise during the bubbling phase.
*/
addSessionListener: function (target, event, callback, capture) {
let args = Array.slice(arguments, 0);
args[2] = this.wrapListener(callback);
args[0].addEventListener.apply(args[0], args.slice(1));
args[0] = Cu.getWeakReference(args[0]);
this.sessionListeners.push(args);
},
get addSessionListener() this.builtin.closure.listen,
get listen() this.builtin.closure.listen,
/**
* Wraps an event listener to ensure that errors are reported.
@@ -1053,7 +1103,7 @@ var Events = Module("events", {
// Hack to deal with <BS> and so forth not dispatching input
// events
if (event.originalTarget instanceof HTMLInputElement && !modes.main.passthrough) {
if (key && event.originalTarget instanceof HTMLInputElement && !modes.main.passthrough) {
let elem = event.originalTarget;
elem.dactylKeyPress = elem.value;
util.timeout(function () {

View File

@@ -677,7 +677,7 @@ var Hints = Module("hints", {
let appContent = document.getElementById("appcontent");
if (appContent)
events.addSessionListener(appContent, "scroll", this.resizeTimer.closure.tell, false);
events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false);
const Mode = Hints.Mode;
Mode.defaultValue("tags", function () function () options["hinttags"]);
@@ -1159,22 +1159,22 @@ var Hints = Module("hints", {
function ({ self }) { self.escapeNumbers = !self.escapeNumbers; });
},
options: function () {
const DEFAULT_HINTTAGS =
util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select",
"*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @tabindex or @role='link' or @role='button']"]);
function xpath(arg) Option.quote(util.makeXPath(arg));
function xpath(arg) util.makeXPath(arg);
options.add(["extendedhinttags", "eht"],
"XPath strings of hintable elements for extended hint modes",
"regexpmap", "[iI]:" + xpath(["img"]) +
",[asOTivVWy]:" + xpath(["{a,area}[@href]", "{img,iframe}[@src]"]) +
",[F]:" + xpath(["body", "code", "div", "html", "p", "pre", "span"]) +
",[S]:" + xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]),
"regexpmap", {
"[iI]": xpath(["img"]),
"[asOTivVWy]": xpath(["{a,area}[@href]", "{img,iframe}[@src]"]),
"[F]": xpath(["body", "code", "div", "html", "p", "pre", "span"]),
"[S]": xpath(["input[not(@type='hidden')]", "textarea", "button", "select"])
},
{ validator: Option.validateXPath });
options.add(["hinttags", "ht"],
"XPath string of hintable elements activated by 'f' and 'F'",
"string", DEFAULT_HINTTAGS,
"string", xpath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select",
"*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
"@tabindex or @role='link' or @role='button']"]),
{ validator: Option.validateXPath });
options.add(["hintkeys", "hk"],

View File

@@ -107,6 +107,10 @@ var Map = Class("Map", {
.map(function ([i, prop]) [prop, this[i]], arguments)
.toObject();
args = update({ context: contexts.context },
this.hive.argsExtra(args),
args);
let self = this;
function repeat() self.action(args)
if (this.names[0] != ".") // FIXME: Kludge.
@@ -134,18 +138,12 @@ var Map = Class("Map", {
id: 0
});
var MapHive = Class("MapHive", {
init: function init(name, description, filter) {
this.name = name;
var MapHive = Class("MapHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
this.stacks = {};
this.description = description;
this.filter = filter || function (uri) true;
},
get toStringParams() [this.name],
get builtin() mappings.builtinHives.indexOf(this) >= 0,
/**
* Iterates over all mappings present in all of the given *modes*.
*
@@ -172,7 +170,7 @@ var MapHive = Class("MapHive", {
extra = extra || {};
let map = Map(modes, keys, description, action, extra);
map.definedAt = Commands.getCaller(Components.stack.caller);
map.definedAt = contexts.getCaller(Components.stack.caller);
map.hive = this;
if (this.name !== "builtin")
@@ -299,16 +297,11 @@ var MapHive = Class("MapHive", {
*/
var Mappings = Module("mappings", {
init: function () {
this.user = MapHive("user", "User-defined mappings");
this.builtin = MapHive("builtin", "Builtin mappings");
this.builtinHives = array([this.user, this.builtin]);
this.allHives = [this.user, this.builtin];
},
repeat: Modes.boundProperty(),
hives: Class.memoize(function () array(this.allHives.filter(function (h) h.filter(buffer.uri)))),
get allHives() contexts.allGroups.mappings,
get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
@@ -344,7 +337,7 @@ var Mappings = Module("mappings", {
*/
add: function () {
let map = this.builtin.add.apply(this.builtin, arguments);
map.definedAt = Commands.getCaller(Components.stack.caller);
map.definedAt = contexts.getCaller(Components.stack.caller);
return map;
},
@@ -361,33 +354,10 @@ var Mappings = Module("mappings", {
*/
addUserMap: function () {
let map = this.user.add.apply(this.user, arguments);
map.definedAt = Commands.getCaller(Components.stack.caller);
map.definedAt = contexts.getCaller(Components.stack.caller);
return map;
},
addHive: function addHive(name, filter, description) {
this.removeHive(name);
let hive = MapHive(name, description, filter);
this.allHives.unshift(hive);
return hive;
},
removeHive: function removeHive(name, filter) {
let hive = this.getHive(name);
dactyl.assert(!hive || !hive.builtin, "Cannot remove builtin group");
if (hive)
this.allHives.splice(this.allHives.indexOf(hive), 1);
if (io.sourcing && io.sourcing.mapHive == hive)
io.sourcing.mapHive = null;
delete this.hives;
return hive;
},
getHive: function getHive(name) array.nth(this.allHives, function (h) h.name == name, 0) || null,
/**
* Returns the map from *mode* named *cmd*.
*
@@ -417,7 +387,7 @@ var Mappings = Module("mappings", {
* @param {string} filter The filter string to match.
*/
list: function (modes, filter, hives) {
hives = hives || mappings.userHives;
hives = (hives || mappings.userHives).filter(function (h) modes.some(function (m) h.getStack(m).length));
let modeSign = "";
modes.filter(function (m) m.char).forEach(function (m) { modeSign += m.char; });
@@ -462,7 +432,14 @@ var Mappings = Module("mappings", {
}
}, {
}, {
commands: function () {
contexts: function initContexts(dactyl, modules, window) {
update(Mappings.prototype, {
hives: contexts.Hives("mappings", MapHive),
user: contexts.hives.mappings.user,
builtin: contexts.hives.mappings.builtin
});
},
commands: function initCommands(dactyl, modules, window) {
function addMapCommands(ch, mapmodes, modeDescription) {
function map(args, noremap) {
let mapmodes = array.uniq(args["-modes"].map(findMode));
@@ -480,9 +457,12 @@ var Mappings = Module("mappings", {
if (!rhs) // list the mapping
mappings.list(mapmodes, mappings.expandLeader(lhs), hives);
else {
util.assert(args["-group"].modifiable,
"Cannot change mappings in the builtin group");
args["-group"].add(mapmodes, [lhs],
args["-description"],
Command.bindMacro(args, "-keys", function (params) params),
contexts.bindMacro(args, "-keys", function (params) params),
{
arg: args["-arg"],
count: args["-count"],
@@ -531,7 +511,7 @@ var Mappings = Module("mappings", {
names: ["-ex", "-e"],
description: "Execute this mapping as an Ex command rather than keys"
},
groupFlag,
contexts.GroupFlag("mappings"),
{
names: ["-javascript", "-js", "-j"],
description: "Execute this mapping as JavaScript rather than keys"
@@ -554,20 +534,16 @@ var Mappings = Module("mappings", {
serialize: function () {
return this.name != "map" ? [] :
array(mappings.userHives)
.filter(function (h) !h.noPersist)
.filter(function (h) h.persist)
.map(function (hive) [
{
command: "mapgroup",
bang: true,
arguments: [hive.name, String(hive.filter)].slice(0, hive.name == "user" ? 1 : 2)
}
].concat([
{
command: "map",
options: array([
hive !== mappings.user && ["-group", hive.name],
["-modes", uniqueModes(map.modes)],
["-description", map.description],
map.silent && ["-silent"]])
.filter(util.identity)
.toObject(),
arguments: [map.names[0]],
@@ -576,7 +552,7 @@ var Mappings = Module("mappings", {
}
for (map in userMappings(hive))
if (map.persist)
]))
])
.flatten().array;
}
};
@@ -602,6 +578,9 @@ var Mappings = Module("mappings", {
commands.add([ch + "mapc[lear]"],
"Remove all mappings" + modeDescription,
function (args) {
util.assert(args["-group"].modifiable,
"Cannot change mappings in the builtin group");
let mapmodes = array.uniq(args["-modes"].map(findMode));
mapmodes.forEach(function (mode) {
args["-group"].clear(mode);
@@ -610,7 +589,7 @@ var Mappings = Module("mappings", {
{
argCount: "0",
options: [
groupFlag,
contexts.GroupFlag("mappings"),
update({}, modeFlag, {
names: ["-modes", "-mode", "-m"],
type: CommandOption.LIST,
@@ -623,6 +602,9 @@ var Mappings = Module("mappings", {
commands.add([ch + "unm[ap]"],
"Remove a mapping" + modeDescription,
function (args) {
util.assert(args["-group"].modifiable,
"Cannot change mappings in the builtin group");
let mapmodes = array.uniq(args["-modes"].map(findMode));
let found = false;
@@ -639,7 +621,7 @@ var Mappings = Module("mappings", {
argCount: "1",
completer: opts.completer,
options: [
groupFlag,
contexts.GroupFlag("mappings"),
update({}, modeFlag, {
names: ["-modes", "-mode", "-m"],
type: CommandOption.LIST,
@@ -650,97 +632,6 @@ var Mappings = Module("mappings", {
});
}
commands.add(["mapg[roup]"],
"Create or select a mapping group",
function (args) {
dactyl.assert(args.length <= 2, "Trailing characters");
if (args.length == 0)
return void completion.listCompleter("mapGroup", "");
let name = Option.dequote(args[0]);
let hive = mappings.getHive(name);
if (args.length == 2) {
dactyl.assert(!hive || args.bang, "Group exists");
let filter = function siteFilter(uri)
siteFilter.filters.every(function (f) f(uri) == f.result);
update(filter, {
toString: function () this.filters.join(","),
filters: Option.splitList(args[1], true).map(function (pattern) {
let [, res, filter] = /^(!?)(.*)/.exec(pattern);
return update(Styles.matchFilter(Option.dequote(filter)), {
result: !res,
toString: function () pattern
});
})
});
hive = mappings.addHive(name, filter, args["-description"]);
if (args["-nopersist"])
hive.noPersist = true;
}
dactyl.assert(hive, "No mapping group: " + name);
dactyl.assert(hive.name != "builtin", "Can't map to builtin hive");
if (io.sourcing)
io.sourcing.mapHive = hive;
},
{
argCount: "*",
bang: true,
completer: function (context, args) {
if (args.length == 1)
completion.mapGroup(context);
else {
Option.splitList(context.filter);
context.advance(Option._splitAt);
context.compare = CompletionContext.Sort.unsorted;
context.completions = [
[buffer.uri.host, "Current Host"],
[buffer.uri.spec, "Current Page"]
];
}
},
keepQuotes: true,
options: [
{
names: ["-description", "-desc", "-d"],
description: "A description of this mapping group",
type: CommandOption.STRING
},
{
names: ["-nopersist", "-n"],
description: "Do not save this mapping group to an auto-generated RC file"
}
]
});
commands.add(["delmapg[roup]"],
"Delete a mapping group",
function (args) {
dactyl.assert(mappings.getHive(args[0]), "No mapping group: " + args[0]);
mappings.removeHive(args[0]);
},
{
argCount: "1",
completer: function (context, args) {
completion.mapGroup(context);
context.filters.push(function ({ item }) !item.builtin);
}
});
let groupFlag = {
names: ["-group", "-g"],
description: "Mapping group to which to add this mapping",
type: ArgType("map-group", function (group) isString(group) ? mappings.getHive(group) : group),
get default() io.sourcing && io.sourcing.mapHive || mappings.user,
completer: function (context) completion.mapGroup(context)
};
let modeFlag = {
names: ["-mode", "-m"],
type: CommandOption.STRING,
@@ -780,11 +671,18 @@ var Mappings = Module("mappings", {
addMapCommands("", [modes.NORMAL, modes.VISUAL], "");
for (let mode in modes.mainModes)
if (mode.char && !commands.get(mode.char + "map", true))
addMapCommands(mode.char,
[m.mask for (m in modes.mainModes) if (m.char == mode.char)],
[mode.name.toLowerCase()]);
let args = {
getMode: function (args) findMode(args["-mode"]),
iterate: function (args) {
let mainMode = this.getMode(args);
let seen = {};
// Bloody hell. --Kris
for (let mode in values([mainMode].concat(mainMode.bases)))
for (let hive in mappings.hives.iterValues())
for (let map in array.iterValues(hive.getStack(mode)))
@@ -794,7 +692,7 @@ var Mappings = Module("mappings", {
name: name,
columns: [
mode == mainMode ? "" : <span highlight="Object" style="padding-right: 1em;">{mode.name}</span>,
hive.name == "builtin" ? "" : <span highlight="Object" style="padding-right: 1em;">{hive.name}</span>
hive == mappings.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{hive.name}</span>
],
__proto__: map
};
@@ -840,19 +738,8 @@ var Mappings = Module("mappings", {
options: []
});
});
for (let mode in modes.mainModes)
if (mode.char && !commands.get(mode.char + "map", true))
addMapCommands(mode.char,
[m.mask for (m in modes.mainModes) if (m.char == mode.char)],
[mode.name.toLowerCase()]);
},
completion: function () {
completion.mapGroup = function mapGroup(context, modes) {
context.title = ["Map group"];
context.keys = { text: "name", description: function (h) h.description || h.filter };
context.completions = mappings.userHives;
};
completion: function initCompletion(dactyl, modules, window) {
completion.userMapping = function userMapping(context, modes, hive) {
// FIXME: have we decided on a 'standard' way to handle this clash? --djk
hive = hive || mappings.user;
@@ -861,20 +748,14 @@ var Mappings = Module("mappings", {
context.completions = hive.iterate(modes);
};
},
javascript: function () {
JavaScript.setCompleter(mappings.get,
javascript: function initJavascript(dactyl, modules, window) {
JavaScript.setCompleter([mappings.get, mappings.builtin.get],
[
null,
function (context, obj, args) {
let mode = args[0];
return array.flatten([
[[name, map.description] for ([i, name] in Iterator(map.names))]
for (map in mappings.iterate(mode))
]);
}
function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))]
]);
},
options: function () {
options: function initOptions(dactyl, modules, window) {
options.add(["mapleader", "ml"],
"Define the replacement keys for the <Leader> pseudo-key",
"string", "\\", {

View File

@@ -208,7 +208,7 @@ var Marks = Module("marks", {
events: function () {
let appContent = document.getElementById("appcontent");
if (appContent)
events.addSessionListener(appContent, "load", marks.closure._onPageLoad, true);
events.listen(appContent, "load", marks.closure._onPageLoad, true);
},
mappings: function () {
var myModes = config.browserModes;

View File

@@ -47,8 +47,7 @@ var Modes = Module("modes", {
count: false
});
this.addMode("COMMAND", {
description: "The base mode for most modes which accept commands rather than input",
hidden: true
description: "The base mode for most modes which accept commands rather than input"
});
this.addMode("NORMAL", {
@@ -246,6 +245,8 @@ var Modes = Module("modes", {
getCharModes: function (chr) (this.modeChars[chr] || []).slice(),
have: function have(mode) this._modeStack.some(function (m) isinstance(m.main, mode)),
matchModes: function (obj)
this._modes.filter(function (mode) Object.keys(obj)
.every(function (k) obj[k] == (mode[k] || false))),
@@ -398,7 +399,7 @@ var Modes = Module("modes", {
},
isinstance: function (obj)
this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
this === obj || this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
allBases: Class.memoize(function () {
let seen = {}, res = [], queue = this.bases;

View File

@@ -9,6 +9,33 @@
var MOW = Module("mow", {
init: function () {
this._resize = Timer(20, 400, function () {
if (this.visible)
this.resize(false);
if (this.visible && isinstance(modes.main, modes.OUTPUT_MULTILINE))
this.updateMorePrompt();
}, this);
this._timer = Timer(20, 400, function () {
if (modes.have(modes.OUTPUT_MULTILINE)) {
this.resize(true);
if (options["more"] && this.isScrollable(1)) {
// start the last executed command's output at the top of the screen
let elements = this.document.getElementsByClassName("ex-command-output");
elements[elements.length - 1].scrollIntoView(true);
}
else
this.body.scrollTop = this.body.scrollHeight;
dactyl.focus(this.window);
this.updateMorePrompt();
}
}, this);
events.listen(window, this, "windowEvents");
let fontSize = util.computedStyle(document.documentElement).fontSize;
styles.system.add("font-size", "dactyl://content/buffer.xhtml",
"body { font-size: " + fontSize + "; } \
@@ -56,8 +83,8 @@ var MOW = Module("mow", {
widgets: Class.memoize(function () commandline.widgets),
body: Class.memoize(function () this.widget.contentDocument.documentElement),
document: Class.memoize(function () this.widget.contentDocument),
window: Class.memoize(function () this.widget.contentWindow),
get document() this.widget.contentDocument,
get window() this.widget.contentWindow,
/**
* Display a multi-line message.
@@ -110,10 +137,9 @@ var MOW = Module("mow", {
// FIXME: need to make sure an open MOW is closed when commands
// that don't generate output are executed
if (this.widgets.mowContainer.collapsed) {
if (!this.visible) {
this.body.scrollTop = 0;
while (body.firstChild)
body.removeChild(body.firstChild);
body.textContent = "";
}
body.appendChild(output);
@@ -122,18 +148,9 @@ var MOW = Module("mow", {
if (!silent)
dactyl.triggerObserver("echoMultiline", data, highlightGroup, output);
this.resize(true);
if (options["more"] && this.isScrollable(1)) {
// start the last executed command's output at the top of the screen
let elements = this.document.getElementsByClassName("ex-command-output");
elements[elements.length - 1].scrollIntoView(true);
}
else
this.body.scrollTop = this.body.scrollHeight;
dactyl.focus(this.window);
this.updateMorePrompt();
this._timer.tell();
if (!this.visible)
this._timer.flush();
},
events: {
@@ -171,6 +188,12 @@ var MOW = Module("mow", {
}
},
windowEvents: {
resize: function onResize(event) {
this._resize.tell();
}
},
contextEvents: {
popupshowing: function (event) {
let menu = commandline.widgets.contextMenu;
@@ -207,14 +230,14 @@ var MOW = Module("mow", {
* already so.
*/
resize: function updateOutputHeight(open, extra) {
if (!open && this.widgets.mowContainer.collapsed)
if (!(open || this.visible))
return;
let doc = this.widget.contentDocument;
let availableHeight = config.outputHeight;
if (!this.widgets.mowContainer.collapsed)
availableHeight += parseFloat(this.widgets.mowContainer.height);
if (this.visible)
availableHeight += parseFloat(this.widgets.mowContainer.height || 0);
availableHeight -= extra || 0;
doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px";
@@ -224,6 +247,7 @@ var MOW = Module("mow", {
0);
doc.body.style.minWidth = "";
this.visible = true;
},
@@ -242,7 +266,7 @@ var MOW = Module("mow", {
* and what they do.
*/
updateMorePrompt: function updateMorePrompt(force, showHelp) {
if (this.widgets.mowContainer.collapsed)
if (!this.visible)
return this.widgets.message = null;
let elem = this.widget.contentDocument.documentElement;
@@ -255,6 +279,7 @@ var MOW = Module("mow", {
},
visible: Modes.boundProperty({
get: function get_mowVisible() !this.widgets.mowContainer.collapsed,
set: function set_mowVisible(value) {
this.widgets.mowContainer.collapsed = !value;

View File

@@ -161,24 +161,25 @@ var StatusLine = Module("statusline", {
// when session information is available, add [+] when we can go
// backwards, [-] when we can go forwards
let modified = "";
if (window.getWebNavigation) {
let sh = window.getWebNavigation().sessionHistory;
if (sh && sh.index > 0)
modified += "+";
if (sh && sh.index < sh.count - 1)
modified += "-";
if (url === buffer.uri.spec) {
if (window.getWebNavigation) {
let sh = window.getWebNavigation().sessionHistory;
if (sh && sh.index > 0)
modified += "+";
if (sh && sh.index < sh.count - 1)
modified += "-";
}
if (modules.bookmarkcache) {
if (bookmarkcache.isBookmarked(url))
modified += UTF8("❤");
//modified += UTF8("♥");
}
if (modules.quickmarks)
modified += quickmarks.find(url.replace(/#.*/, "")).join("");
}
if (modules.bookmarkcache) {
if (bookmarkcache.isBookmarked(buffer.uri))
modified += UTF8("❤");
//modified += UTF8("♥");
}
if (modules.quickmarks)
modified += quickmarks.find(url.replace(/#.*/, "")).join("");
url = losslessDecodeURI(url);
// make it even more Vim-like
if (url == "about:blank") {
if (!buffer.title)
url = "[No Name]";

View File

@@ -39,11 +39,17 @@ var Tabs = Module("tabs", {
xul|tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#tab) !important; }
]]></>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m),
false, true);
this.timeout(function () {
for (let { linkedBrowser: { contentDocument } } in values(this.allTabs))
if (contentDocument.readyState === "complete")
dactyl.initDocument(contentDocument);
});
},
cleanup: function cleanup() {
for (let [i, tab] in Iterator(this.allTabs)) {
let node = function node(clas) document.getAnonymousElementByAttribute(tab, "class", clas);
let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
for (let elem in values(["dactyl-tab-icon-number", "dactyl-tab-number"].map(node)))
if (elem)
elem.parentNode.parentNode.removeChild(elem.parentNode);
@@ -53,7 +59,7 @@ var Tabs = Module("tabs", {
updateTabCount: function () {
for (let [i, tab] in Iterator(this.visibleTabs)) {
if (dactyl.has("Gecko2")) {
let node = function node(clas) document.getAnonymousElementByAttribute(tab, "class", clas);
let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
if (!node("dactyl-tab-number")) {
let img = node("tab-icon-image");
if (img) {
@@ -880,8 +886,8 @@ var Tabs = Module("tabs", {
tabs.timeout(function () { this.updateTabCount(); });
}
for (let event in values(["TabMove", "TabOpen", "TabClose"]))
events.addSessionListener(tabContainer, event, callback, false);
events.addSessionListener(tabContainer, "TabSelect", tabs.closure._onTabSelect, false);
events.listen(tabContainer, event, callback, false);
events.listen(tabContainer, "TabSelect", tabs.closure._onTabSelect, false);
},
mappings: function () {
mappings.add([modes.NORMAL], ["g0", "g^"],

View File

@@ -41,6 +41,12 @@
interpreted as an Ex command.
</p>
<p>
If the <em>-group</em>=<a>group</a> flag is given, add this autocmd
to the named <t>group</t>. Any filters for <a>group</a> apply in
addition to <oa>pat</oa>.
</p>
<note>
This behavior differs from Vim's implementation in that
<oa>pat</oa> is a regular expression rather than a glob.

View File

@@ -119,6 +119,7 @@
<dt>-count</dt> <dd>Accept a count before the requisite key press. Sets the <tt>count</tt> parameter to the result. (short name <em>-c</em>)</dd>
<dt>-description</dt> <dd>A description of this mapping (short name <em>-d</em>)</dd>
<dt>-ex</dt> <dd>Execute <a>rhs</a> as an Ex command rather than keys (short name <em>-e</em>)</dd>
<dt>-group=<a>group</a></dt> <dd>Add this command to the given <t>group</t> (short name <em>-g</em>)</dd>
<dt>-javascript</dt> <dd>Execute <a>rhs</a> as JavaScript rather than keys (short names <em>-js</em>, <em>-j</em>)</dd>
<dt>-literal=<a>n</a></dt> <dd>Parse the <a>n</a>th argument without specially processing any quote or meta characters. (short name <em>-l</em>)</dd>
<dt>-modes</dt> <dd>Create this mapping in the given modes (short names <em>-mode</em>, <em>-m</em>)</dd>
@@ -198,7 +199,10 @@
<spec>:tm<oa>ap</oa></spec>
<spec>:cm<oa>ap</oa></spec>
<description>
<p>List all mappings for the applicable mode(s).</p>
<p>
List all mappings for the applicable mode(s). Mappings are
partitioned into <t>groups</t>.
</p>
</description>
</item>
@@ -561,9 +565,12 @@
</item>
<item>
<spec>:com<oa>mand</oa> <a>cmd</a></spec>
<spec>:com<oa>mand</oa> <oa>cmd</oa></spec>
<description>
<p>List all user-defined commands that start with <a>cmd</a>.</p>
<p>
List all user-defined commands that start with <oa>cmd</oa>. Commands
are partitioned into <t>groups</t>.
</p>
</description>
</item>
@@ -598,11 +605,18 @@
options when the command is defined.
</p>
<h3 tag=":command-group">Grouping</h3>
<p>
The <em>-group</em> flag (short name: <em>-g</em>) can be used to
assign this command to a specific <t>group</t>.
</p>
<h3 tag="E175 E176 :command-nargs">Argument handling</h3>
<p>
By default, user commands accept no arguments. This can be changed by specifying
the -nargs option.
the <tt>-nargs</tt> option.
</p>
<p>The valid values are:</p>

View File

@@ -133,6 +133,99 @@
</item>
<h2 tag="group groups">Groups</h2>
<p>
In order to facilitate script writing, especially scripts which only
apply to certain web sites, many types of commands and mappings can
be assigned to a named group. In addition to helping identify the
source of such mappings in listings, and aiding in the cleanup of
scripts, these groups can be configured to apply only to certain web
sites.
</p>
<item>
<tags>:gr :group</tags>
<spec>:group<oa>!</oa> <a>group</a></spec>
<description>
<p>List all active <t>groups</t>.</p>
</description>
</item>
<item>
<spec>:group<oa>!</oa> <a>group</a></spec>
<description>
<p>
Select, create, or modify a <t>group</t>. After invocation,
<a>group</a> becomes the default group for all further commands
issued in the current script. If <oa>!</oa> is given the group is
cleared of all mappings, commands, and any other entries bound to
it.
</p>
<p>The following <a>group</a> names have special meanings:</p>
<dl>
<dt>builtin</dt> <dd>The default group for builtin items. Can not be modified in any way by scripts.</dd>
<dt>default</dt> <dd>The default group for this script.</dd>
<dt>user</dt> <dd>The default group for the command line and <t>&dactyl.name;rc</t>.</dd>
</dl>
<p>The following arguments are available:</p>
<dl>
<dt>-args=<a>javascript</a></dt> <dd>JavaScript Object which augments the arguments passed to commands, mappings, and autocommands (short name: <em>-a</em>)</dd>
<dt>-description</dt> <dd>A description of this group (short names: <em>-desc</em>, <em>-d</em>)</dd>
<dt>-locations=<a>filters</a></dt> <dd>The URLs for which this group should be active. See <t>site-filters</t> (short names: <em>-locs</em>, <em>-loc</em>, <em>-l</em>)</dd>
<dt>-nopersist</dt> <dd>Do not save this group to an auto-generated RC file (short name: <em>-n</em>)</dd>
</dl>
</description>
</item>
<h2 tag="site-filter site-filters">Site Filters</h2>
<p>
Many &dactyl.appName; commands accept filters so that they may be applied
only to specific sites. Most of these commands accept filters in any of the
following formats:
</p>
<dl>
<dd>domain</dd>
<dt>
Any filter which is a valid domain name will match any site on that
domain or any sub-domain thereof. These filters may contain any letter
of the Roman alphabet, Arabic numerals, hyphens, and full stops.
Non-Latin domain names must be punycode encoded.
</dt>
<dd>URL prefix</dd>
<dt>
Any URL beginning with a valid protocol name and ending with a
<tt>*</tt> is treated as a URL prefix. It will match any URL which
begins with the given filter sans the trailing asterisk.
</dt>
<dd>Full URL</dd>
<dt>
Any URL beginning with a valid protocol name and not ending with an
asterisk is treated as a full URL match. It will match any page which
has a URL identical to the filter.
</dt>
<dd>Regular expression</dd>
<dt>
Any filter which does not fall into one of the above categories is
treated as a case-sensitive regular expression.
</dt>
</dl>
<p>
In most cases, any of the above may be prefixed with a <tt>!</tt> character
to invert the sense of the match.
</p>
<h2 tag="using-scripts">Using scripts</h2>
<item>
@@ -157,6 +250,32 @@
for more information.
</p>
<h3 tag=":source-contexts">Script Contexts</h3>
<p>
Each script executes in its own JavaScript context. This means that
any global variable or function, including those defined by
<ex>:javascript</ex> and the <tt>-javascript</tt> flag of
<ex>:map</ex>, <ex>:command</ex>, and <ex>:autocmd</ex>,
is directly available only within the current script. Outside of the
current script, they can only be accessed as properties of the
script's global object, which is stored in the <tt>plugins</tt>
global under the script's full path.
</p>
<h3 tag=":source-groups">Script Groups</h3>
<p>
In addition to its own JavaScript context, each script is executed
with its own default <link topic="groups">group</link> into which
its styles, mappings, commands, and autocommands are placed. This
means that commands such as <ex>:comclear!</ex> can be issued
without fear of trampling other user-defined mappings. The command
<ex>:group! default</ex> can be issued to clear all such items at
once, and should be placed at the head of most scripts to prevent
the accumulation of stale items when the script is re-sourced.
</p>
<h3 tag=":source-css">Cascading Stylesheets</h3>
<p>

View File

@@ -95,7 +95,7 @@
Windows only. If this file exists, its contents
are executed and <tt>$MY_&dactyl.idName;RC</tt> set to its path.
</li>
<li>
<li tag="&dactyl.name;rc ">
<em>~/.&dactyl.name;rc</em>
<strut/>
If this file exists, its contents are executed.

View File

@@ -149,10 +149,11 @@
<spec>:sty<oa>le</oa><oa>!</oa> <oa>-name=<a>name</a></oa> <oa>-append</oa> <a>filter</a> <oa>css</oa></spec>
<description>
<p>
Add CSS styles to the browser or to web pages. <a>filter</a> is a comma-separated
list of URLs to match. URLs ending with <em>*</em> are matched as prefixes, URLs not
containing any <em>:</em> or <em>/</em> characters are matched as domains. <oa>css</oa> is a full
CSS rule set (e.g., <tt>body { color: blue; }</tt>).
Add CSS styles to the browser or to web pages. <a>filter</a> is a
comma-separated list of <t>site-filters</t> for which the style will
apply. Regular expression filters may not be used and the <tt>!</tt>
character may not be used to invert the sense of the match.
<oa>css</oa> is a full CSS rule set (e.g., <tt>body { color: blue; }</tt>).
</p>
<p>The following options are available:</p>
@@ -166,6 +167,11 @@
applies to contents user interface widgets as well as normal
elements. (short name <em>-A</em>)</dd>
<dt>-group=<a>group</a></dt>
<dd>The <t>group</t> to which to add this style. Please note that
this grouping is for semantic and cleanup purposes only. No
additional site filtering is applied.</dd>
<dt>-name=<a>name</a></dt>
<dd>If provided, any existing style with the same name is
overridden, and the style may later be deleted using

View File

@@ -437,17 +437,16 @@ var Addons = Module("addons", {
context.completions = types.map(function (t) [t, util.capitalize(t)]);
}
if (AddonManager.getAllAddons)
context.incomplete = true;
context.generate = function generate() {
update(base);
if (AddonManager.getAllAddons)
if (AddonManager.getAllAddons) {
context.incomplete = true;
AddonManager.getAllAddons(function (addons) {
context.incomplete = false;
update(array.uniq(base.concat(addons.map(function (a) a.type)),
true));
});
}
}
}

View File

@@ -61,6 +61,8 @@ if (!Object.defineProperties)
for (let [k, v] in Iterator(props))
Object.defineProperty(obj, k, v);
}
if (!Object.freeze)
Object.freeze = function freeze(obj) {};
if (!Object.getOwnPropertyDescriptor)
Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(obj, prop) {
if (!hasOwnProperty.call(obj, prop))
@@ -101,13 +103,15 @@ if (!Object.keys)
Object.keys = function keys(obj)
Object.getOwnPropertyNames(obj).filter(function (k) objproto.propertyIsEnumerable.call(obj, k));
let getGlobalForObject = Cu.getGlobalForObject || function (obj) obj.__parent__;
let use = {};
let loaded = {};
let currentModule;
let global = this;
function defineModule(name, params, module) {
if (!module)
module = Cu.getGlobalForObject ? Cu.getGlobalForObject(params) : params.__parent__;
module = getGlobalForObject(params);
module.NAME = name;
module.EXPORTED_SYMBOLS = params.exports || [];
@@ -436,25 +440,24 @@ function isinstance(object, interfaces) {
if (object == null)
return false;
interfaces = Array.concat(interfaces);
for (var i = 0; i < interfaces.length; i++) {
if (typeof interfaces[i] === "string") {
if (objproto.toString.call(object) === "[object " + interfaces[i] + "]")
return Array.concat(interfaces).some(function isinstance_some(iface) {
if (typeof iface === "string") {
if (objproto.toString.call(object) === "[object " + iface + "]")
return true;
}
else if (typeof object === "object" && "isinstance" in object && object.isinstance !== isinstance) {
if (object.isinstance(interfaces[i]))
if (object.isinstance(iface))
return true;
}
else {
if (object instanceof interfaces[i])
if (object instanceof iface)
return true;
var type = isinstance_types[typeof object];
if (type && isSubclass(interfaces[i], type))
if (type && isSubclass(iface, type))
return true;
}
}
return false;
return false;
});
}
/**
@@ -978,7 +981,8 @@ let StructBase = Class("StructBase", Array, {
}
}, {
fromArray: function (ary) {
ary.__proto__ = this.prototype;
if (!(ary instanceof this))
ary.__proto__ = this.prototype;
return ary;
},
@@ -1013,7 +1017,7 @@ var Timer = Class("Timer", {
notify: function (timer, force) {
try {
if (loaded.util && util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0)
if (!loaded || loaded.util && util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0)
return;
this._timer.cancel();

File diff suppressed because it is too large Load Diff

View File

@@ -42,9 +42,11 @@ var CompletionContext = Class("CompletionContext", {
let parent = editor;
name = parent.name + "/" + name;
this.autoComplete = this.options.get("autocomplete").getKey(name);
this.sortResults = this.options.get("wildsort").getKey(name);
this.wildcase = this.options.get("wildcase").getKey(name);
if (this.options) {
this.autoComplete = this.options.get("autocomplete").getKey(name);
this.sortResults = this.options.get("wildsort").getKey(name);
this.wildcase = this.options.get("wildcase").getKey(name);
}
this.contexts = parent.contexts;
if (name in this.contexts)
@@ -448,7 +450,7 @@ var CompletionContext = Class("CompletionContext", {
let self = this;
delete this._substrings;
if (!this.forceAnchored)
if (!this.forceAnchored && this.options)
this.anchored = this.options.get("wildanchor").getKey(this.name, this.anchored);
// Item matchers

View File

@@ -31,7 +31,7 @@ var ConfigBase = Class("ConfigBase", {
iter(config.dtdExtra,
(["dactyl." + k, v] for ([k, v] in iter(config.dtd))),
(["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
.map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v, /'/g, "&apos;"), "'>"].join(""))
.map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v || "null", /'/g, "&apos;"), "'>"].join(""))
.join("\n")]
});
},
@@ -115,6 +115,23 @@ var ConfigBase = Class("ConfigBase", {
.nth(function (l) set.has(langs, l), 0);
},
haveHg: Class.memoize(function () {
if (/pre$/.test(this.addon.version)) {
let uri = this.addon.getResourceURI("../.hg");
if (uri instanceof Ci.nsIFileURL &&
uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
io.pathSearch("hg"))
return ["hg", "-R", uri.file.parent.path];
}
return null;
}),
branch: Class.memoize(function () {
if (this.haveHg)
return io.system(this.haveHg.concat(["branch"])).output;
return (/pre-hg\d+-(.*)$/.exec(this.version) || [])[1];
}),
/** @property {string} The Dactyl version string. */
version: Class.memoize(function () {
if (/pre$/.test(this.addon.version)) {
@@ -124,11 +141,11 @@ var ConfigBase = Class("ConfigBase", {
io.pathSearch("hg")) {
return io.system(["hg", "-R", uri.file.parent.path,
"log", "-r.",
"--template=hg{rev} ({date|isodate})"]).output;
"--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output;
}
}
let version = this.addon.version;
if ("@DATE" !== "@" + "DATE@")
if ("@DATE@" !== "@" + "DATE@")
version += " (created: @DATE@)";
return version;
}),
@@ -723,6 +740,15 @@ config.INIT = update(Object.create(config.INIT), config.INIT, {
{"}"}</>);
img = null;
};
},
load: function load(dactyl, modules, window) {
load.superapply(this, arguments);
if (this.branch && this.branch !== "default" &&
modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1)
dactyl.warn("You are running " + config.appName + " from a testing branch: " + this.branch + ". " +
"Please do not report errors which do not also occur in the default branch.");
}
});

620
common/modules/contexts.jsm Normal file
View File

@@ -0,0 +1,620 @@
// Copyright (c) 2010-2011 by Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("contexts", {
exports: ["Contexts", "Group", "contexts"],
use: ["commands", "options", "services", "storage", "styles", "template", "util"]
}, this);
var Group = Class("Group", {
init: function init(name, description, filter, persist) {
const self = this;
this.name = name;
this.description = description;
this.filter = filter || this.constructor.defaultFilter;
this.persist = persist || false;
this.hives = [];
},
modifiable: true,
cleanup: function cleanup() {
for (let hive in values(this.hives))
util.trapErrors("cleanup", hive);
this.hives = [];
for (let hive in keys(this.hiveMap))
delete this[hive];
},
destroy: function destroy() {
for (let hive in values(this.hives))
util.trapErrors("destroy", hive);
},
argsExtra: function argsExtra() ({}),
get toStringParams() [this.name],
get builtin() this.modules.contexts.builtinGroups.indexOf(this) >= 0,
}, {
compileFilter: function (patterns) {
function siteFilter(uri) siteFilter.filters.every(function (f) f(uri) == f.result);
if (!isArray(patterns))
patterns = Option.splitList(patterns, true);
return update(siteFilter, {
toString: function () this.filters.join(","),
toXML: function (modules) let (uri = modules && modules.buffer.uri)
template.map(this.filters,
function (f) <span highlight={uri && f(uri) ? "Filter" : ""}>{f}</span>,
<>,</>),
filters: patterns.map(function (pattern) {
let [, res, filter] = /^(!?)(.*)/.exec(pattern);
return update(Styles.matchFilter(Option.dequote(filter)), {
result: !res,
toString: function () pattern
});
})
});
},
defaultFilter: Class.memoize(function () this.compileFilter(["*"]))
});
var Contexts = Module("contexts", {
Local: function Local(dactyl, modules, window) ({
init: function () {
const contexts = this;
this.modules = modules;
modules.plugins.contexts = {};
this.groupList = [];
this.groupMap = {};
this.groupsProto = {};
this.hives = {};
this.hiveProto = {};
this.builtin = this.addGroup("builtin", "Builtin items");
this.user = this.addGroup("user", "User-defined items", null, true);
this.builtinGroups = [this.builtin, this.user];
this.builtin.modifiable = false;
this.GroupFlag = Class("GroupFlag", CommandOption, {
init: function (name) {
this.name = name;
this.type = ArgType("group", function (group) {
return isString(group) ? contexts.getGroup(group, name)
: group[name];
});
},
get toStringParams() [this.name],
names: ["-group", "-g"],
description: "Group to which to add",
get default() (contexts.context && contexts.context.group || contexts.user)[this.name],
completer: function (context) modules.completion.group(context)
});
},
cleanup: function () {
for (let hive in values(this.groupList))
util.trapErrors("cleanup", hive);
},
destroy: function () {
for (let hive in values(this.groupList))
util.trapErrors("destroy", hive);
for (let [name, plugin] in iter(this.modules.plugins.contexts))
if (plugin && "onUnload" in plugin)
util.trapErrors("onUnload", plugin);
},
Group: Class("Group", Group, { modules: modules, get hiveMap() modules.contexts.hives }),
Hives: Class("Hives", Class.Property, {
init: function init(name, constructor) {
const { contexts } = modules;
const self = this;
if (this.Hive)
return {
enumerable: true,
get: function () array(contexts.groups[self.name])
};
this.Hive = constructor;
this.name = name;
memoize(contexts.Group.prototype, name, function () {
let group = constructor(this);
this.hives.push(group);
delete contexts.groups;
return group;
});
memoize(contexts.hives, name,
function () Object.create(Object.create(contexts.hiveProto,
{ _hive: { value: name } })));
memoize(contexts.groupsProto, name,
function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]);
},
get toStringParams() [this.name, this.Hive]
})
}),
Context: function Context(file, group, args) {
const { contexts, io, newContext, plugins, userContext } = this.modules;
function Const(val) Class.Property({ enumerable: true, value: val });
let isPlugin = array.nth(io.getRuntimeDirectories("plugins"),
function (dir) dir.contains(file, true),
0);
let isRuntime = array.nth(io.getRuntimeDirectories(""),
function (dir) dir.contains(file, true),
0);
let self = set.has(plugins, file.path) && plugins[file.path];
if (self) {
if (set.has(self, "onUnload"))
self.onUnload();
}
else {
let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
: file.leafName;
self = update(newContext.apply(null, args || [userContext]), {
NAME: Const(name.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())),
PATH: Const(file.path),
CONTEXT: Const(self),
unload: Const(function unload() {
if (plugins[this.NAME] === this || plugins[this.PATH] === this)
if (this.onUnload)
this.onUnload();
if (plugins[this.NAME] === this)
delete plugins[this.NAME];
if (plugins[this.PATH] === this)
delete plugins[this.PATH];
if (!this.GROUP.builtin)
contexts.removeGroup(this.GROUP);
})
});
Class.replaceProperty(plugins, file.path, self);
// This belongs elsewhere
if (isPlugin && args)
Object.defineProperty(plugins, self.NAME, {
configurable: true,
enumerable: true,
value: self
});
}
let path = isRuntime ? file.getRelativeDescriptor(isRuntime) : file.path;
let name = isRuntime ? path.replace(/^(plugin|color)s([\\\/])/, "$1$2") : "script-" + path;
if (!group)
group = this.addGroup(commands.nameRegexp
.iterate(name.replace(/\.[^.]*$/, ""))
.join("-"),
"Script group for " + file.path,
null, false);
Class.replaceProperty(self, "GROUP", group);
Class.replaceProperty(self, "group", group);
return plugins.contexts[file.path] = self;
},
Script: function Script(file, group) {
return this.Context(file, group, [this.modules.plugins, true]);
},
context: null,
/**
* Returns a frame object describing the currently executing
* command, if applicable, otherwise returns the passed frame.
*
* @param {nsIStackFrame} frame
*/
getCaller: function getCaller(frame) {
if (this.context && this.context.file)
return {
__proto__: frame,
filename: this.context.file[0] == "[" ? this.context.file
: services.io.newFileURI(File(this.context.file)).spec,
lineNumber: this.context.line
};
return frame;
},
groups: Class.memoize(function () Object.create(this.groupsProto, {
groups: { value: this.activeGroups() },
})),
allGroups: Class.memoize(function () Object.create(this.groupsProto, {
groups: { value: this.initializedGroups() }
})),
activeGroups: function (hive) this.initializedGroups().filter(function (g) g.filter(this), this.modules.buffer.uri),
initializedGroups: function (hive)
let (need = hive ? [hive] : Object.keys(this.hives))
this.groupList.filter(function (group) need.some(function (name) set.has(group, name))),
addGroup: function addGroup(name, description, filter, persist, replace) {
let group = this.getGroup(name);
if (group)
name = group.name;
if (!group) {
group = this.Group(name, description, filter, persist);
this.groupList.unshift(group);
this.groupMap[name] = group;
this.hiveProto.__defineGetter__(name, function () group[this._hive]);
}
if (replace) {
util.trapErrors("cleanup", group);
if (description)
group.description = description;
if (filter)
group.filter = filter
group.persist = persist;
}
delete this.groups;
return group;
},
removeGroup: function removeGroup(name, filter) {
if (isObject(name)) {
if (this.groupList.indexOf(name) === -1)
return;
name = name.name;
}
let group = this.getGroup(name);
util.assert(!group || !group.builtin, "Cannot remove builtin group");
if (group) {
name = group.name;
this.groupList.splice(this.groupList.indexOf(group), 1);
util.trapErrors("destroy", group);
}
if (this.context && this.context.group === group)
this.context.group = null;
delete this.groupMap[name];
delete this.hiveProto[name];
delete this.groups;
return group;
},
getGroup: function getGroup(name, hive) {
if (name === "default")
var group = this.context && this.context.context && this.context.context.GROUP;
else if (set.has(this.groupMap, name))
group = this.groupMap[name];
if (group && hive)
return group[hive];
return group;
},
bindMacro: function (args, default_, params) {
const { dactyl, events } = this.modules;
let process = util.identity;
if (callable(params))
var makeParams = function makeParams(self, args)
iter.toObject([k, process(v)]
for ([k, v] in iter(params.apply(self, args))));
else if (params)
makeParams = function makeParams(self, args)
iter.toObject([name, process(args[i])]
for ([i, name] in Iterator(params)));
let rhs = args.literalArg;
let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_);
switch (type) {
case "-builtin":
let noremap = true;
/* fallthrough */
case "-keys":
let silent = args["-silent"];
rhs = events.canonicalKeys(rhs, true);
var action = function action() events.feedkeys(action.macro(makeParams(this, arguments)),
noremap, silent);
action.macro = util.compileMacro(rhs, true);
break;
case "-ex":
action = function action() this.modules.commands
.execute(action.macro, makeParams(this, arguments),
false, null, action.context);
action.macro = util.compileMacro(rhs, true);
action.context = this.context && update({}, this.context);
break;
case "-javascript":
if (callable(params))
action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })");
else
action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg).array);
process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param;
action.makeParams = makeParams;
break;
}
action.toString = function toString() (type === default_ ? "" : type + " ") + rhs;
args = null;
return action;
},
withContext: function withContext(defaults, callback, self)
this.withSavedValues(["context"], function () {
this.context = defaults && update({}, defaults);
return callback.call(self, this.context);
})
}, {
Hive: Class("Hive", {
init: function init(group) {
this.group = group;
},
cleanup: function cleanup() {},
destroy: function destroy() {},
get modifiable() this.group.modifiable,
get argsExtra() this.group.argsExtra,
get builtin() this.group.builtin,
get name() this.group.name,
set name(val) this.group.name = val,
get description() this.group.description,
set description(val) this.group.description = val,
get filter() this.group.filter,
set filter(val) this.group.filter = val,
get persist() this.group.persist,
set persist(val) this.group.persist = val,
get toStringParams() [this.name]
})
}, {
commands: function initCommands(dactyl, modules, window) {
const { commands, contexts } = modules;
commands.add(["gr[oup]"],
"Create or select a group",
function (args) {
if (args.length > 0) {
var name = Option.dequote(args[0]);
util.assert(name !== "builtin", "Cannot modify builtin group");
util.assert(commands.validName.test(name), "Invalid group name");
var group = contexts.getGroup(name);
}
else if (args.bang)
var group = args.context && args.context.group;
else
return void modules.completion.listCompleter("group", "", null, null);
util.assert(group || name, "No current group");
let filter = Group.compileFilter(args["-locations"]);
if (!group || args.bang)
group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"], args.bang);
else if (!group.builtin) {
if (args.has("-locations"))
group.filter = filter;
if (args.has("-description"))
group.description = args["-description"]
if (args.has("-nopersist"))
group.persist = !args["-nopersist"]
}
if (!group.builtin && args.has("-args")) {
group.argsExtra = contexts.bindMacro({ literalArg: "return " + args["-args"] },
"-javascript", util.identity);
group.args = args["-args"];
}
if (args.context)
args.context.group = group;
},
{
argCount: "?",
bang: true,
completer: function (context, args) {
if (args.length == 1)
modules.completion.group(context);
},
keepQuotes: true,
options: [
{
names: ["-args", "-a"],
description: "JavaScript Object which augments the arguments passed to commands, mappings, and autocommands",
type: CommandOption.STRING
},
{
names: ["-description", "-desc", "-d"],
description: "A description of this group",
default: ["User-defined group"],
type: CommandOption.STRING
},
{
names: ["-locations", "-locs", "-loc", "-l"],
description: ["The URLs for which this group should be active"],
default: ["*"],
type: CommandOption.LIST
},
{
names: ["-nopersist", "-n"],
description: "Do not save this group to an auto-generated RC file"
}
],
serialGroup: 20,
serialize: function () [
{
command: this.name,
bang: true,
options: iter([v, typeof group[k] == "boolean" ? null : group[k]]
// FIXME: this map is expressed multiple times
for ([k, v] in Iterator({
args: "-args",
description: "-description",
filter: "-locations"
}))
if (group[k])).toObject(),
arguments: [group.name],
ignoreDefaults: true
}
for (group in values(contexts.initializedGroups()))
if (!group.builtin && group.persist)
].concat([{ command: this.name, arguments: ["user"] }])
});
commands.add(["delg[roup]"],
"Delete a group",
function (args) {
util.assert(contexts.getGroup(args[0]), "No such group: " + args[0]);
contexts.removeGroup(args[0]);
},
{
argCount: "1",
completer: function (context, args) {
modules.completion.group(context);
context.filters.push(function ({ item }) !item.builtin);
}
});
commands.add(["fini[sh]"],
"Stop sourcing a script file",
function (args) {
util.assert(args.context, "E168: :finish used outside of a sourced file");
args.context.finished = true;
},
{ argCount: "0" });
function checkStack(cmd) {
util.assert(contexts.context && contexts.context.stack &&
contexts.context.stack[cmd] && contexts.context.stack[cmd].length,
"Invalid use of conditional");
}
function pop(cmd) {
checkStack(cmd);
return contexts.context.stack[cmd].pop();
}
function push(cmd, value) {
util.assert(contexts.context, "Invalid use of conditional");
if (arguments.length < 2)
value = contexts.context.noExecute;
contexts.context.stack = contexts.context.stack || {};
contexts.context.stack[cmd] = (contexts.context.stack[cmd] || []).concat([value]);
}
commands.add(["if"],
"Execute commands until the next :elseif, :else, or :endif only if the argument returns true",
function (args) { args.context.noExecute = !dactyl.userEval(args[0]); },
{
always: function (args) { push("if"); },
argCount: "1",
literal: 0
});
commands.add(["elsei[f]", "elif"],
"Execute commands until the next :elseif, :else, or :endif only if the argument returns true",
function (args) {},
{
always: function (args) {
checkStack("if");
args.context.noExecute = args.context.stack.if.slice(-1)[0] ||
!args.context.noExecute || !dactyl.userEval(args[0]);
},
argCount: "1",
literal: 0
});
commands.add(["el[se]"],
"Execute commands until the next :endif only if the previous conditionals were not executed",
function (args) {},
{
always: function (args) {
checkStack("if");
args.context.noExecute = args.context.stack.if.slice(-1)[0] ||
!args.context.noExecute;
},
argCount: "0"
});
commands.add(["en[dif]", "fi"],
"End a string of :if/:elseif/:else conditionals",
function (args) {},
{
always: function (args) { args.context.noExecute = pop("if"); },
argCount: "0"
});
},
completion: function initCompletion(dactyl, modules, window) {
const { completion, contexts } = modules;
completion.group = function group(context, active) {
context.title = ["Group"];
let uri = modules.buffer.uri;
context.keys = {
active: function (group) group.filter(uri),
text: "name",
description: function (g) <>{g.filter.toXML ? g.filter.toXML(modules) + <>&#xa0;</> : ""}{g.description || ""}</>
};
context.completions = (active === undefined ? contexts.groupList : contexts.initializedGroups(active))
.slice(0, -1);
iter({ Active: true, Inactive: false }).forEach(function ([name, active]) {
context.split(name, null, function (context) {
context.title[0] = name + " Groups";
context.filters.push(function (item) item.active == active);
});
});
};
}
});
endModule();
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:

View File

@@ -106,9 +106,13 @@ var Download = Class("Download", {
let file = io.File(this.targetFile);
if (file.isExecutable() && prefs.get("browser.download.manager.alertOnEXEOpen", true))
this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]) ",
this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]/always) ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i))
if (/^a(lways)$/i.test(resp)) {
prefs.set("browser.download.manager.alertOnEXEOpen", false);
resp = "yes";
}
if (/^y(es)?$/i.test(resp))
action.call(self);
});
else
@@ -144,10 +148,7 @@ var Download = Class("Download", {
updateStatus: function updateStatus() {
if (this.alive)
this.nodes.row.setAttribute("active", "true");
else
this.nodes.row.removeAttribute("active");
this.nodes.row[this.alive ? "setAttribute" : "removeAttribute"]("active", "true");
this.nodes.row.setAttribute("status", this.status);
this.nodes.state.textContent = util.capitalize(this.status);

View File

@@ -196,7 +196,7 @@ var RangeFinder = Module("rangefinder", {
get prompt() this.mode === modules.modes.FIND_BACKWARD ? "?" : "/",
get onCancel() modules.rangefinder.closure.onCancel,
get onCancel() rangefinder.closure.onCancel,
get onChange() modules.rangefinder.closure.onChange,
get onSubmit() modules.rangefinder.closure.onSubmit
});

View File

@@ -23,8 +23,7 @@ Highlight.liveProperty = function (name, prop) {
val = Array.slice(val);
else
val = update({}, val);
if (Object.freeze)
Object.freeze(val);
Object.freeze(val);
}
this.set(name, val);
@@ -82,7 +81,7 @@ update(Highlight.prototype, {
get cssText() this.inheritedCSS + this.value,
toString: function () "Highlight(" + this.class + ")\n\t" +
[k + ": " + String.quote(v) for ([k, v] in this)] .join("\n\t")
[k + ": " + String(v).quote() for ([k, v] in this)] .join("\n\t")
});
/**
@@ -291,16 +290,21 @@ var Highlights = Module("Highlight", {
}, {
commands: function (dactyl, modules) {
const { autocommands, commands, completion, CommandOption, config, io } = modules;
let lastScheme;
commands.add(["colo[rscheme]"],
"Load a color scheme",
function (args) {
let scheme = args[0];
if (lastScheme)
lastScheme.unload();
if (scheme == "default")
highlight.clear();
else
dactyl.assert(modules.io.sourceFromRuntimePath(["colors/" + scheme + "." + config.fileExtension]),
"E185: Cannot find color scheme " + scheme);
else {
lastScheme = modules.io.sourceFromRuntimePath(["colors/" + scheme + "." + config.fileExtension]);
dactyl.assert(lastScheme, "E185: Cannot find color scheme " + scheme);
}
autocommands.trigger("ColorScheme", { name: scheme });
},
{

View File

@@ -29,7 +29,7 @@ var IO = Module("io", {
this.config = config;
},
Local: function (dactyl, modules, window) let ({ Script, io, plugins } = modules) ({
Local: function (dactyl, modules, window) let ({ io, plugins } = modules) ({
init: function init() {
this.config = modules.config;
@@ -84,9 +84,6 @@ var IO = Module("io", {
destroy: function destroy() {
services.downloadManager.removeListener(this.downloadListener);
for (let [, plugin] in Iterator(plugins.contexts))
if (plugin.onUnload)
plugin.onUnload();
},
/**
@@ -113,7 +110,7 @@ var IO = Module("io", {
*/
sourceFromRuntimePath: function sourceFromRuntimePath(paths, all) {
let dirs = modules.options.get("runtimepath").files;
let found = false;
let found = null;
dactyl.echomsg("Searching for " + paths.join(" ").quote() + " in " + modules.options.get("runtimepath").stringValue, 2);
@@ -125,8 +122,7 @@ var IO = Module("io", {
dactyl.echomsg("Searching for " + file.path.quote(), 3);
if (file.exists() && file.isFile() && file.isReadable()) {
io.source(file.path, false);
found = true;
found = io.source(file.path, false) || true;
if (!all)
break outer;
@@ -144,18 +140,22 @@ var IO = Module("io", {
* Reads Ex commands, JavaScript or CSS from *filename*.
*
* @param {string} filename The name of the file to source.
* @param {boolean} silent Whether errors should be reported.
* @param {object} params Extra parameters:
* group: The group in which to execute commands.
* silent: Whether errors should not be reported.
*/
source: function source(filename, silent) {
source: function source(filename, params) {
const { contexts } = modules;
defineModule.loadLog.push("sourcing " + filename);
params = params || {};
let time = Date.now();
this.withSavedValues(["sourcing"], function _source() {
this.sourcing = null;
return contexts.withContext(null, function () {
try {
var file = util.getFile(filename) || io.File(filename);
if (!file.exists() || !file.isReadable() || file.isDirectory()) {
if (!silent)
if (!params.silent)
dactyl.echoerr("E484: Can't open file " + filename.quote());
return;
}
@@ -167,7 +167,8 @@ var IO = Module("io", {
// handle pure JavaScript files specially
if (/\.js$/.test(filename)) {
try {
dactyl.loadScript(uri.spec, Script(file));
var context = contexts.Script(file, params.group);
dactyl.loadScript(uri.spec, context);
dactyl.helpInitialized = false;
}
catch (e) {
@@ -185,10 +186,14 @@ var IO = Module("io", {
else if (/\.css$/.test(filename))
styles.registerSheet(uri.spec, false, true);
else {
if (!(file.path in plugins))
plugins[file.path] = modules.newContext(modules.userContext);
modules.commands.execute(file.read(), null, silent || "loud", null,
{ file: file.path, line: 1, context: plugins[file.path] });
context = contexts.Context(file, params.group);
modules.commands.execute(file.read(), null, params.silent,
null, {
context: context,
file: file.path,
group: context.GROUP,
line: 1
});
}
if (this._scriptNames.indexOf(file.path) == -1)
@@ -197,18 +202,19 @@ var IO = Module("io", {
dactyl.echomsg("finished sourcing " + filename.quote(), 2);
dactyl.log("Sourced: " + filename, 3);
return context;
}
catch (e) {
if (!(e instanceof FailedAssertion))
dactyl.reportError(e);
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
if (!silent)
if (!params.silent)
dactyl.echoerr(message);
}
finally {
defineModule.loadLog.push("done sourcing " + filename + ": " + (Date.now() - time) + "ms");
}
});
}, this);
},
}),
@@ -547,35 +553,6 @@ var IO = Module("io", {
*/
PATH_SEP: deprecated("File.PATH_SEP", { get: function PATH_SEP() File.PATH_SEP })
}, {
init: function init(dactyl, modules, window) {
modules.plugins.contexts = {};
modules.Script = function Script(file) {
const { io, plugins } = modules;
let self = set.has(plugins, file.path) && plugins[file.path];
if (self) {
if (set.has(self, "onUnload"))
self.onUnload();
}
else {
self = update(modules.newContext(plugins, true), {
NAME: file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()),
PATH: file.path,
CONTEXT: self
});
Class.replaceProperty(plugins, file.path, self);
// This belongs elsewhere
if (io.getRuntimeDirectories("plugins").some(
function (dir) dir.contains(file, false)))
Class.replaceProperty(plugins, self.NAME, self);
}
plugins.contexts[file.path] = self;
return self;
}
init.superapply(this, arguments);
},
commands: function (dactyl, modules, window) {
const { commands, completion, io } = modules;
@@ -617,15 +594,6 @@ var IO = Module("io", {
literal: 0
});
// NOTE: this command is only used in :source
commands.add(["fini[sh]"],
"Stop sourcing a script file",
function () {
dactyl.assert(io.sourcing, "E168: :finish used outside of a sourced file");
io.sourcing.finished = true;
},
{ argCount: "0" });
commands.add(["pw[d]"],
"Print the current directory name",
function () { dactyl.echomsg(io.cwd.path); },
@@ -642,7 +610,7 @@ var IO = Module("io", {
"E189: " + file.path.quote() + " exists (add ! to override)");
// TODO: Use a set/specifiable list here:
let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)];
let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)];
lines = array.flatten(lines);
lines.unshift('"' + config.version + "\n");
@@ -780,16 +748,17 @@ unlet s:cpo_save
return lines.map(function (l) l.join("")).join("\n").replace(/\s+\n/gm, "\n");
}
const { commands, options } = modules;
file.write(template({
name: config.name,
autocommands: wrap("syn keyword " + config.name + "AutoEvent ",
keys(config.autocommands)),
commands: wrap("syn keyword " + config.name + "Command ",
array(c.specs for (c in commands)).flatten()),
array(c.specs for (c in commands.iterator())).flatten()),
options: wrap("syn keyword " + config.name + "Option ",
array(o.names for (o in modules.options) if (o.type != "boolean")).flatten()),
array(o.names for (o in options) if (o.type != "boolean")).flatten()),
toggleoptions: wrap("let s:toggleOptions = [",
array(o.realNames for (o in modules.options) if (o.type == "boolean"))
array(o.realNames for (o in options) if (o.type == "boolean"))
.flatten().map(String.quote),
", ") + "]"
}));

View File

@@ -6,6 +6,16 @@
// given in the LICENSE.txt file included with this file.
"use strict";
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("options", {
exports: ["Option", "Options", "ValueError", "options"],
require: ["storage"],
use: ["commands", "completion", "prefs", "services", "template", "util"]
}, this);
/** @scope modules */
let ValueError = Class("ValueError", ErrorBase);
@@ -62,6 +72,13 @@ var Option = Class("Option", {
if (arguments.length > 3) {
if (this.type == "string")
defaultValue = Commands.quote(defaultValue);
if (isObject(defaultValue))
defaultValue = iter(defaultValue).map(function (val) val.map(Option.quote).join(":")).join(",");
if (isArray(defaultValue))
defaultValue = defaultValue.map(Option.quote).join(",");
this.defaultValue = this.parse(defaultValue);
}
@@ -76,7 +93,7 @@ var Option = Class("Option", {
get helpTag() "'" + this.name + "'",
initValue: function () {
dactyl.trapErrors(function () this.value = this.value, this);
util.trapErrors(function () this.value = this.value, this);
},
get isDefault() this.stringValue === this.stringDefaultValue,
@@ -120,13 +137,15 @@ var Option = Class("Option", {
let values;
if (dactyl.has("tabs") && (scope & Option.SCOPE_LOCAL))
/*
if (config.has("tabs") && (scope & Option.SCOPE_LOCAL))
values = tabs.options[this.name];
*/
if ((scope & Option.SCOPE_GLOBAL) && (values == undefined))
values = this.globalValue;
if (this.getter)
return dactyl.trapErrors(this.getter, this, values);
return util.trapErrors(this.getter, this, values);
return values;
},
@@ -144,19 +163,21 @@ var Option = Class("Option", {
return;
if (this.setter)
newValues = dactyl.trapErrors(this.setter, this, newValues);
newValues = this.modules.dactyl.trapErrors(this.setter, this, newValues);
if (newValues === undefined)
return;
if (dactyl.has("tabs") && (scope & Option.SCOPE_LOCAL))
/*
if (config.has("tabs") && (scope & Option.SCOPE_LOCAL))
tabs.options[this.name] = newValues;
*/
if ((scope & Option.SCOPE_GLOBAL) && !skipGlobal)
this.globalValue = newValues;
this.hasChanged = true;
this.setFrom = null;
dactyl.triggerObserver("options." + this.name, newValues);
// dactyl.triggerObserver("options." + this.name, newValues);
},
getValues: deprecated("Option#get", "get"),
@@ -232,7 +253,7 @@ var Option = Class("Option", {
}
catch (e) {
if (!(e instanceof ValueError))
dactyl.reportError(e);
util.reportError(e);
return this.invalidArgument(str || this.stringify(values), operator) + ": " + e.message;
}
@@ -473,6 +494,7 @@ var Option = Class("Option", {
Option._splitAt = 0;
return arg;
},
splitList: function (value, keepQuotes) {
let res = [];
Option._splitAt = 0;
@@ -488,6 +510,7 @@ var Option = Class("Option", {
}
return res;
},
quote: function quote(str, re)
Commands.quoteArg[/[\s|"'\\,]|^$/.test(str) || re && re.test && re.test(str)
? (/[\b\f\n\r\t]/.test(str) ? '"' : "'")
@@ -507,8 +530,8 @@ var Option = Class("Option", {
values = values[(values.indexOf(String(this.value)) + 1) % values.length];
let value = parseInt(values);
dactyl.assert(Number(values) % 1 == 0,
"E521: Number required after =: " + this.name + "=" + values);
util.assert(Number(values) % 1 == 0,
"E521: Number required after =: " + this.name + "=" + values);
switch (operator) {
case "+":
@@ -620,7 +643,7 @@ var Option = Class("Option", {
},
validateXPath: function (values) {
let evaluator = XPathEvaluator();
let evaluator = services.XPathEvaluator();
return this.testValues(values,
function (value) evaluator.createExpression(value, util.evaluateXPath.resolver));
}
@@ -630,73 +653,124 @@ var Option = Class("Option", {
* @instance options
*/
var Options = Module("options", {
init: function () {
this.needInit = [];
this._options = [];
this._optionMap = {};
Local: function (dactyl, modules, window) let ({ contexts } = modules) ({
init: function init() {
const self = this;
this.needInit = [];
this._options = [];
this._optionMap = {};
this.Option = Class("Option", Option, { modules: modules });
storage.newMap("options", { store: false });
storage.addObserver("options", function optionObserver(key, event, option) {
// Trigger any setters.
let opt = options.get(option);
if (event == "change" && opt)
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
}, window);
},
storage.newMap("options", { store: false });
storage.addObserver("options", function optionObserver(key, event, option) {
// Trigger any setters.
let opt = self.get(option);
if (event == "change" && opt)
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
}, window);
},
cleanup: function cleanup() {
for (let opt in this)
if (opt.cleanupValue != null)
opt.value = opt.parse(opt.cleanupValue);
},
dactyl: dactyl,
/**
* Lists all options in *scope* or only those with changed values if
* *onlyNonDefault* is specified.
*
* @param {function(Option)} filter Limit the list
* @param {number} scope Only list options in this scope (see
* {@link Option#scope}).
*/
list: function (filter, scope) {
if (!scope)
scope = Option.SCOPE_BOTH;
function opts(opt) {
for (let opt in Iterator(options)) {
let option = {
__proto__: opt,
isDefault: opt.isDefault,
default: opt.stringDefaultValue,
pre: "\u00a0\u00a0", // Unicode nonbreaking space.
value: <></>
};
if (filter && !filter(opt))
continue;
if (!(opt.scope & scope))
continue;
if (opt.type == "boolean") {
if (!opt.value)
option.pre = "no";
option.default = (opt.defaultValue ? "" : "no") + opt.name;
}
else if (isArray(opt.value))
option.value = <>={template.map(opt.value, function (v) template.highlight(String(v)), <>,<span style="width: 0; display: inline-block"> </span></>)}</>;
else
option.value = <>={template.highlight(opt.stringValue)}</>;
yield option;
}
};
modules.commandline.commandOutput(template.options("Options", opts(), options["verbose"] > 0));
},
cleanup: function cleanup() {
for (let opt in this)
if (opt.cleanupValue != null)
opt.value = opt.parse(opt.cleanupValue);
},
/**
* Adds a new option.
*
* @param {string[]} names All names for the option.
* @param {string} description A description of the option.
* @param {string} type The option type (see {@link Option#type}).
* @param {value} defaultValue The option's default value.
* @param {Object} extra An optional extra configuration hash (see
* {@link Map#extraInfo}).
* @optional
*/
add: function (names, description, type, defaultValue, extraInfo) {
const self = this;
if (!extraInfo)
extraInfo = {};
extraInfo.definedAt = contexts.getCaller(Components.stack.caller);
let name = names[0];
if (name in this._optionMap) {
this.dactyl.log("Warning: " + name.quote() + " already exists: replacing existing option.", 1);
this.remove(name);
}
let closure = function () self._optionMap[name];
memoize(this._optionMap, name, function () self.Option(names, description, type, defaultValue, extraInfo));
for (let alias in values(names.slice(1)))
memoize(this._optionMap, alias, closure);
if (extraInfo.setter && (!extraInfo.scope || extraInfo.scope & Option.SCOPE_GLOBAL))
if (this.dactyl.initialized)
closure().initValue();
else
memoize(this.needInit, this.needInit.length, closure);
this._floptions = (this._floptions || []).concat(name);
memoize(this._options, this._options.length, closure);
// quickly access options with options["wildmode"]:
this.__defineGetter__(name, function () this._optionMap[name].value);
this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; });
}
}),
/** @property {Iterator(Option)} @private */
__iterator__: function ()
values(this._options.sort(function (a, b) String.localeCompare(a.name, b.name))),
/**
* Adds a new option.
*
* @param {string[]} names All names for the option.
* @param {string} description A description of the option.
* @param {string} type The option type (see {@link Option#type}).
* @param {value} defaultValue The option's default value.
* @param {Object} extra An optional extra configuration hash (see
* {@link Map#extraInfo}).
* @optional
*/
add: function (names, description, type, defaultValue, extraInfo) {
if (!extraInfo)
extraInfo = {};
extraInfo.definedAt = Commands.getCaller(Components.stack.caller);
let name = names[0];
if (name in this._optionMap) {
dactyl.log("Warning: " + name.quote() + " already exists: replacing existing option.", 1);
this.remove(name);
}
let closure = function () options._optionMap[name];
memoize(this._optionMap, name, function () Option(names, description, type, defaultValue, extraInfo));
for (let alias in values(names.slice(1)))
memoize(this._optionMap, alias, closure);
if (extraInfo.setter && (!extraInfo.scope || extraInfo.scope & Option.SCOPE_GLOBAL))
if (dactyl.initialized)
closure().initValue();
else
memoize(this.needInit, this.needInit.length, closure);
this._floptions = (this._floptions || []).concat(name);
memoize(this._options, this._options.length, closure);
// quickly access options with options["wildmode"]:
this.__defineGetter__(name, function () this._optionMap[name].value);
this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; });
},
allPrefs: deprecated("prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)),
getPref: deprecated("prefs.get", function getPref() prefs.get.apply(prefs, arguments)),
invertPref: deprecated("prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)),
@@ -727,49 +801,6 @@ var Options = Module("options", {
return null;
},
/**
* Lists all options in *scope* or only those with changed values if
* *onlyNonDefault* is specified.
*
* @param {function(Option)} filter Limit the list
* @param {number} scope Only list options in this scope (see
* {@link Option#scope}).
*/
list: function (filter, scope) {
if (!scope)
scope = Option.SCOPE_BOTH;
function opts(opt) {
for (let opt in Iterator(options)) {
let option = {
__proto__: opt,
isDefault: opt.isDefault,
default: opt.stringDefaultValue,
pre: "\u00a0\u00a0", // Unicode nonbreaking space.
value: <></>
};
if (filter && !filter(opt))
continue;
if (!(opt.scope & scope))
continue;
if (opt.type == "boolean") {
if (!opt.value)
option.pre = "no";
option.default = (opt.defaultValue ? "" : "no") + opt.name;
}
else if (isArray(opt.value))
option.value = <>={template.map(opt.value, function (v) template.highlight(String(v)), <>,<span style="width: 0; display: inline-block"> </span></>)}</>;
else
option.value = <>={template.highlight(opt.stringValue)}</>;
yield option;
}
};
commandline.commandOutput(template.options("Options", opts(), options["verbose"] > 0));
},
/**
* Parses a :set command's argument string.
*
@@ -794,7 +825,7 @@ var Options = Module("options", {
}
if (matches) {
ret.option = options.get(ret.name, ret.scope);
ret.option = this.get(ret.name, ret.scope);
if (!ret.option && (ret.option = options.get(prefix + ret.name, ret.scope))) {
ret.name = prefix + ret.name;
prefix = "";
@@ -842,7 +873,9 @@ var Options = Module("options", {
get store() storage.options
}, {
}, {
commands: function () {
commands: function initCommands(dactyl, modules, window) {
const { commands, contexts, options } = modules;
let args = {
getMode: function (args) findMode(args["-mode"]),
iterate: function (args) {
@@ -909,7 +942,7 @@ var Options = Module("options", {
}
if (name == "all" && reset)
commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ",
modules.commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ",
function (resp) {
if (resp == "yes")
for (let pref in values(prefs.getNames()))
@@ -939,22 +972,21 @@ var Options = Module("options", {
prefs.set(name, value);
}
else
commandline.commandOutput(prefs.list(onlyNonDefault, name));
modules.commandline.commandOutput(prefs.list(onlyNonDefault, name));
return;
}
let opt = options.parseOpt(arg, modifiers);
dactyl.assert(opt, "Error parsing :set command: " + arg);
let opt = modules.options.parseOpt(arg, modifiers);
util.assert(opt, "Error parsing :set command: " + arg);
let option = opt.option;
dactyl.assert(option != null || opt.all,
"E518: Unknown option: " + opt.name);
util.assert(option != null || opt.all, "E518: Unknown option: " + opt.name);
// reset a variable to its default value
if (opt.reset) {
flushList();
if (opt.all) {
for (let option in options)
for (let option in modules.options)
option.reset();
}
else {
@@ -968,7 +1000,7 @@ var Options = Module("options", {
else {
flushList();
if (opt.option.type === "boolean") {
dactyl.assert(!opt.valueGiven, "E474: Invalid argument: " + arg);
util.assert(!opt.valueGiven, "E474: Invalid argument: " + arg);
opt.values = !opt.unsetBoolean;
}
else if (/^(string|number)$/.test(opt.option.type) && opt.invert)
@@ -982,7 +1014,7 @@ var Options = Module("options", {
}
if (res)
dactyl.echoerr(res);
option.setFrom = Commands.getCaller(null);
option.setFrom = contexts.getCaller(null);
}
}
flushList();
@@ -1007,7 +1039,7 @@ var Options = Module("options", {
return completion.preference(context);
}
let opt = options.parseOpt(filter, modifiers);
let opt = modules.options.parseOpt(filter, modifiers);
let prefix = opt.prefix;
context.highlight();
@@ -1048,7 +1080,7 @@ var Options = Module("options", {
}
let optcontext = context.fork("values");
completion.optionValue(optcontext, opt.name, opt.operator);
modules.completion.optionValue(optcontext, opt.name, opt.operator);
// Fill in the current values if we're removing
if (opt.operator == "-" && isArray(opt.values)) {
@@ -1058,7 +1090,7 @@ var Options = Module("options", {
context.maxItems = optcontext.maxItems;
context.filters.push(function (i) !set.has(have, i.text));
completion.optionValue(context, opt.name, opt.operator, null,
modules.completion.optionValue(context, opt.name, opt.operator, null,
function (context) {
context.generate = function () option.value.map(function (o) [o, ""]);
});
@@ -1101,10 +1133,10 @@ var Options = Module("options", {
let [, scope, name, op, expr] = matches;
let fullName = (scope || "") + name;
dactyl.assert(scope == "g:" || scope == null,
"E461: Illegal variable name: " + scope + name);
dactyl.assert(set.has(globalVariables, name) || (expr && !op),
"E121: Undefined variable: " + fullName);
util.assert(scope == "g:" || scope == null,
"E461: Illegal variable name: " + scope + name);
util.assert(set.has(globalVariables, name) || (expr && !op),
"E121: Undefined variable: " + fullName);
if (!expr)
dactyl.echo(fullName + "\t\t" + fmt(globalVariables[name]));
@@ -1113,7 +1145,7 @@ var Options = Module("options", {
var newValue = dactyl.userEval(expr);
}
catch (e) {}
dactyl.assert(newValue !== undefined,
util.assert(newValue !== undefined,
"E15: Invalid expression: " + expr);
let value = newValue;
@@ -1160,7 +1192,7 @@ var Options = Module("options", {
literalArg: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
: opt.name + "=" + opt.stringValue]
}
for (opt in options)
for (opt in modules.options)
if (!opt.getter && !opt.isDefault && (opt.scope & Option.SCOPE_GLOBAL))
]
}
@@ -1175,18 +1207,18 @@ var Options = Module("options", {
completer: setCompleter,
domains: function (args) array.flatten(args.map(function (spec) {
try {
let opt = options.parseOpt(spec);
let opt = modules.options.parseOpt(spec);
if (opt.option && opt.option.domains)
return opt.option.domains(opt.values);
}
catch (e) {
dactyl.reportError(e);
util.reportError(e);
}
return [];
})),
keepQuotes: true,
privateData: function (args) args.some(function (spec) {
let opt = options.parseOpt(spec);
let opt = modules.options.parseOpt(spec);
return opt.option && opt.option.privateData &&
(!callable(opt.option.privateData) ||
opt.option.privateData(opt.values));
@@ -1216,12 +1248,14 @@ var Options = Module("options", {
deprecated: "the options system"
});
},
completion: function () {
completion: function initCompletion(dactyl, modules, window) {
const { completion } = modules;
completion.option = function option(context, scope, prefix) {
context.title = ["Option"];
context.keys = { text: "names", description: "description" };
context.anchored = false;
context.completions = options;
context.completions = modules.options;
if (prefix == "inv")
context.keys.text = function (opt)
opt.type == "boolean" || isArray(opt.value) ? opt.names.map(function (n) "inv" + n)
@@ -1231,7 +1265,7 @@ var Options = Module("options", {
};
completion.optionValue = function (context, name, op, curValue, completer) {
let opt = options.get(name);
let opt = modules.options.get(name);
completer = completer || opt.completer;
if (!completer || !opt)
return;
@@ -1294,15 +1328,18 @@ var Options = Module("options", {
context.completions = res;
};
},
javascript: function () {
javascript: function initJavascript(dactyl, modules, window) {
const { options, JavaScript } = modules;
JavaScript.setCompleter(options.get, [function () ([o.name, o.description] for (o in options))]);
},
sanitizer: function () {
sanitizer: function initSanitizer(dactyl, modules, window) {
const { sanitizer } = modules;
sanitizer.addItem("options", {
description: "Options containing hostname data",
action: function (timespan, host) {
if (host)
for (let opt in values(options._options))
for (let opt in values(modules.options._options))
if (timespan.contains(opt.lastSet * 1000) && opt.domains)
try {
opt.value = opt.filterDomain(host, opt.value);
@@ -1312,12 +1349,12 @@ var Options = Module("options", {
}
},
privateEnter: function () {
for (let opt in values(options._options))
for (let opt in values(modules.options._options))
if (opt.privateData && (!callable(opt.privateData) || opt.privateData(opt.value)))
opt.oldValue = opt.value;
},
privateLeave: function () {
for (let opt in values(options._options))
for (let opt in values(modules.options._options))
if (opt.oldValue != null) {
opt.value = opt.oldValue;
opt.oldValue = null;
@@ -1327,4 +1364,8 @@ var Options = Module("options", {
}
});
// vim: set fdm=marker sw=4 ts=4 et:
endModule();
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:

View File

@@ -102,6 +102,7 @@ var Overlay = Module("Overlay", {
const jsmodules = { NAME: "jsmodules" };
const modules = update(create(jsmodules), {
yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [],
jsmodules: jsmodules,
@@ -153,13 +154,16 @@ var Overlay = Module("Overlay", {
defineModule.time("load", null, function _load() {
["addons",
"base",
"commands",
"completion",
"config",
"contexts",
"downloads",
"finder",
"highlight",
"io",
"javascript",
"options",
"overlay",
"prefs",
"services",
@@ -171,18 +175,16 @@ var Overlay = Module("Overlay", {
["dactyl",
"modes",
"commandline",
"abbreviations",
"autocommands",
"buffer",
"commandline",
"commands",
"editor",
"events",
"hints",
"mappings",
"marks",
"mow",
"options",
"statusline"
].forEach(function (name) defineModule.time("load", name, modules.load, modules, name));
@@ -294,8 +296,10 @@ var Overlay = Module("Overlay", {
frob("init");
defineModule.modules.forEach(function ({ lazyInit, constructor: { className } }) {
if (!lazyInit)
if (!lazyInit) {
frob(className);
modules[className] = modules[className];
}
else
modules.__defineGetter__(className, function () {
delete modules[className];
@@ -323,6 +327,8 @@ var Overlay = Module("Overlay", {
}
});
endModule();
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:

View File

@@ -604,12 +604,12 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef
context.completions = this.values;
},
values: [
["all", "Everything"],
["session", "The current session"],
["10m", "Last ten minutes"],
["1h", "Past hour"],
["1d", "Past day"],
["1w", "Past week"]
["all", "Everything"],
["session", "The current session"],
["10m", "Last ten minutes"],
["1h", "Past hour"],
["1d", "Past day"],
["1w", "Past week"]
],
validator: function (value) /^(a(ll)?|s(ession)|\d+[mhdw])$/.test(value)
});

View File

@@ -84,6 +84,7 @@ var Services = Module("Services", {
this.addClass("Timer", "@mozilla.org/timer;1", Ci.nsITimer, "initWithCallback");
this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1",Ci.nsIAsyncStreamCopier, "init");
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", Ci.nsIDOMXPathEvaluator);
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", Ci.nsIZipReader, "open");
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter);
},

View File

@@ -8,13 +8,13 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("styles", {
exports: ["Style", "Styles", "styles"],
require: ["services", "util"],
use: ["template"]
use: ["contexts", "template"]
}, this);
function cssUri(css) "chrome-data:text/css," + encodeURI(css);
var namespace = "@namespace html " + XHTML.uri.quote() + ";\n" +
"@namespace xul " + XUL.uri.quote() + ";\n" +
"@namespace dactyl " + NS.uri.quote() + ";\n";
"@namespace xul " + XUL.uri.quote() + ";\n" +
"@namespace dactyl " + NS.uri.quote() + ";\n";
var Sheet = Struct("name", "id", "sites", "css", "hive", "agent");
Sheet.liveProperty = function (name) {
@@ -23,7 +23,7 @@ Sheet.liveProperty = function (name) {
this.prototype.__defineSetter__(name, function (val) {
if (isArray(val))
val = Array.slice(val);
if (isArray(val) && Object.freeze)
if (isArray(val))
Object.freeze(val);
this[i] = val;
this.enabled = this.enabled;
@@ -87,6 +87,19 @@ var Hive = Class("Hive", {
this.name = name;
this.sheets = [];
this.names = {};
this.refs = [];
},
addRef: function (obj) {
this.refs.push(Cu.getWeakReference(obj));
this.dropRef(null);
},
dropRef: function (obj) {
this.refs = this.refs.filter(function (ref) ref.get() && ref.get() !== obj);
if (!this.refs.length) {
this.cleanup();
styles.hives = styles.hives.filter(function (h) h !== this, this);
}
},
cleanup: function cleanup() {
@@ -219,7 +232,6 @@ var Hive = Class("Hive", {
},
});
try {
/**
* Manages named and unnamed user style sheets, which apply to both
* chrome and content pages.
@@ -229,8 +241,7 @@ try {
var Styles = Module("Styles", {
init: function () {
this._id = 0;
this.user = Hive("user");
this.system = Hive("system");
this.cleanup();
this.allSheets = {};
services["dactyl:"].providers["style"] = function styleProvider(uri) {
@@ -242,8 +253,22 @@ var Styles = Module("Styles", {
},
cleanup: function cleanup() {
for each (let hive in [this.user, this.system])
hive.cleanup();
for each (let hive in this.hives)
util.trapErrors("cleanup", hive);
this.hives = [];
this.user = this.addHive("user", this);
this.system = this.addHive("system", this);
},
addHive: function addHive(name, ref) {
let hive = array.nth(this.hives, function (h) h.name === name, 0);
if (!hive) {
hive = Hive(name);
this.hives.push(hive);
}
if (ref)
hive.addRef(ref);
return hive;
},
__iterator__: function () Iterator(this.user.sheets.concat(this.system.sheets)),
@@ -324,7 +349,7 @@ var Styles = Module("Styles", {
matchFilter: function (filter) {
if (filter === "*")
var test = function test(uri) true;
else if (filter[0] === "^") {
else if (!/^(?:[a-z-]+:|[a-z-.]+$)/.test(filter)) {
let re = RegExp(filter);
test = function test(uri) re.test(uri.spec);
}
@@ -425,7 +450,7 @@ var Styles = Module("Styles", {
})
}, {
commands: function (dactyl, modules, window) {
const commands = modules.commands;
const { commands, contexts } = modules;
commands.add(["sty[le]"],
"Add or list user styles",
@@ -434,17 +459,17 @@ var Styles = Module("Styles", {
if (css) {
if ("-append" in args) {
let sheet = styles.user.get(args["-name"]);
let sheet = args["-group"].get(args["-name"]);
if (sheet) {
filter = sheet.sites.concat(filter).join(",");
css = sheet.css + " " + css;
}
}
styles.user.add(args["-name"], filter, css, args["-agent"]);
args["-group"].add(args["-name"], filter, css, args["-agent"]);
}
else {
let list = styles.user.sheets.slice()
let list = args["-group"].sheets.slice()
.sort(function (a, b) a.name && b.name ? String.localeCompare(a.name, b.name)
: !!b.name - !!a.name || a.id - b.id);
let uris = util.visibleURIs(window.content);
@@ -455,7 +480,7 @@ var Styles = Module("Styles", {
"padding: 0 1em 0 1ex; vertical-align: top;",
"padding: 0 1em 0 0; vertical-align: top;"],
([sheet.enabled ? "" : UTF8("×"),
sheet.name || styles.user.sheets.indexOf(sheet),
sheet.name || args["-group"].sheets.indexOf(sheet),
sheet.formatSites(uris),
sheet.css]
for (sheet in values(list))
@@ -466,7 +491,7 @@ var Styles = Module("Styles", {
bang: true,
completer: function (context, args) {
let compl = [];
let sheet = styles.user.get(args["-name"]);
let sheet = args["-group"].get(args["-name"]);
if (args.completeArg == 0) {
if (sheet)
context.completions = [[sheet.sites.join(","), "Current Value"]];
@@ -483,10 +508,11 @@ var Styles = Module("Styles", {
options: [
{ names: ["-agent", "-A"], description: "Apply style as an Agent sheet" },
{ names: ["-append", "-a"], description: "Append site filter and css to an existing, matching sheet" },
contexts.GroupFlag("styles"),
{
names: ["-name", "-n"],
description: "The name of this stylesheet",
completer: function () [[k, v.css] for ([k, v] in Iterator(styles.user.names))],
completer: function (context, args) [[k, v.css] for ([k, v] in Iterator(args["-group"].hive.names))],
type: modules.CommandOption.STRING
}
],
@@ -587,6 +613,28 @@ var Styles = Module("Styles", {
});
});
},
contexts: function (dactyl, modules, window) {
modules.contexts.Hives("styles",
Class("LocalHive", Contexts.Hive, {
init: function init(group) {
init.superapply(this, arguments);
this.hive = styles.addHive(group.name);
this.hive.addRef(this);
},
get names() this.hive.names,
get sheets() this.hive.sheets,
__noSuchMethod__: function __noSuchMethod__(meth, args) {
return this.hive[meth].apply(this.hive, args);
},
destroy: function () {
this.hive.dropRef(this);
}
}));
},
completion: function (dactyl, modules, window) {
const names = Array.slice(util.computedStyle(window.document.createElement("div")));
modules.completion.css = function (context) {
@@ -642,6 +690,6 @@ var Styles = Module("Styles", {
endModule();
} catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:

View File

@@ -6,7 +6,7 @@
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("template", {
exports: ["Template", "template"],
exports: ["Binding", "Template", "template"],
require: ["util"],
use: ["services"]
}, this);
@@ -14,8 +14,9 @@ defineModule("template", {
default xml namespace = XHTML;
var Binding = Class("Binding", {
init: function (node) {
init: function (node, nodes) {
this.node = node;
this.nodes = nodes;
node.dactylBinding = this;
Object.defineProperties(node, this.constructor.properties);
@@ -32,9 +33,13 @@ var Binding = Class("Binding", {
},
get collapsed() !!this.getAttribute("collapsed"),
__noSuchMethod__: function __noSuchMethod__(meth, args) {
return this.node[meth].apply(this.node, args);
}
__noSuchMethod__: Class.Property({
configurable: true,
writeable: true,
value: function __noSuchMethod__(meth, args) {
return this.node[meth].apply(this.node, args);
}
})
}, {
get bindings() {
let bindingProto = Object.getPrototypeOf(Binding.prototype);
@@ -66,10 +71,12 @@ var Binding = Class("Binding", {
for (let obj in this.bindings)
for (let prop in properties(obj)) {
let desc = Object.getOwnPropertyDescriptor(obj, prop);
for (let k in values(["get", "set", "value"]))
if (typeof desc[k] === "function")
desc[k] = this.bind(desc[k]);
res[prop] = desc;
if (desc.enumerable) {
for (let k in values(["get", "set", "value"]))
if (typeof desc[k] === "function")
desc[k] = this.bind(desc[k]);
res[prop] = desc;
}
}
return res;
})
@@ -86,10 +93,10 @@ var Template = Module("Template", {
let ret = <></>;
let n = 0;
for each (let i in Iterator(iter)) {
let val = func(i);
let val = func(i, n);
if (val == undefined)
continue;
if (sep && n++)
if (n++ && sep)
ret += sep;
if (interruptable && n % interruptable == 0)
util.threadYield(true, true);

View File

@@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("util", {
exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
require: ["services"],
use: ["config", "highlight", "storage", "template"]
use: ["commands", "config", "highlight", "storage", "template"]
}, this);
var XBL = Namespace("xbl", "http://www.mozilla.org/xbl");
@@ -22,13 +22,6 @@ var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is
var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
default xml namespace = XHTML;
memoize(this, "Commands", function () {
// FIXME
let obj = { Module: Class };
JSMLoader.loadSubScript("resource://dactyl-content/commands.js", obj);
return obj.Commands;
});
var FailedAssertion = Class("FailedAssertion", ErrorBase);
var Point = Struct("x", "y");
@@ -132,7 +125,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
obj.observers[target].call(obj, subject, data);
}
catch (e) {
util.reportError(e);
if (typeof util === "undefined")
dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl: "));
else
util.reportError(e);
}
});
@@ -770,14 +766,18 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
try {
let xmlhttp = services.Xmlhttp();
xmlhttp.mozBackgroundRequest = true;
if (params.callback) {
xmlhttp.onload = function handler(event) { util.trapErrors(params.callback, params, xmlhttp, event) };
xmlhttp.onerror = xmlhttp.onload;
let async = params.callback || params.onload || params.onerror;
if (async) {
xmlhttp.onload = function handler(event) { util.trapErrors(params.onload || params.callback, params, xmlhttp, event) };
xmlhttp.onerror = function handler(event) { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event) };
}
if (params.mimeType)
xmlhttp.overrideMimeType(params.mimeType);
xmlhttp.open(params.method || "GET", url, !!params.callback, params.user, params.pass);
xmlhttp.open(params.method || "GET", url, async,
params.user, params.pass);
xmlhttp.send(null);
return xmlhttp;
}
@@ -1042,7 +1042,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
JSMLoader.cleanup();
services.observer.addObserver(this, "dactyl-rehash", true);
if (!this.rehashing)
services.observer.addObserver(this, "dactyl-rehash", true);
},
"dactyl-rehash": function () {
services.observer.removeObserver(this, "dactyl-rehash");
@@ -1083,6 +1084,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
this._loadOverlay(window, obj(window));
}
},
_loadOverlay: function _loadOverlay(window, obj) {
let doc = window.document;
if (!doc.dactylOverlayElements) {
@@ -1353,7 +1355,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
* @param {string} string The string to search.
* @param {number} lastIndex The index at which to begin searching. @optional
*/
iterate: function iterate(regexp, string, lastIndex) {
iterate: function iterate(regexp, string, lastIndex) iter(function () {
regexp.lastIndex = lastIndex = lastIndex || 0;
let match;
while (match = regexp.exec(string)) {
@@ -1363,7 +1365,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
if (match[0].length == 0 || !regexp.global)
break;
}
}
}())
}),
rehash: function (args) {
@@ -1407,6 +1409,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
}
catch (e) { dump(e + "\n"); }
}
// ctypes.open("libc.so.6").declare("kill", ctypes.default_abi, ctypes.void_t, ctypes.int, ctypes.int)(
// ctypes.open("libc.so.6").declare("getpid", ctypes.default_abi, ctypes.int)(), 2)
},
/**
@@ -1598,6 +1603,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
*/
trapErrors: function trapErrors(func, self) {
try {
if (isString(func))
func = self[func];
return func.apply(self || this, Array.slice(arguments, 2));
}
catch (e) {

View File

@@ -64,7 +64,7 @@ function Controller(controller) {
}
this.errors = [];
this._countError = function countError(message, highlight) {
if (/\bErrorMsg\b/.test(highlight))
if (/\b(Error|Warning)Msg\b/.test(highlight))
self.errors.push(String(message));
}
this.modules.dactyl.registerObserver("beep", this._countBeep);

View File

@@ -122,7 +122,17 @@ var tests = {
singleOutput: ["", "foobar"],
noOutput: ["foo bar", "-js bar baz"],
multiOutput: [""],
error: ["foo bar", "-js bar baz"]
error: [
"foo bar",
"-js bar baz",
"-group=builtin baz quux",
"! -group=builtin baz quux",
],
completions: [
["", hasItems],
["-group=", hasItems],
["-group=user ", hasItems]
]
},
comclear: {
noOutput: [""]
@@ -140,6 +150,11 @@ var tests = {
delcommand: [
{
init: ["comclear", "command foo bar"],
completions: [
["", hasItems],
["-group=", hasItems],
["-group=user ", hasItems]
],
noOutput: ["foo"]
},
{
@@ -152,7 +167,6 @@ var tests = {
noOutput: ["x"],
completions: ["", "x"]
},
delmapgroup: {}, // Skip for now
get delmarks() this.delmacros,
get delqmarks() this.delmacros,
delstyle: {
@@ -233,6 +247,25 @@ var tests = {
finish: { noOutput: [""] },
forward: { noOutput: [""] },
frameonly: { noOutput: [""] },
delgroup: {
error: ["builtin"],
completions: [""]
},
group: {
multiOutput: [""],
noOutput: [
"foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'",
"! foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'",
"foo",
"user"
],
error: ["builtin"],
completions: [
"",
"foo "
],
cleanup: ["delmapgroup foo"]
},
hardcopy: {}, // Skip for now
help: {
noOutput: ["", "intro"],
@@ -279,7 +312,21 @@ var tests = {
["window", hasItems],
["window.", hasItems],
["window['", hasItems],
["commands.get('", hasItems]
["File('", hasItems],
["File.expandPath('", hasItems],
"autocommands.user.get('",
["commands.get('", hasItems],
["commands.builtin.get('", hasItems],
["highlight.get('", hasItems],
["highlight.highlightNode(null, '", hasItems],
["mappings.get(modes.NORMAL, '", hasItems],
// ["mappings.builtin.get(modes.NORMAL, '", hasItems],
["options.get('", hasItems],
["prefs.get('", hasItems],
["prefs.defaults.get('", hasItems],
["localPrefs.get('", hasItems],
["localPrefs.defaults.get('", hasItems],
["styles.system.get('", hasItems],
]
},
jumps: {
@@ -318,7 +365,8 @@ var tests = {
],
error: [
"-mode=some-nonexistent-mode <C-a> <C-a>",
"-gtroup=some-nonexistent-group <C-a> <C-a>"
"-group=some-nonexistent-group <C-a> <C-a>",
"-group=builtin <C-a> <C-a>"
],
completions: [
["", hasItems],
@@ -333,25 +381,13 @@ var tests = {
},
mapclear: {
noOutput: [""],
completions: [""]
},
mapgroup: {
multiOutput: [""],
noOutput: [
"foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'",
"! foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'",
"foo",
"user"
],
error: [
"some-nonexistent-group",
"foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'"
"-group=builtin"
],
completions: [
"",
"foo "
],
cleanup: ["delmapgroup foo"]
"-group="
]
},
mark: {
error: ["", "#", "xy"],

View File

@@ -8,6 +8,16 @@
- Only visible tabs are considered in tab numbering,
gt/gn/gN, etc. [b1]
* Improved startup time by a factor of 7. [b1]
* Further improved startup time. [b6]
* Added site-local and script-local groups: [b6]
- Added the :group command to define and select groups.
- Added the -group flag to :autocmd, :command, :map, :style,
and friends.
- Mappings and commands can now be bound to groups which
execute only for certain websites.
- Autocommands, commands, mappings, and styles are now
automatically added to per-script groups so that most traces
of a script can be easily purged.
* Significant completion speed improvements, especially for
JavaScript. [b1]
* Greatly improved private mode support and :sanitize command.
@@ -76,8 +86,6 @@
and linking to source code locations). [b4]
- :downloads now opens a download list in the multi-line output
buffer. [b6]
- Added :mapgroup command and -group flag to :map, :unmap, and
:mapclear. [b6]
- Added -arg flag to :map. [b6]
- :extensions has been replaced with a more powerful :addons.
- Added -literal flag to :command. [b6]