diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -22,3 +22,7 @@ 2c21fc6135f832c7bbadf43586d2ffe585f02f60
1f1342f58d8e7d3972928f193e3f40cbf4425230 pentadactyl-1.0b4.1
962d8a1e823d0855e14aecaa294ef02e718401b1 pentadactyl-1.0b4.2
d783bcace8c6a24bee7bd22ead22fe44baaebe90 pentadactyl-1.0b4.3
+b83bb8e6d273f71b1278c4ee03376594ad6dd039 pentadactyl-1.0b6
+0000000000000000000000000000000000000000 vimperator-2.2~beta1
+0000000000000000000000000000000000000000 vimperator-2.0~alpha1
+2281deb4c1323643dfc130c43d79f1a14f4a761a pentadactyl-1.0b7
diff --git a/HACKING b/HACKING
--- a/HACKING
+++ b/HACKING
@@ -87,7 +87,7 @@ In general: Just look at the existing so
https://developer.mozilla.org/en/New_in_JavaScript_1.7#Block_scope_with_let
* Reuse common local variable names E.g. "elem" is generally used for element,
- "win" for windows, "func" for functions, "ret" for return values etc.
+ "win" for windows, "func" for functions, "res" for return values etc.
* Prefer // over /* */ comments (exceptions for big comments are usually OK)
Right: if (HACK) // TODO: remove hack
@@ -138,12 +138,7 @@ In general: Just look at the existing so
Functional tests are implemented using the Mozmill automated testing framework
-- https://developer.mozilla.org/en/Mozmill_Tests.
-A fresh profile is created for the duration of the test run, however, passing
-arguments to the host application won't be supported until Mozmill 1.5.2, the
-next release, so any user RC and plugin files should be temporarily disabled.
-This can be done by adding the following to the head of the RC file:
-set loadplugins=
-finish
+A fresh profile is created for the duration of the test run.
The host application binary tested can be overridden via the HOSTAPP_PATH
makefile variable. E.g.,
diff --git a/common/Makefile b/common/Makefile
--- a/common/Makefile
+++ b/common/Makefile
@@ -1,13 +1,21 @@
#### configuration
+
+AWK ?= awk
+B64ENCODE ?= base64
+CURL ?= curl
+SED := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)//' 2>/dev/null) ]; \
+ then echo sed -E; else echo sed -r; \
+ fi)
+
TOP = $(shell pwd)
OS = $(shell uname -s)
BUILD_DATE = $(shell date "+%Y/%m/%d %H:%M:%S")
BASE = $(TOP)/../common
GOOGLE_PROJ = dactyl
GOOGLE = https://$(GOOGLE_PROJ).googlecode.com/files
-VERSION ?= $(shell sed -n 's/.*em:version\(>\|="\)\(.*\)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
-UUID := $(shell sed -n 's/.*em:id\(>\|="\)\(.*\)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
+VERSION ?= $(shell $(SED) -n 's/.*em:version(>|=")(.*)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
+UUID := $(shell $(SED) -n 's/.*em:id(>|=")(.*)["<].*/\2/p' $(TOP)/install.rdf | sed 1q)
MANGLE := $(shell date '+%s' | awk '{ printf "%x", $$1 }')
MOZMILL = mozmill
HOSTAPP_PATH = $(shell which $(HOSTAPP))
@@ -46,13 +54,6 @@ RDF_IN = $(RDF).in
BUILD_DIR = build.$(VERSION).$(OS)
-AWK ?= awk
-B64ENCODE ?= base64
-CURL ?= curl
-SED := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)//' 2>/dev/null) ]; \
- then echo sed -E; else echo sed -r; \
- fi)
-
.SILENT:
#### rules
diff --git a/common/bootstrap.js b/common/bootstrap.js
--- a/common/bootstrap.js
+++ b/common/bootstrap.js
@@ -29,6 +29,8 @@ const resourceProto = Services.io.getPro
const categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
const manager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+const BOOTSTRAP_JSM = "resource://dactyl/bootstrap.jsm";
+
const BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap";
JSMLoader = JSMLoader || BOOTSTRAP_CONTRACT in Cc && Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader;
@@ -41,7 +43,6 @@ if (!JSMLoader && "@mozilla.org/fuel/app
.getService(Components.interfaces.extIApplication)
.storage.get("dactyl.JSMLoader", null);
-
function reportError(e) {
dump("\ndactyl: bootstrap: " + e + "\n" + (e.stack || Error().stack) + "\n");
Cu.reportError(e);
@@ -156,7 +157,6 @@ function init() {
let chars = "0123456789abcdefghijklmnopqrstuv";
for (let n = Date.now(); n; n = Math.round(n / chars.length))
suffix += chars[n % chars.length];
- suffix = "";
for each (let line in manifest.split("\n")) {
let fields = line.split(/\s+/);
@@ -173,12 +173,22 @@ function init() {
break;
case "resource":
+ var hardSuffix = /^[^\/]*/.exec(fields[2])[0];
+
resources.push(fields[1], fields[1] + suffix);
resourceProto.setSubstitution(fields[1], getURI(fields[2]));
resourceProto.setSubstitution(fields[1] + suffix, getURI(fields[2]));
}
}
+ // Flush the cache if necessary, just to be paranoid
+ let pref = "extensions.dactyl.cacheFlushCheck";
+ let val = addon.version + "-" + hardSuffix;
+ if (!Services.prefs.prefHasUserValue(pref) || Services.prefs.getCharPref(pref) != val) {
+ Services.obs.notifyObservers(null, "startupcache-invalidate", "");
+ Services.prefs.setCharPref(pref, val);
+ }
+
try {
module("resource://dactyl-content/disable-acr.jsm").init(addon.id);
}
@@ -186,16 +196,23 @@ function init() {
reportError(e);
}
- if (JSMLoader && JSMLoader.bump !== 4) // Temporary hack
+ if (JSMLoader) {
+ if (Cu.unload) {
+ Cu.unload(BOOTSTRAP_JSM);
+ for (let [name] in Iterator(JSMLoader.globals))
+ Cu.unload(~name.indexOf(":") ? name : "resource://dactyl" + JSMLoader.suffix + "/" + name);
+ }
+ else if (JSMLoader.bump != 5) // Temporary hack
Services.scriptloader.loadSubScript("resource://dactyl" + suffix + "/bootstrap.jsm",
- Cu.import("resource://dactyl/bootstrap.jsm", global));
+ Cu.import(BOOTSTRAP_JSM, global));
+ }
- if (!JSMLoader || JSMLoader.bump !== 4)
- Cu.import("resource://dactyl/bootstrap.jsm", global);
+ if (!JSMLoader || JSMLoader.bump !== 5 || Cu.unload)
+ Cu.import(BOOTSTRAP_JSM, global);
JSMLoader.bootstrap = this;
- JSMLoader.load("resource://dactyl/bootstrap.jsm", global);
+ JSMLoader.load(BOOTSTRAP_JSM, global);
JSMLoader.init(suffix);
JSMLoader.load("base.jsm", global);
@@ -211,9 +228,9 @@ function init() {
wrappedJSObject: {}
},
createInstance: function () this.instance
- })
+ });
- Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader = JSMLoader;
+ Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader = !Cu.unload && JSMLoader;
for each (let component in components)
component.register();
@@ -233,11 +250,11 @@ function shutdown(data, reason) {
reportError(e);
}
- if ([ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason) >= 0)
+ if (~[ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason))
Services.obs.notifyObservers(null, "dactyl-purge", null);
- Services.obs.notifyObservers(null, "dactyl-cleanup", null);
- Services.obs.notifyObservers(null, "dactyl-cleanup-modules", null);
+ Services.obs.notifyObservers(null, "dactyl-cleanup", reasonToString(reason));
+ Services.obs.notifyObservers(null, "dactyl-cleanup-modules", reasonToString(reason));
JSMLoader.purge();
for each (let [category, entry] in categories)
diff --git a/common/components/protocols.js b/common/components/protocols.js
--- a/common/components/protocols.js
+++ b/common/components/protocols.js
@@ -248,7 +248,7 @@ function LocaleChannel(pkg, path, orig)
function StringChannel(data, contentType, uri) {
let channel = services.StreamChannel(uri);
- channel.contentStream = services.StringStream(data);
+ channel.contentStream = services.CharsetConv("UTF-8").convertToInputStream(data);
if (contentType)
channel.contentType = contentType;
channel.contentCharset = "UTF-8";
@@ -293,12 +293,12 @@ function XMLChannel(uri, contentType) {
if (doctype) {
this.writes.push(doctype + "[\n");
try {
- this.writes.push(services.io.newChannel(url, null, null).open())
+ this.writes.push(services.io.newChannel(url, null, null).open());
}
catch (e) {}
if (!open)
this.writes.push("\n]");
- this.writes.push(post)
+ this.writes.push(post);
}
this.writes.push(channelStream);
diff --git a/common/content/abbreviations.js b/common/content/abbreviations.js
--- a/common/content/abbreviations.js
+++ b/common/content/abbreviations.js
@@ -8,6 +8,23 @@
/** @scope modules */
+/**
+ * A user-defined input mode binding of a typed string to an automatically
+ * inserted expansion string.
+ *
+ * Abbreviations have a left-hand side (LHS) whose text is replaced by that of
+ * the right-hand side (RHS) when triggered by an Input mode expansion key.
+ * E.g. an abbreviation with a LHS of "gop" and RHS of "Grand Old Party" will
+ * replace the former with the latter.
+ *
+ * @param {[Mode]} modes The modes in which this abbreviation is active.
+ * @param {string} lhs The left hand side of the abbreviation; the text to
+ * be replaced.
+ * @param {string|function(nsIEditor):string} rhs The right hand side of
+ * the abbreviation; the replacement text. This may either be a string
+ * literal or a function that will be passed the appropriate nsIEditor.
+ * @private
+ */
var Abbreviation = Class("Abbreviation", {
init: function (modes, lhs, rhs) {
this.modes = modes.sort();
@@ -15,20 +32,61 @@ var Abbreviation = Class("Abbreviation",
this.rhs = rhs;
},
+ /**
+ * Returns true if this abbreviation's LHS and RHS are equal to those in
+ * *other*.
+ *
+ * @param {Abbreviation} other The abbreviation to test.
+ * @returns {boolean} The result of the comparison.
+ */
equals: function (other) this.lhs == other.lhs && this.rhs == other.rhs,
+ /**
+ * Returns the abbreviation's expansion text.
+ *
+ * @param {nsIEditor} editor The editor in which abbreviation expansion is
+ * occurring.
+ * @returns {string}
+ */
expand: function (editor) String(callable(this.rhs) ? this.rhs(editor) : this.rhs),
+ /**
+ * Returns true if this abbreviation is defined for all *modes*.
+ *
+ * @param {[Mode]} modes The modes to test.
+ * @returns {boolean} The result of the comparison.
+ */
modesEqual: function (modes) array.equals(this.modes, modes),
+ /**
+ * Returns true if this abbreviation is defined for *mode*.
+ *
+ * @param {Mode} mode The mode to test.
+ * @returns {boolean} The result of the comparison.
+ */
inMode: function (mode) this.modes.some(function (_mode) _mode == mode),
+ /**
+ * Returns true if this abbreviation is defined in any of *modes*.
+ *
+ * @param {[Modes]} modes The modes to test.
+ * @returns {boolean} The result of the comparison.
+ */
inModes: function (modes) modes.some(function (mode) this.inMode(mode), this),
+ /**
+ * Remove *mode* from the list of supported modes for this abbreviation.
+ *
+ * @param {Mode} mode The mode to remove.
+ */
removeMode: function (mode) {
this.modes = this.modes.filter(function (m) m != mode).sort();
},
+ /**
+ * @property {string} The mode display characters associated with the
+ * supported mode combination.
+ */
get modeChar() Abbreviation.modeChar(this.modes)
}, {
modeChar: function (_modes) {
@@ -45,6 +103,7 @@ var AbbrevHive = Class("AbbrevHive", Con
this._store = {};
},
+ /** @property {boolean} True if there are no abbreviations. */
get empty() !values(this._store).nth(util.identity, 0),
/**
@@ -68,14 +127,15 @@ var AbbrevHive = Class("AbbrevHive", Con
*
* @param {Mode} mode The mode of the abbreviation.
* @param {string} lhs The LHS of the abbreviation.
+ * @returns {Abbreviation} The matching abbreviation.
*/
get: function (mode, lhs) {
let abbrevs = this._store[mode];
- return abbrevs && set.has(abbrevs, lhs) ? abbrevs[lhs] : null;
+ return abbrevs && Set.has(abbrevs, lhs) ? abbrevs[lhs] : null;
},
/**
- * @property {Abbreviation[]} The list of the abbreviations merged from
+ * @property {[Abbreviation]} The list of the abbreviations merged from
* each mode.
*/
get merged() {
@@ -189,13 +249,15 @@ var Abbreviations = Module("abbreviation
},
/**
- * Lists all abbreviations matching *modes* and *lhs*.
+ * Lists all abbreviations matching *modes*, *lhs* and optionally *hives*.
*
* @param {Array} modes List of modes.
* @param {string} lhs The LHS of the abbreviation.
+ * @param {[Hive]} hives List of hives.
+ * @optional
*/
- list: function (modes, lhs) {
- let hives = contexts.allGroups.abbrevs.filter(function (h) !h.empty);
+ list: function (modes, lhs, hives) {
+ let hives = hives || contexts.allGroups.abbrevs.filter(function (h) !h.empty);
function abbrevs(hive)
hive.merged.filter(function (abbr) (abbr.inModes(modes) && abbr.lhs.indexOf(lhs) == 0));
@@ -203,9 +265,9 @@ var Abbreviations = Module("abbreviation
let list = <table>
<tr highlight="Title">
<td/>
- <td style="padding-right: 1em;">Mode</td>
- <td style="padding-right: 1em;">Abbrev</td>
- <td style="padding-right: 1em;">Replacement</td>
+ <td style="padding-right: 1em;">{_("title.Mode")}</td>
+ <td style="padding-right: 1em;">{_("title.Abbrev")}</td>
+ <td style="padding-right: 1em;">{_("title.Replacement")}</td>
</tr>
<col style="min-width: 6em; padding-right: 1em;"/>
{
@@ -224,7 +286,7 @@ var Abbreviations = Module("abbreviation
// TODO: Move this to an ItemList to show this automatically
if (list.*.length() === list.text().length() + 2)
- dactyl.echomsg(_("abbrev.none"));
+ dactyl.echomsg(_("abbreviation.none"));
else
commandline.commandOutput(list);
}
@@ -245,7 +307,6 @@ var Abbreviations = Module("abbreviation
context.completions = group.merged.filter(fn);
};
},
-
commands: function () {
function addAbbreviationCommands(modes, ch, modeDescription) {
modes.sort();
@@ -258,14 +319,17 @@ var Abbreviations = Module("abbreviation
dactyl.assert(!args.length || abbreviations._check.test(lhs),
_("error.invalidArgument"));
- if (!rhs)
- abbreviations.list(modes, lhs || "");
+ if (!rhs) {
+ let hives = args.explicitOpts["-group"] ? [args["-group"]] : null;
+ abbreviations.list(modes, lhs || "", hives);
+ }
else {
if (args["-javascript"])
rhs = contexts.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]);
args["-group"].add(modes, lhs, rhs);
}
}, {
+ identifier: "abbreviate",
completer: function (context, args) {
if (args.length == 1)
return completion.abbreviation(context, modes, args["-group"]);
@@ -301,7 +365,7 @@ var Abbreviations = Module("abbreviation
if (args.bang)
args["-group"].clear(modes);
else if (!args["-group"].remove(modes, args[0]))
- return dactyl.echoerr(_("abbrev.noSuch"));
+ return dactyl.echoerr(_("abbreviation.noSuch"));
}, {
argCount: "?",
bang: true,
@@ -312,8 +376,9 @@ var Abbreviations = Module("abbreviation
}
addAbbreviationCommands([modes.INSERT, modes.COMMAND_LINE], "", "");
- addAbbreviationCommands([modes.INSERT], "i", "insert");
- addAbbreviationCommands([modes.COMMAND_LINE], "c", "command line");
+ [modes.INSERT, modes.COMMAND_LINE].forEach(function (mode) {
+ addAbbreviationCommands([mode], mode.char, mode.displayName);
+ });
}
});
diff --git a/common/content/autocommands.js b/common/content/autocommands.js
--- a/common/content/autocommands.js
+++ b/common/content/autocommands.js
@@ -47,7 +47,7 @@ var AutoCmdHive = Class("AutoCmdHive", C
*
* @param {string} event The event name filter.
* @param {string} pattern The URL pattern filter.
- * @returns {AutoCommand[]}
+ * @returns {[AutoCommand]}
*/
get: function (event, pattern) {
return this._store.filter(function (autoCmd) autoCmd.match(event, regexp));
@@ -84,12 +84,17 @@ var AutoCommands = Module("autocommands"
remove: deprecated("group.autocmd.remove", { get: function remove() autocommands.user.closure.remove }),
/**
- * Lists all autocommands with a matching *event* and *regexp*.
+ * Lists all autocommands with a matching *event*, *regexp* and optionally
+ * *hives*.
*
* @param {string} event The event name filter.
* @param {string} regexp The URL pattern filter.
+ * @param {[Hive]} hives List of hives.
+ * @optional
*/
- list: function (event, regexp) {
+ list: function (event, regexp, hives) {
+
+ let hives = hives || this.activeHives;
function cmds(hive) {
let cmds = {};
@@ -108,7 +113,7 @@ var AutoCommands = Module("autocommands"
<td colspan="3">----- Auto Commands -----</td>
</tr>
{
- template.map(this.activeHives, function (hive)
+ template.map(hives, function (hive)
<tr highlight="Title">
<td colspan="3">{hive.name}</td>
</tr> +
@@ -143,7 +148,7 @@ var AutoCommands = Module("autocommands"
let lastPattern = null;
var { url, doc } = args;
if (url)
- uri = util.newURI(url);
+ uri = util.createURI(url);
else
var { uri, doc } = buffer;
@@ -200,7 +205,7 @@ var AutoCommands = Module("autocommands"
args["-group"].remove(event, regexp); // remove all
}
else
- autocommands.list(event, regexp); // list all
+ autocommands.list(event, regexp, args.explicitOpts["-group"] ? [args["-group"]] : null); // list all
}
}, {
bang: true,
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -11,6 +11,10 @@ var DEFAULT_FAVICON = "chrome://mozapps/
// also includes methods for dealing with keywords and search engines
var Bookmarks = Module("bookmarks", {
init: function () {
+ this.timer = Timer(0, 100, function () {
+ this.checkBookmarked(buffer.uri);
+ }, this);
+
storage.addObserver("bookmark-cache", function (key, event, arg) {
if (["add", "change", "remove"].indexOf(event) >= 0)
autocommands.trigger("Bookmark" + event[0].toUpperCase() + event.substr(1),
@@ -20,10 +24,17 @@ var Bookmarks = Module("bookmarks", {
valueOf: function () arg
}
}, arg));
- statusline.updateStatus();
+ bookmarks.timer.tell();
}, window);
},
+ signals: {
+ "browser.locationChange": function (webProgress, request, uri) {
+ statusline.bookmarked = false;
+ this.checkBookmarked(uri);
+ }
+ },
+
get format() ({
anchored: false,
title: ["URL", "Info"],
@@ -103,7 +114,7 @@ var Bookmarks = Module("bookmarks", {
*
* @param {Element} elem A form element for which to add a keyword.
*/
- addSearchKeyword: function (elem) {
+ addSearchKeyword: function addSearchKeyword(elem) {
if (elem instanceof HTMLFormElement || elem.form)
var [url, post, charset] = util.parseForm(elem);
else
@@ -119,6 +130,17 @@ var Bookmarks = Module("bookmarks", {
commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ");
},
+ checkBookmarked: function checkBookmarked(uri) {
+ if (PlacesUtils.asyncGetBookmarkIds)
+ PlacesUtils.asyncGetBookmarkIds(uri, function (ids) {
+ statusline.bookmarked = ids.length;
+ });
+ else
+ this.timeout(function () {
+ statusline.bookmarked = bookmarkcache.isBookmarked(uri);
+ });
+ },
+
/**
* Toggles the bookmarked state of the given URL. If the URL is
* bookmarked, all bookmarks for said URL are removed.
@@ -197,7 +219,7 @@ var Bookmarks = Module("bookmarks", {
if (!alias)
alias = "search"; // for search engines which we can't find a suitable alias
- if (set.has(aliases, alias))
+ if (Set.has(aliases, alias))
alias += ++aliases[alias];
else
aliases[alias] = 0;
@@ -225,7 +247,7 @@ var Bookmarks = Module("bookmarks", {
getSuggestions: function getSuggestions(engineName, query, callback) {
const responseType = "application/x-suggestions+json";
- let engine = set.has(this.searchEngines, engineName) && this.searchEngines[engineName];
+ let engine = Set.has(this.searchEngines, engineName) && this.searchEngines[engineName];
if (engine && engine.supportsResponseType(responseType))
var queryURI = engine.getSubmission(query, responseType).uri.spec;
if (!queryURI)
@@ -274,7 +296,7 @@ var Bookmarks = Module("bookmarks", {
param = query.substr(offset + 1);
}
- var engine = set.has(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
+ var engine = Set.has(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
if (engine) {
if (engine.searchForm && !param)
return engine.searchForm;
@@ -382,7 +404,7 @@ var Bookmarks = Module("bookmarks", {
let frames = buffer.allFrames();
if (!args.bang)
return [
- [win.document.title, frames.length == 1 ? "Current Location" : "Frame: " + win.location.href]
+ [win.document.title, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.location.href]
for ([, win] in Iterator(frames))];
context.keys.text = "title";
context.keys.description = "url";
@@ -442,7 +464,7 @@ var Bookmarks = Module("bookmarks", {
context.title = ["Page URL"];
let frames = buffer.allFrames();
context.completions = [
- [win.document.documentURI, frames.length == 1 ? "Current Location" : "Frame: " + win.document.title]
+ [win.document.documentURI, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.document.title]
for ([, win] in Iterator(frames))];
return;
}
@@ -485,11 +507,11 @@ var Bookmarks = Module("bookmarks", {
"Delete a bookmark",
function (args) {
if (args.bang)
- commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
+ commandline.input(_("bookmark.prompt.deleteAll") + " ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i)) {
bookmarks.remove(Object.keys(bookmarkcache.bookmarks));
- dactyl.echomsg(_("bookmark.allGone"));
+ dactyl.echomsg(_("bookmark.allDeleted"));
}
});
else {
@@ -499,7 +521,7 @@ var Bookmarks = Module("bookmarks", {
let context = CompletionContext(args.join(" "));
context.fork("bookmark", 0, completion, "bookmark",
args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
- var deletedCount = bookmarks.remove(context.allItems.items.map(function (item) item.item.id));
+ deletedCount = bookmarks.remove(context.allItems.items.map(function (item) item.item.id));
}
dactyl.echomsg({ message: _("bookmark.deleted", deletedCount) });
@@ -604,7 +626,7 @@ var Bookmarks = Module("bookmarks", {
if (item && item.url.indexOf("%s") > -1)
context.fork("keyword/" + keyword, keyword.length + space.length, null, function (context) {
context.format = history.format;
- context.title = [keyword + " Quick Search"];
+ context.title = [/*L*/keyword + " Quick Search"];
// context.background = true;
context.compare = CompletionContext.Sort.unsorted;
context.generate = function () {
@@ -649,7 +671,7 @@ var Bookmarks = Module("bookmarks", {
return;
let ctxt = context.fork(name, 0);
- ctxt.title = [engine.description + " Suggestions"];
+ ctxt.title = [/*L*/engine.description + " Suggestions"];
ctxt.keys = { text: util.identity, description: function () "" };
ctxt.compare = CompletionContext.Sort.unsorted;
ctxt.filterFunc = null;
diff --git a/common/content/browser.js b/common/content/browser.js
--- a/common/content/browser.js
+++ b/common/content/browser.js
@@ -28,8 +28,11 @@ var Browser = Module("browser", XPCOM(Ci
"content-document-global-created": function (win, uri) {
let top = util.topWindow(win);
- if (top == window)
- this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri);
+ if (uri == "null")
+ uri = null;
+
+ if (top == window && (win.location.href || uri))
+ this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href || uri);
}
},
@@ -43,12 +46,18 @@ var Browser = Module("browser", XPCOM(Ci
title: doc.title
};
- if (dactyl.has("tabs")) {
+ if (!dactyl.has("tabs"))
+ update(args, { doc: doc, win: doc.defaultView });
+ else {
args.tab = tabs.getContentIndex(doc) + 1;
args.doc = {
valueOf: function () doc,
toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument"
};
+ args.win = {
+ valueOf: function () doc.defaultView,
+ toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentWindow"
+ };
}
autocommands.trigger(name, args);
@@ -95,26 +104,29 @@ var Browser = Module("browser", XPCOM(Ci
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)) {
+ const L = Ci.nsIWebProgressListener;
+
+ if (request)
dactyl.applyTriggerObserver("browser.stateChange", arguments);
+
+ if (flags & (L.STATE_IS_DOCUMENT | L.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) {
+ if (flags & L.STATE_START) {
while (document.commandDispatcher.focusedWindow == webProgress.DOMWindow
&& modes.have(modes.INPUT))
modes.pop();
}
- else if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
+ else if (flags & L.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();
}
}
+
+ onStateChange.superapply(this, arguments);
}),
onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) {
onSecurityChange.superapply(this, arguments);
@@ -200,9 +212,13 @@ var Browser = Module("browser", XPCOM(Ci
["o"], "Open one or more URLs",
function () { CommandExMode().open("open "); });
+ function decode(uri) util.losslessDecodeURI(uri)
+ .replace(/%20(?!(?:%20)*$)/g, " ")
+ .replace(RegExp(options["urlseparator"], "g"), encodeURIComponent);
+
mappings.add([modes.NORMAL], ["O"],
"Open one or more URLs, based on current location",
- function () { CommandExMode().open("open " + buffer.uri.spec); });
+ function () { CommandExMode().open("open " + decode(buffer.uri.spec)); });
mappings.add([modes.NORMAL], ["t"],
"Open one or more URLs in a new tab",
@@ -210,7 +226,7 @@ var Browser = Module("browser", XPCOM(Ci
mappings.add([modes.NORMAL], ["T"],
"Open one or more URLs in a new tab, based on current location",
- function () { CommandExMode().open("tabopen " + buffer.uri.spec); });
+ function () { CommandExMode().open("tabopen " + decode(buffer.uri.spec)); });
mappings.add([modes.NORMAL], ["w"],
"Open one or more URLs in a new window",
@@ -218,24 +234,24 @@ var Browser = Module("browser", XPCOM(Ci
mappings.add([modes.NORMAL], ["W"],
"Open one or more URLs in a new window, based on current location",
- function () { CommandExMode().open("winopen " + buffer.uri.spec); });
+ function () { CommandExMode().open("winopen " + decode(buffer.uri.spec)); });
- mappings.add([modes.NORMAL], ["~"],
+ mappings.add([modes.NORMAL], ["<open-home-directory>", "~"],
"Open home directory",
function () { dactyl.open("~"); });
- mappings.add([modes.NORMAL], ["gh"],
+ mappings.add([modes.NORMAL], ["<open-homepage>", "gh"],
"Open homepage",
function () { BrowserHome(); });
- mappings.add([modes.NORMAL], ["gH"],
+ mappings.add([modes.NORMAL], ["<tab-open-homepage>", "gH"],
"Open homepage in a new tab",
function () {
let homepages = gHomeButton.getHomePage();
dactyl.open(homepages, { from: "homepage", where: dactyl.NEW_TAB });
});
- mappings.add([modes.MAIN], ["<C-l>"],
+ mappings.add([modes.MAIN], ["<redraw-screen>", "<C-l>"],
"Redraw the screen",
function () { ex.redraw(); });
}
diff --git a/common/content/buffer.js b/common/content/buffer.js
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -19,6 +19,24 @@ var Buffer = Module("buffer", {
this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {};
+ this.addPageInfoSection("e", "Search Engines", function (verbose) {
+
+ let n = 1;
+ let nEngines = 0;
+ for (let { document: doc } in values(buffer.allFrames())) {
+ let engines = util.evaluateXPath(["link[@href and @rel='search' and @type='application/opensearchdescription+xml']"], doc);
+ nEngines += engines.snapshotLength;
+
+ if (verbose)
+ for (let link in engines)
+ yield [link.title || /*L*/ "Engine " + n++,
+ <a xmlns={XHTML} href={link.href} onclick="if (event.button == 0) { window.external.AddSearchProvider(this.href); return false; }" highlight="URL">{link.href}</a>];
+ }
+
+ if (!verbose && nEngines)
+ yield nEngines + /*L*/" engine" + (nEngines > 1 ? "s" : "");
+ });
+
this.addPageInfoSection("f", "Feeds", function (verbose) {
const feedTypes = {
"application/rss+xml": "RSS",
@@ -75,7 +93,7 @@ var Buffer = Module("buffer", {
}
if (!verbose && nFeed)
- yield nFeed + " feed" + (nFeed > 1 ? "s" : "");
+ yield nFeed + /*L*/" feed" + (nFeed > 1 ? "s" : "");
});
this.addPageInfoSection("g", "General Info", function (verbose) {
@@ -110,7 +128,7 @@ var Buffer = Module("buffer", {
if (!verbose) {
if (pageSize[0])
- yield (pageSize[1] || pageSize[0]) + " bytes";
+ yield (pageSize[1] || pageSize[0]) + /*L*/" bytes";
yield lastMod;
return;
}
@@ -134,6 +152,9 @@ var Buffer = Module("buffer", {
});
this.addPageInfoSection("m", "Meta Tags", function (verbose) {
+ if (!verbose)
+ return [];
+
// get meta tag data, sort and put into pageMeta[]
let metaNodes = buffer.focusedFrame.document.getElementsByTagName("meta");
@@ -141,9 +162,48 @@ var Buffer = Module("buffer", {
.sort(function (a, b) util.compareIgnoreCase(a[0], b[0]));
});
+ let identity = window.gIdentityHandler;
+ this.addPageInfoSection("s", "Security", function (verbose) {
+ if (!verbose || !identity)
+ return; // For now
+
+ // Modified from Firefox
+ function location(data) array.compact([
+ data.city, data.state, data.country
+ ]).join(", ");
+
+ switch (statusline.security) {
+ case "secure":
+ case "extended":
+ var data = identity.getIdentityData();
+
+ yield ["Host", identity.getEffectiveHost()];
+
+ if (statusline.security === "extended")
+ yield ["Owner", data.subjectOrg];
+ else
+ yield ["Owner", _("pageinfo.s.ownerUnverified", data.subjectOrg)];
+
+ if (location(data).length)
+ yield ["Location", location(data)];
+
+ yield ["Verified by", data.caOrg];
+
+ if (identity._overrideService.hasMatchingOverride(identity._lastLocation.hostname,
+ (identity._lastLocation.port || 443),
+ data.cert, {}, {}))
+ yield ["User exception", /*L*/"true"];
+ break;
+ }
+ });
+
dactyl.commands["buffer.viewSource"] = function (event) {
let elem = event.originalTarget;
- buffer.viewSource([elem.getAttribute("href"), Number(elem.getAttribute("line"))]);
+ let obj = { url: elem.getAttribute("href"), line: Number(elem.getAttribute("line")) };
+ if (elem.hasAttribute("column"))
+ obj.column = elem.getAttribute("column");
+
+ buffer.viewSource(obj);
};
},
@@ -311,7 +371,7 @@ var Buffer = Module("buffer", {
allFrames: function allFrames(win, focusedFirst) {
let frames = [];
(function rec(frame) {
- if (frame.document.body instanceof HTMLBodyElement)
+ if (true || frame.document.body instanceof HTMLBodyElement)
frames.push(frame);
Array.forEach(frame.frames, rec);
})(win || content);
@@ -340,7 +400,7 @@ var Buffer = Module("buffer", {
* @returns {string}
*/
get currentWord() Buffer.currentWord(this.focusedFrame),
- getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() this.currentWord),
+ getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() Buffer.currentWord(this.focusedFrame, true)),
/**
* Returns true if a scripts are allowed to focus the given input
@@ -352,8 +412,16 @@ var Buffer = Module("buffer", {
focusAllowed: function focusAllowed(elem) {
if (elem instanceof Window && !Editor.getEditor(elem))
return true;
+
let doc = elem.ownerDocument || elem.document || elem;
- return !options["strictfocus"] || doc.dactylFocusAllowed;
+ switch (options.get("strictfocus").getKey(doc.documentURIObject || util.newURI(doc.documentURI), "moderate")) {
+ case "despotic":
+ return elem.dactylFocusAllowed || elem.frameElement && elem.frameElement.dactylFocusAllowed;
+ case "moderate":
+ return doc.dactylFocusAllowed || elem.frameElement && elem.frameElement.ownerDocument.dactylFocusAllowed;
+ default:
+ return true;
+ }
},
/**
@@ -365,6 +433,7 @@ var Buffer = Module("buffer", {
*/
focusElement: function focusElement(elem) {
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+ elem.dactylFocusAllowed = true;
win.document.dactylFocusAllowed = true;
if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
@@ -413,7 +482,7 @@ var Buffer = Module("buffer", {
},
/**
- * Find the counth last link on a page matching one of the given
+ * Find the *count*th last link on a page matching one of the given
* regular expressions, or with a @rel or @rev attribute matching
* the given relation. Each frame is searched beginning with the
* last link and progressing to the first, once checking for
@@ -483,7 +552,7 @@ var Buffer = Module("buffer", {
*/
followLink: function followLink(elem, where) {
let doc = elem.ownerDocument;
- let view = doc.defaultView;
+ let win = doc.defaultView;
let { left: offsetX, top: offsetY } = elem.getBoundingClientRect();
if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
@@ -526,6 +595,8 @@ var Buffer = Module("buffer", {
ctrlKey: ctrlKey, shiftKey: shiftKey, metaKey: ctrlKey
}));
});
+ let sel = util.selectionController(win);
+ sel.getSelection(sel.SELECTION_FOCUS_REGION).collapseToStart();
});
},
@@ -533,9 +604,7 @@ var Buffer = Module("buffer", {
* @property {nsISelectionController} The current document's selection
* controller.
*/
- get selectionController() config.browser.docShell
- .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay)
- .QueryInterface(Ci.nsISelectionController),
+ get selectionController() util.selectionController(this.focusedFrame),
/**
* Opens the appropriate context menu for *elem*.
@@ -561,7 +630,7 @@ var Buffer = Module("buffer", {
try {
window.urlSecurityCheck(uri.spec, doc.nodePrincipal);
- io.CommandFileMode("Save link: ", {
+ io.CommandFileMode(_("buffer.prompt.saveLink") + " ", {
onSubmit: function (path) {
let file = io.File(path);
if (file.exists() && file.isDirectory())
@@ -598,16 +667,16 @@ var Buffer = Module("buffer", {
| persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
let downloadListener = new window.DownloadListener(window,
- services.Transfer(uri, services.io.newFileURI(file), "",
+ services.Transfer(uri, File(file).URI, "",
null, null, null, persist));
persist.progressListener = update(Object.create(downloadListener), {
- onStateChange: function onStateChange(progress, request, flag, status) {
- if (callback && (flag & Ci.nsIWebProgressListener.STATE_STOP) && status == 0)
- dactyl.trapErrors(callback, self, uri, file, progress, request, flag, status);
+ onStateChange: util.wrapCallback(function onStateChange(progress, request, flags, status) {
+ if (callback && (flags & Ci.nsIWebProgressListener.STATE_STOP) && status == 0)
+ dactyl.trapErrors(callback, self, uri, file, progress, request, flags, status);
return onStateChange.superapply(this, arguments);
- }
+ })
});
persist.saveURI(uri, null, null, null, null, file);
@@ -672,7 +741,7 @@ var Buffer = Module("buffer", {
*/
findScrollable: function findScrollable(dir, horizontal) {
function find(elem) {
- while (!(elem instanceof Element) && elem.parentNode)
+ while (elem && !(elem instanceof Element) && elem.parentNode)
elem = elem.parentNode;
for (; elem && elem.parentNode instanceof Element; elem = elem.parentNode)
if (Buffer.isScrollable(elem, dir, horizontal))
@@ -702,7 +771,7 @@ var Buffer = Module("buffer", {
doc.documentElement);
}
let doc = this.focusedFrame.document;
- return elem || doc.body || doc.documentElement;
+ return dactyl.assert(elem || doc.body || doc.documentElement);
},
/**
@@ -728,6 +797,37 @@ var Buffer = Module("buffer", {
return win;
},
+ /**
+ * Finds the next visible element for the node path in 'jumptags'
+ * for *arg*.
+ *
+ * @param {string} arg The element in 'jumptags' to use for the search.
+ * @param {number} count The number of elements to jump.
+ * @optional
+ * @param {boolean} reverse If true, search backwards. @optional
+ */
+ findJump: function findJump(arg, count, reverse) {
+ const FUDGE = 10;
+
+ let path = options["jumptags"][arg];
+ dactyl.assert(path, _("error.invalidArgument", arg));
+
+ let distance = reverse ? function (rect) -rect.top : function (rect) rect.top;
+ let elems = [[e, distance(e.getBoundingClientRect())] for (e in path.matcher(this.focusedFrame.document))]
+ .filter(function (e) e[1] > FUDGE)
+ .sort(function (a, b) a[1] - b[1])
+
+ let idx = Math.min((count || 1) - 1, elems.length);
+ dactyl.assert(idx in elems);
+
+ let elem = elems[idx][0];
+ elem.scrollIntoView(true);
+
+ let sel = elem.ownerDocument.defaultView.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(RangeFind.endpoint(RangeFind.nodeRange(elem), true));
+ },
+
// TODO: allow callback for filtering out unwanted frames? User defined?
/**
* Shifts the focus to another frame within the buffer. Each buffer
@@ -785,7 +885,7 @@ var Buffer = Module("buffer", {
* @param {Node} elem The element to query.
*/
showElementInfo: function showElementInfo(elem) {
- dactyl.echo(<>Element:<br/>{util.objectToString(elem, true)}</>, commandline.FORCE_MULTILINE);
+ dactyl.echo(<><!--L-->Element:<br/>{util.objectToString(elem, true)}</>, commandline.FORCE_MULTILINE);
},
/**
@@ -798,15 +898,15 @@ var Buffer = Module("buffer", {
showPageInfo: function showPageInfo(verbose, sections) {
// Ctrl-g single line output
if (!verbose) {
- let file = content.location.pathname.split("/").pop() || "[No Name]";
- let title = content.document.title || "[No Title]";
+ let file = content.location.pathname.split("/").pop() || _("buffer.noName");
+ let title = content.document.title || _("buffer.noTitle");
- let info = template.map("gf",
+ let info = template.map(sections || options["pageinfo"],
function (opt) template.map(buffer.pageInfo[opt].action(), util.identity, ", "),
", ");
if (bookmarkcache.isBookmarked(this.URL))
- info += ", bookmarked";
+ info += ", " + _("buffer.bookmarked");
let pageInfoText = <>{file.quote()} [{info}] {title}</>;
dactyl.echo(pageInfoText, commandline.FORCE_SINGLELINE);
@@ -852,26 +952,34 @@ var Buffer = Module("buffer", {
* specified *url*. Either the default viewer or the configured external
* editor is used.
*
- * @param {string} url The URL of the source.
+ * @param {string|object|null} loc If a string, the URL of the source,
+ * otherwise an object with some or all of the following properties:
+ *
+ * url: The URL to view.
+ * doc: The document to view.
+ * line: The line to select.
+ * column: The column to select.
+ *
+ * If no URL is provided, the current document is used.
* @default The current buffer.
* @param {boolean} useExternalEditor View the source in the external editor.
*/
- viewSource: function viewSource(url, useExternalEditor) {
+ viewSource: function viewSource(loc, useExternalEditor) {
let doc = this.focusedFrame.document;
- if (isArray(url)) {
- if (options.get("editor").has("line"))
- this.viewSourceExternally(url[0] || doc, url[1]);
+ if (isObject(loc)) {
+ if (options.get("editor").has("line") || !loc.url)
+ this.viewSourceExternally(loc.doc || loc.url || doc, loc);
else
window.openDialog("chrome://global/content/viewSource.xul",
"_blank", "all,dialog=no",
- url[0], null, null, url[1]);
+ loc.url, null, null, loc.line);
}
else {
if (useExternalEditor)
- this.viewSourceExternally(url || doc);
+ this.viewSourceExternally(loc || doc);
else {
- url = url || doc.location.href;
+ let url = loc || doc.location.href;
const PREFIX = "view-source:";
if (url.indexOf(PREFIX) == 0)
url = url.substr(PREFIX.length);
@@ -894,13 +1002,19 @@ var Buffer = Module("buffer", {
* immediately.
*
* @param {Document} doc The document to view.
+ * @param {function|object} callback If a function, the callback to be
+ * called with two arguments: the nsIFile of the file, and temp, a
+ * boolean which is true if the file is temporary. Otherwise, an object
+ * with line and column properties used to determine where to open the
+ * source.
+ * @optional
*/
viewSourceExternally: Class("viewSourceExternally",
XPCOM([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), {
init: function init(doc, callback) {
this.callback = callable(callback) ? callback :
function (file, temp) {
- editor.editFileExternally({ file: file.path, line: callback },
+ editor.editFileExternally(update({ file: file.path }, callback || {}),
function () { temp && file.remove(false); });
return true;
};
@@ -928,8 +1042,8 @@ var Buffer = Module("buffer", {
return null;
},
- onStateChange: function onStateChange(progress, request, flag, status) {
- if ((flag & this.STATE_STOP) && status == 0) {
+ onStateChange: function onStateChange(progress, request, flags, status) {
+ if ((flags & this.STATE_STOP) && status == 0) {
try {
var ok = this.callback(this.file, true);
}
@@ -997,14 +1111,14 @@ var Buffer = Module("buffer", {
* Adjusts the page zoom of the current buffer relative to the
* current zoom level.
*
- * @param {number} steps The integral number of natural fractions by
- * which to adjust the current page zoom. If positive, the zoom
- * level is increased, if negative it is decreased.
+ * @param {number} steps The integral number of natural fractions by which
+ * to adjust the current page zoom. If positive, the zoom level is
+ * increased, if negative it is decreased.
* @param {boolean} fullZoom If true, zoom all content of the page,
- * including raster images. If false, zoom only text. If omitted,
- * use the current zoom function. @optional
- * @throws {FailedAssertion} if the buffer's zoom level is already
- * at its extreme in the given direction.
+ * including raster images. If false, zoom only text. If omitted, use
+ * the current zoom function. @optional
+ * @throws {FailedAssertion} if the buffer's zoom level is already at its
+ * extreme in the given direction.
*/
bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) {
if (fullZoom === undefined)
@@ -1019,7 +1133,7 @@ var Buffer = Module("buffer", {
this.setZoom(Math.round(values[i] * 100), fullZoom);
},
- getAllFrames: deprecated("buffer.allFrames", function getAllFrames() buffer.getAllFrames.apply(buffer, arguments)),
+ getAllFrames: deprecated("buffer.allFrames", "allFrames"),
scrollTop: deprecated("buffer.scrollToPercent", function scrollTop() buffer.scrollToPercent(null, 0)),
scrollBottom: deprecated("buffer.scrollToPercent", function scrollBottom() buffer.scrollToPercent(null, 100)),
scrollStart: deprecated("buffer.scrollToPercent", function scrollStart() buffer.scrollToPercent(0, null)),
@@ -1027,7 +1141,7 @@ var Buffer = Module("buffer", {
scrollColumns: deprecated("buffer.scrollHorizontal", function scrollColumns(cols) buffer.scrollHorizontal("columns", cols)),
scrollPages: deprecated("buffer.scrollHorizontal", function scrollPages(pages) buffer.scrollVertical("pages", pages)),
scrollTo: deprecated("Buffer.scrollTo", function scrollTo(x, y) content.scrollTo(x, y)),
- textZoom: deprecated("buffer.zoomValue and buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100)
+ textZoom: deprecated("buffer.zoomValue/buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100)
}, {
PageInfo: Struct("PageInfo", "name", "title", "action")
.localize("title"),
@@ -1044,17 +1158,21 @@ var Buffer = Module("buffer", {
*
* @returns {string}
*/
- currentWord: function currentWord(win) {
+ currentWord: function currentWord(win, select) {
let selection = win.getSelection();
if (selection.rangeCount == 0)
return "";
let range = selection.getRangeAt(0).cloneRange();
- if (range.collapsed) {
+ if (range.collapsed && range.startContainer instanceof Text) {
let re = options.get("iskeyword").regexp;
Editor.extendRange(range, true, re, true);
Editor.extendRange(range, false, re, true);
}
+ if (select) {
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
return util.domToString(range);
},
@@ -1079,13 +1197,13 @@ var Buffer = Module("buffer", {
var names = [];
if (node.title)
- names.push([node.title, "Page Name"]);
+ names.push([node.title, /*L*/"Page Name"]);
if (node.alt)
- names.push([node.alt, "Alternate Text"]);
+ names.push([node.alt, /*L*/"Alternate Text"]);
if (!isinstance(node, Document) && node.textContent)
- names.push([node.textContent, "Link Text"]);
+ names.push([node.textContent, /*L*/"Link Text"]);
names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]);
@@ -1133,6 +1251,11 @@ var Buffer = Module("buffer", {
elem.scrollLeft = left;
if (top != null)
elem.scrollTop = top;
+
+ if (util.haveGecko("2.0") && !util.haveGecko("7.*"))
+ elem.ownerDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+ .redraw();
},
/**
@@ -1156,10 +1279,11 @@ var Buffer = Module("buffer", {
else
throw Error();
+ dactyl.assert(number < 0 ? elem.scrollLeft > 0 : elem.scrollLeft < elem.scrollWidth - elem.clientWidth);
+
let left = elem.dactylScrollDestX !== undefined ? elem.dactylScrollDestX : elem.scrollLeft;
elem.dactylScrollDestX = undefined;
- dactyl.assert(number < 0 ? left > 0 : left < elem.scrollWidth - elem.clientWidth);
Buffer.scrollTo(elem, left + number * increment, null);
},
@@ -1184,10 +1308,11 @@ var Buffer = Module("buffer", {
else
throw Error();
+ dactyl.assert(number < 0 ? elem.scrollTop > 0 : elem.scrollTop < elem.scrollHeight - elem.clientHeight);
+
let top = elem.dactylScrollDestY !== undefined ? elem.dactylScrollDestY : elem.scrollTop;
elem.dactylScrollDestY = undefined;
- dactyl.assert(number < 0 ? top > 0 : top < elem.scrollHeight - elem.clientHeight);
Buffer.scrollTo(elem, null, top + number * increment);
},
@@ -1212,7 +1337,7 @@ var Buffer = Module("buffer", {
},
openUploadPrompt: function openUploadPrompt(elem) {
- io.CommandFileMode("Upload file: ", {
+ io.CommandFileMode(_("buffer.prompt.uploadFile") + " ", {
onSubmit: function onSubmit(path) {
let file = io.File(path);
dactyl.assert(file.exists());
@@ -1238,12 +1363,21 @@ var Buffer = Module("buffer", {
// FIXME: arg handling is a bit of a mess, check for filename
dactyl.assert(!arg || arg[0] == ">" && !util.OS.isWindows,
- _("error.trailing"));
+ _("error.trailingCharacters"));
+
+ const PRINTER = "PostScript/default";
+ const BRANCH = "print.printer_" + PRINTER + ".";
prefs.withContext(function () {
if (arg) {
- prefs.set("print.print_to_file", "true");
+ prefs.set("print.print_printer", PRINTER);
+
+ prefs.set( "print.print_to_file", true);
+ prefs.set(BRANCH + "print_to_file", true);
+
prefs.set("print.print_to_filename", io.File(arg.substr(1)).path);
+ prefs.set(BRANCH + "print_to_filename", io.File(arg.substr(1)).path);
+
dactyl.echomsg(_("print.toFile", arg.substr(1)));
}
else
@@ -1417,7 +1551,7 @@ var Buffer = Module("buffer", {
level = Math.constrain(level, Buffer.ZOOM_MIN, Buffer.ZOOM_MAX);
}
else
- dactyl.assert(false, _("error.trailing"));
+ dactyl.assert(false, _("error.trailingCharacters"));
buffer.setZoom(level, args.bang);
},
@@ -1434,21 +1568,24 @@ var Buffer = Module("buffer", {
let styles = iter([s.title, []] for (s in values(buffer.alternateStyleSheets))).toObject();
buffer.alternateStyleSheets.forEach(function (style) {
- styles[style.title].push(style.href || "inline");
+ styles[style.title].push(style.href || _("style.inline"));
});
context.completions = [[title, href.join(", ")] for ([title, href] in Iterator(styles))];
};
- completion.buffer = function buffer(context) {
+ completion.buffer = function buffer(context, visible) {
let filter = context.filter.toLowerCase();
+
let defItem = { parent: { getTitle: function () "" } };
+
let tabGroups = {};
tabs.getGroups();
- tabs.allTabs.forEach(function (tab, i) {
+ tabs[visible ? "visibleTabs" : "allTabs"].forEach(function (tab, i) {
let group = (tab.tabItem || tab._tabViewTabItem || defItem).parent || defItem.parent;
- if (!set.has(tabGroups, group.id))
+ if (!Set.has(tabGroups, group.id))
tabGroups[group.id] = [group.getTitle(), []];
+
group = tabGroups[group.id];
group[1].push([i, tab.linkedBrowser]);
});
@@ -1470,7 +1607,7 @@ var Buffer = Module("buffer", {
command: function () "tabs.select"
};
context.compare = CompletionContext.Sort.number;
- context.filters = [CompletionContext.Filter.textDescription];
+ context.filters[0] = CompletionContext.Filter.textDescription;
for (let [id, vals] in Iterator(tabGroups))
context.fork(id, 0, this, function (context, [name, browsers]) {
@@ -1483,12 +1620,12 @@ var Buffer = Module("buffer", {
else if (i == tabs.index(tabs.alternate))
indicator = "#";
- let tab = tabs.getTab(i);
+ let tab = tabs.getTab(i, visible);
let url = browser.contentDocument.location.href;
i = i + 1;
return {
- text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url],
+ text: [i + ": " + (tab.label || /*L*/"(Untitled)"), i + ": " + url],
tab: tab,
id: i - 1,
url: url,
@@ -1514,21 +1651,21 @@ var Buffer = Module("buffer", {
function () { dactyl.clipboardWrite(buffer.uri.spec, true); });
mappings.add([modes.NORMAL],
- ["<C-a>"], "Increment last number in URL",
+ ["<C-a>", "<increment-url-path>"], "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",
+ ["<C-x>", "<decrement-url-path>"], "Decrement last number in URL",
function (args) { buffer.incrementURL(-Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.NORMAL], ["gu"],
+ mappings.add([modes.NORMAL], ["gu", "<open-parent-path>"],
"Go to parent directory",
function (args) { buffer.climbUrlPath(Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.NORMAL], ["gU"],
+ mappings.add([modes.NORMAL], ["gU", "<open-root-path>"],
"Go to the root of the website",
function () { buffer.climbUrlPath(-1); });
@@ -1542,11 +1679,11 @@ var Buffer = Module("buffer", {
},
{ count: true });
- mappings.add([modes.COMMAND], ["i", "<Insert>"],
- "Start caret mode",
+ mappings.add([modes.NORMAL], ["i", "<Insert>"],
+ "Start Caret mode",
function () { modes.push(modes.CARET); });
- mappings.add([modes.COMMAND], ["<C-c>"],
+ mappings.add([modes.NORMAL], ["<C-c>", "<stop-load>"],
"Stop loading the current web page",
function () { ex.stop(); });
@@ -1579,12 +1716,12 @@ var Buffer = Module("buffer", {
"Scroll to the absolute right of the document",
function () { buffer.scrollToPercent(100, null); });
- mappings.add([modes.COMMAND], ["gg", "<Home>"],
+ mappings.add([modes.COMMAND], ["gg", "<Home>", "<scroll-top>"],
"Go to the top of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 0); },
{ count: true });
- mappings.add([modes.COMMAND], ["G", "<End>"],
+ mappings.add([modes.COMMAND], ["G", "<End>", "<scroll-bottom>"],
"Go to the end of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
{ count: true });
@@ -1607,55 +1744,84 @@ var Buffer = Module("buffer", {
function (args) { buffer._scrollByScrollSize(args.count, false); },
{ count: true });
- mappings.add([modes.COMMAND], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-page-up>"],
+ mappings.add([modes.COMMAND], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-up-page>"],
"Scroll up a full page",
function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.COMMAND], ["<C-f>", "<PageDown>", "<Space>", "<scroll-page-down>"],
+ mappings.add([modes.COMMAND], ["<Space>"],
+ "Scroll down a full page",
+ function (args) {
+ if (isinstance(content.document.activeElement, [HTMLInputElement, HTMLButtonElement]))
+ return Events.PASS;
+ buffer.scrollVertical("pages", Math.max(args.count, 1));
+ },
+ { count: true });
+
+ mappings.add([modes.COMMAND], ["<C-f>", "<PageDown>", "<scroll-down-page>"],
"Scroll down a full page",
function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.COMMAND], ["]f", "<previous-frame>"],
+ mappings.add([modes.NORMAL], ["]f", "<previous-frame>"],
"Focus next frame",
function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.COMMAND], ["[f", "<next-frame>"],
+ mappings.add([modes.NORMAL], ["[f", "<next-frame>"],
"Focus previous frame",
function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); },
{ count: true });
- mappings.add([modes.COMMAND], ["]]", "<next-page>"],
+ mappings.add([modes.NORMAL], ["["],
+ "Jump to the previous element as defined by 'jumptags'",
+ function (args) { buffer.findJump(args.arg, args.count, true); },
+ { arg: true, count: true });
+
+ mappings.add([modes.NORMAL], ["]"],
+ "Jump to the next element as defined by 'jumptags'",
+ function (args) { buffer.findJump(args.arg, args.count, false); },
+ { arg: true, count: true });
+
+ mappings.add([modes.NORMAL], ["{"],
+ "Jump to the previous paragraph",
+ function (args) { buffer.findJump("p", args.count, true); },
+ { count: true });
+
+ mappings.add([modes.NORMAL], ["}"],
+ "Jump to the next paragraph",
+ function (args) { buffer.findJump("p", args.count, false); },
+ { count: true });
+
+ mappings.add([modes.NORMAL], ["]]", "<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([modes.COMMAND], ["[[", "<previous-page>"],
+ mappings.add([modes.NORMAL], ["[[", "<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([modes.COMMAND], ["gf", "<view-source>"],
+ mappings.add([modes.NORMAL], ["gf", "<view-source>"],
"Toggle between rendered and source view",
function () { buffer.viewSource(null, false); });
- mappings.add([modes.COMMAND], ["gF", "<view-source-externally>"],
+ mappings.add([modes.NORMAL], ["gF", "<view-source-externally>"],
"View source with an external editor",
function () { buffer.viewSource(null, true); });
- mappings.add([modes.COMMAND], ["gi", "<focus-input>"],
+ mappings.add([modes.NORMAL], ["gi", "<focus-input>"],
"Focus last used input field",
function (args) {
let elem = buffer.lastInputField;
if (args.count >= 1 || !elem || !events.isContentNode(elem)) {
- let xpath = ["frame", "iframe", "input", "textarea[not(@disabled) and not(@readonly)]"];
+ let xpath = ["frame", "iframe", "input", "xul:textbox", "textarea[not(@disabled) and not(@readonly)]"];
let frames = buffer.allFrames(null, true);
@@ -1664,13 +1830,14 @@ var Buffer = Module("buffer", {
if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
return Editor.getEditor(elem.contentWindow);
- if (elem.readOnly || elem instanceof HTMLInputElement && !set.has(util.editableInputs, elem.type))
+ if (elem.readOnly || elem instanceof HTMLInputElement && !Set.has(util.editableInputs, elem.type))
return false;
let computedStyle = util.computedStyle(elem);
let rect = elem.getBoundingClientRect();
return computedStyle.visibility != "hidden" && computedStyle.display != "none" &&
- computedStyle.MozUserFocus != "ignore" && rect.width && rect.height;
+ (elem instanceof Ci.nsIDOMXULTextBoxElement || computedStyle.MozUserFocus != "ignore") &&
+ rect.width && rect.height;
});
dactyl.assert(elements.length > 0);
@@ -1681,36 +1848,40 @@ var Buffer = Module("buffer", {
},
{ count: true });
- mappings.add([modes.COMMAND], ["gP"],
- "Open (]put) a URL based on the current clipboard contents in a new buffer",
- function () {
+ function url() {
let url = dactyl.clipboardRead();
dactyl.assert(url, _("error.clipboardEmpty"));
- dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true });
+
+ let proto = /^([-\w]+):/.exec(url);
+ if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc && !RegExp(options["urlseparator"]).test(url))
+ return url.replace(/\s+/g, "");
+ return url;
+ }
+
+ mappings.add([modes.NORMAL], ["gP"],
+ "Open (put) a URL based on the current clipboard contents in a new background buffer",
+ function () {
+ dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB, background: true });
});
- mappings.add([modes.COMMAND], ["p", "<MiddleMouse>", "<open-clipboard-url>"],
+ mappings.add([modes.NORMAL], ["p", "<MiddleMouse>", "<open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in the current buffer",
function () {
- let url = dactyl.clipboardRead();
- dactyl.assert(url, _("error.clipboardEmpty"));
- dactyl.open(url);
+ dactyl.open(url());
});
- mappings.add([modes.COMMAND], ["P", "<tab-open-clipboard-url>"],
+ mappings.add([modes.NORMAL], ["P", "<tab-open-clipboard-url>"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
- let url = dactyl.clipboardRead();
- dactyl.assert(url, _("error.clipboardEmpty"));
- dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB });
+ dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB });
});
// reloading
- mappings.add([modes.COMMAND], ["r", "<reload>"],
+ mappings.add([modes.NORMAL], ["r", "<reload>"],
"Reload the current web page",
function () { tabs.reload(tabs.getTab(), false); });
- mappings.add([modes.COMMAND], ["R", "<full-reload>"],
+ mappings.add([modes.NORMAL], ["R", "<full-reload>"],
"Reload while skipping the cache",
function () { tabs.reload(tabs.getTab(), true); });
@@ -1724,62 +1895,62 @@ var Buffer = Module("buffer", {
});
// zooming
- mappings.add([modes.COMMAND], ["zi", "+", "<text-zoom-in>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["zm", "<text-zoom-more>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["zo", "-", "<text-zoom-out>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["zr", "<text-zoom-reduce>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["zz", "<text-zoom>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["ZI", "zI", "<full-zoom-in>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["ZM", "zM", "<full-zoom-more>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["ZO", "zO", "<full-zoom-out>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["ZR", "zR", "<full-zoom-reduce>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["zZ", "<full-zoom>"],
+ mappings.add([modes.NORMAL], ["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([modes.COMMAND], ["<C-g>", "<page-info>"],
+ mappings.add([modes.NORMAL], ["<C-g>", "<page-info>"],
"Print the current file name",
function () { buffer.showPageInfo(false); });
- mappings.add([modes.COMMAND], ["g<C-g>", "<more-page-info>"],
+ mappings.add([modes.NORMAL], ["g<C-g>", "<more-page-info>"],
"Print file information",
function () { buffer.showPageInfo(true); });
},
@@ -1817,6 +1988,23 @@ var Buffer = Module("buffer", {
validator: function (value) RegExp(value)
});
+ options.add(["jumptags", "jt"],
+ "XPath or CSS selector strings of jumpable elements for extended hint modes",
+ "stringmap", {
+ "p": "p,table,ul,ol,blockquote",
+ "h": "h1,h2,h3,h4,h5,h6"
+ },
+ {
+ keepQuotes: true,
+ setter: function (vals) {
+ for (let [k, v] in Iterator(vals))
+ vals[k] = update(new String(v), { matcher: util.compileMatcher(Option.splitList(v)) });
+ return vals;
+ },
+ validator: function (value) util.validateMatcher.call(this, value)
+ && Object.keys(value).every(function (v) v.length == 1)
+ });
+
options.add(["nextpattern"],
"Patterns to use when guessing the next page in a document sequence",
"regexplist", UTF8("'\\bnext\\b',^>$,^(>>|»)$,^(>|»),(>|»)$,'\\bmore\\b'"),
@@ -1829,7 +2017,7 @@ var Buffer = Module("buffer", {
options.add(["pageinfo", "pa"],
"Define which sections are shown by the :pageinfo command",
- "charlist", "gfm",
+ "charlist", "gesfm",
{ get values() values(buffer.pageInfo).toObject() });
options.add(["scroll", "scr"],
diff --git a/common/content/commandline.js b/common/content/commandline.js
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -152,6 +152,7 @@ var CommandWidgets = Class("CommandWidge
return this.commandbar;
}
});
+ this.updateVisibility();
},
addElement: function addElement(obj) {
const self = this;
@@ -174,7 +175,7 @@ var CommandWidgets = Class("CommandWidge
if (obj.value != null)
return [obj.value[0],
obj.get ? obj.get.call(this, elem) : elem.value]
- .concat(obj.value.slice(2))
+ .concat(obj.value.slice(2));
return null;
},
@@ -300,19 +301,21 @@ var CommandWidgets = Class("CommandWidge
});
var CommandMode = Class("CommandMode", {
- init: function init() {
+ init: function CM_init() {
this.keepCommand = userContext.hidden_option_command_afterimage;
},
+ get autocomplete() options["autocomplete"].length,
+
get command() this.widgets.command[1],
set command(val) this.widgets.command = val,
get prompt() this.widgets.prompt,
set prompt(val) this.widgets.prompt = val,
- open: function (command) {
+ open: function CM_open(command) {
dactyl.assert(isinstance(this.mode, modes.COMMAND_LINE),
- "Not opening command line in non-command-line mode.");
+ /*L*/"Not opening command line in non-command-line mode.");
this.messageCount = commandline.messageCount;
modes.push(this.mode, this.extendedMode, this.closure);
@@ -340,7 +343,7 @@ var CommandMode = Class("CommandMode", {
get widgets() commandline.widgets,
- enter: function (stack) {
+ enter: function CM_enter(stack) {
commandline.commandSession = this;
if (stack.pop && commandline.command) {
this.onChange(commandline.command);
@@ -349,7 +352,7 @@ var CommandMode = Class("CommandMode", {
}
},
- leave: function (stack) {
+ leave: function CM_leave(stack) {
if (!stack.push) {
commandline.commandSession = null;
this.input.dactylKeyPress = undefined;
@@ -374,7 +377,7 @@ var CommandMode = Class("CommandMode", {
},
events: {
- input: function onInput(event) {
+ input: function CM_onInput(event) {
if (this.completions) {
this.resetCompletions();
@@ -382,7 +385,7 @@ var CommandMode = Class("CommandMode", {
}
this.onChange(commandline.command);
},
- keyup: function onKeyUp(event) {
+ keyup: function CM_onKeyUp(event) {
let key = events.toString(event);
if (/-?Tab>$/.test(key) && this.completions)
this.completions.tabTimer.flush();
@@ -391,23 +394,22 @@ var CommandMode = Class("CommandMode", {
keepCommand: false,
- onKeyPress: function onKeyPress(events) {
+ onKeyPress: function CM_onKeyPress(events) {
if (this.completions)
this.completions.previewClear();
return true; /* Pass event */
},
- onCancel: function (value) {
- },
+ onCancel: function (value) {},
- onChange: function (value) {
- },
+ onChange: function (value) {},
- onSubmit: function (value) {
- },
+ onHistory: function (value) {},
- resetCompletions: function resetCompletions() {
+ onSubmit: function (value) {},
+
+ resetCompletions: function CM_resetCompletions() {
if (this.completions) {
this.completions.context.cancelAll();
this.completions.wildIndex = -1;
@@ -426,12 +428,12 @@ var CommandExMode = Class("CommandExMode
prompt: ["Normal", ":"],
- complete: function complete(context) {
+ complete: function CEM_complete(context) {
context.fork("ex", 0, completion, "ex");
},
- onSubmit: function onSubmit(command) {
- contexts.withContext({ file: "[Command Line]", line: 1 },
+ onSubmit: function CEM_onSubmit(command) {
+ contexts.withContext({ file: /*L*/"[Command Line]", line: 1 },
function _onSubmit() {
io.withSavedValues(["readHeredoc"], function _onSubmit() {
this.readHeredoc = commandline.readHeredoc;
@@ -449,7 +451,7 @@ var CommandPromptMode = Class("CommandPr
init.supercall(this);
},
- complete: function (context) {
+ complete: function CPM_complete(context) {
if (this.completer)
context.forkapply("prompt", 0, this, "completer", Array.slice(arguments, 1));
},
@@ -586,6 +588,9 @@ var CommandLine = Module("commandline",
get completionList() {
let node = this.widgets.active.commandline;
+ if (this.commandSession && this.commandSession.completionList)
+ node = document.getElementById(this.commandSession.completionList);
+
if (!node.completionList) {
let elem = document.getElementById("dactyl-completions-" + node.id);
util.waitFor(bind(this.widgets._ready, null, elem));
@@ -648,10 +653,10 @@ var CommandLine = Module("commandline",
* @param {XML} xml The output as an E4X XML object.
*/
commandOutput: function commandOutput(xml) {
- XML.ignoreWhitespace = false;
- XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
if (this.command)
- this.echo(<>:{this.command}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
+ this.echo(<><div xmlns={XHTML}>:{this.command}</div>
{xml}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
+ else
this.echo(xml, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
this.command = null;
},
@@ -722,6 +727,9 @@ var CommandLine = Module("commandline",
if (flags & this.APPEND_TO_MESSAGES) {
let message = isObject(data) ? data : { message: data };
+
+ // Make sure the memoized message property is an instance property.
+ message.message;
this._messageHistory.add(update({ highlight: highlightGroup }, message));
data = message.message;
}
@@ -737,7 +745,7 @@ var CommandLine = Module("commandline",
let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine;
- if ((flags & this.FORCE_MULTILINE) || (/\n/.test(data) || !isString(data)) && !(flags & this.FORCE_SINGLELINE))
+ if ((flags & this.FORCE_MULTILINE) || (/\n/.test(data) || !isinstance(data, [_, "String"])) && !(flags & this.FORCE_SINGLELINE))
action = mow.closure.echo;
if (single)
@@ -803,12 +811,20 @@ var CommandLine = Module("commandline",
// FIXME: Buggy, especially when pasting.
inputMultiline: function inputMultiline(end, callback) {
let cmd = this.command;
- modes.push(modes.INPUT_MULTILINE, null, {
- mappingSelf: {
+ let self = {
end: "\n" + end + "\n",
callback: callback
- }
+ };
+
+ modes.push(modes.INPUT_MULTILINE, null, {
+ holdFocus: true,
+ leave: function leave() {
+ if (!self.done)
+ self.callback(null);
+ },
+ mappingSelf: self
});
+
if (cmd != false)
this._echoLine(cmd, this.HL_NORMAL);
@@ -838,7 +854,7 @@ var CommandLine = Module("commandline",
event.target.blur();
dactyl.beep();
}
- },
+ }
}
),
@@ -940,6 +956,7 @@ var CommandLine = Module("commandline",
if (this.completions)
this.completions.previewClear();
this.input.value = val;
+ this.session.onHistory(val);
},
/**
@@ -1014,9 +1031,9 @@ var CommandLine = Module("commandline",
dactyl.registerObserver("events.doneFeeding", this.closure.onDoneFeeding, true);
this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
- if (events.feedingKeys)
+ if (events.feedingKeys && !tabPressed)
this.ignoredCount++;
- if (options["autocomplete"].length) {
+ else if (this.session.autocomplete) {
this.itemList.visible = true;
this.complete(true, false);
}
@@ -1086,6 +1103,7 @@ var CommandLine = Module("commandline",
get wildtype() this.wildtypes[this.wildIndex] || "",
complete: function complete(show, tabPressed) {
+ this.session.ignoredCount = 0;
this.context.reset();
this.context.tabPressed = tabPressed;
this.session.complete(this.context);
@@ -1230,7 +1248,7 @@ var CommandLine = Module("commandline",
for (let [, context] in Iterator(list)) {
let done = function done() !(idx >= n + context.items.length || idx == -2 && !context.items.length);
- util.waitFor(function () !context.incomplete || done())
+ util.waitFor(function () !context.incomplete || done());
if (done())
break;
@@ -1259,6 +1277,7 @@ var CommandLine = Module("commandline",
tab: function tab(reverse, wildmode) {
this.autocompleteTimer.flush();
+ this.ignoredCount = 0;
if (this._caret != this.caret)
this.reset();
@@ -1301,7 +1320,7 @@ var CommandLine = Module("commandline",
if (this.selected == null)
statusline.progress = "";
else
- statusline.progress = "match " + (this.selected + 1) + " of " + this.items.length;
+ statusline.progress = _("completion.matchIndex", this.selected + 1, this.items.length);
}
if (this.items.length == 0)
@@ -1415,7 +1434,7 @@ var CommandLine = Module("commandline",
mappings: function init_mappings() {
mappings.add([modes.COMMAND],
- [":"], "Enter command-line mode",
+ [":"], "Enter Command Line mode",
function () { CommandExMode().open(""); });
mappings.add([modes.INPUT_MULTILINE],
@@ -1427,6 +1446,7 @@ var CommandLine = Module("commandline",
let index = text.indexOf(self.end);
if (index >= 0) {
+ self.done = true;
text = text.substring(1, index);
modes.pop();
@@ -1614,7 +1634,7 @@ var ItemList = Class("ItemList", {
_init: function _init() {
this._div = this._dom(
<div class="ex-command-output" highlight="Normal" style="white-space: nowrap">
- <div highlight="Completions" key="noCompletions"><span highlight="Title">No Completions</span></div>
+ <div highlight="Completions" key="noCompletions"><span highlight="Title">{_("completion.noCompletions")}</span></div>
<div key="completions"/>
<div highlight="Completions">
{
@@ -1812,7 +1832,7 @@ var ItemList = Class("ItemList", {
onKeyPress: function onKeyPress(event) false
}, {
- WAITING_MESSAGE: "Generating results..."
+ WAITING_MESSAGE: _("completion.generating")
});
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -12,9 +12,6 @@ default xml namespace = XHTML;
XML.ignoreWhitespace = false;
XML.prettyPrinting = false;
-var userContext = { __proto__: modules };
-var _userContext = newContext(userContext);
-
var EVAL_ERROR = "__dactyl_eval_error";
var EVAL_RESULT = "__dactyl_eval_result";
var EVAL_STRING = "__dactyl_eval_string";
@@ -41,9 +38,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
};
styles.registerSheet("resource://dactyl-skin/dactyl.css");
+
+ this.cleanups = [];
+ this.cleanups.push(util.overlayObject(window, {
+ focusAndSelectUrlBar: function focusAndSelectUrlBar() {
+ switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
+ case "laissez-faire":
+ if (!Events.isHidden(window.gURLBar, true))
+ return focusAndSelectUrlBar.superapply(this, arguments);
+ default:
+ // Evil. Ignore.
+ }
+ }
+ }));
},
cleanup: function () {
+ for (let cleanup in values(this.cleanups))
+ cleanup.call(this);
+
delete window.dactyl;
delete window.liberator;
@@ -57,21 +70,36 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
autocommands.trigger("Leave", {});
},
+ // initially hide all GUI elements, they are later restored unless the user
+ // has :set go= or something similar in his config
+ hideGUI: function () {
+ let guioptions = config.guioptions;
+ for (let option in guioptions) {
+ guioptions[option].forEach(function (elem) {
+ try {
+ document.getElementById(elem).collapsed = true;
+ }
+ catch (e) {}
+ });
+ }
+ },
+
+
observers: {
- "dactyl-cleanup": function dactyl_cleanup() {
+ "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
let modules = dactyl.modules;
for (let mod in values(modules.moduleList.reverse())) {
mod.stale = true;
if ("cleanup" in mod)
- this.trapErrors("cleanup", mod);
+ this.trapErrors("cleanup", mod, reason);
if ("destroy" in mod)
- this.trapErrors("destroy", mod);
+ this.trapErrors("destroy", mod, reason);
}
for (let mod in values(modules.ownPropertyValues.reverse()))
if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT)
- this.trapErrors(mod.cleanup, mod, dactyl, modules, window);
+ this.trapErrors(mod.cleanup, mod, dactyl, modules, window, reason);
for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
try {
@@ -98,7 +126,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}),
/**
- * @property {number} The current main mode.
+ * @property {Modes.Mode} The current main mode.
* @see modes#mainModes
*/
mode: deprecated("modes.main", {
@@ -106,7 +134,37 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
set: function mode(val) modes.main = val
}),
- get menuItems() Dactyl.getMenuItems(),
+ get menuItems() {
+ function dispatch(node, name) {
+ let event = node.ownerDocument.createEvent("Events");
+ event.initEvent(name, false, false);
+ node.dispatchEvent(event);
+ }
+
+ function addChildren(node, parent) {
+ if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
+ dispatch(node, "popupshowing");
+
+ for (let [, item] in Iterator(node.childNodes)) {
+ if (item.childNodes.length == 0 && item.localName == "menuitem"
+ && !item.hidden
+ && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
+ item.dactylPath = parent + item.getAttribute("label");
+ items.push(item);
+ }
+ else {
+ let path = parent;
+ if (item.localName == "menu")
+ path += item.getAttribute("label") + ".";
+ addChildren(item, path);
+ }
+ }
+ }
+
+ let items = [];
+ addChildren(document.getElementById(config.guioptions["m"][1]), "");
+ return items;
+ },
// Global constants
CURRENT_TAB: "here",
@@ -184,14 +242,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
},
addUsageCommand: function (params) {
+ function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
+
let name = commands.add(params.name, params.description,
function (args) {
let results = array(params.iterate(args))
.sort(function (a, b) String.localeCompare(a.name, b.name));
- let filters = args.map(function (arg) RegExp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
+ let filters = args.map(function (arg) util.regexp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
if (filters.length)
- results = results.filter(function (item) filters.every(function (re) re.test(item.name + " " + item.description)));
+ results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
commandline.commandOutput(
template.usage(results, params.format));
@@ -200,9 +260,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
argCount: "*",
completer: function (context, args) {
context.keys.text = util.identity;
- context.keys.description = function () seen[this.text] + " matching items";
+ context.keys.description = function () seen[this.text] + /*L*/" matching items";
let seen = {};
- context.completions = array(item.description.toLowerCase().split(/[()\s]+/)
+ context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
for (item in params.iterate(args)))
.flatten().filter(function (w) /^\w[\w-_']+$/.test(w))
.map(function (k) {
@@ -221,7 +281,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
let tags = services["dactyl:"].HELP_TAGS;
for (let obj in values(results)) {
let res = dactyl.generateHelp(obj, null, null, true);
- if (!set.has(tags, obj.helpTag))
+ if (!Set.has(tags, obj.helpTag))
res[1].@tag = obj.helpTag;
yield res;
@@ -311,7 +371,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
clipboardHelper.copyString(str);
if (verbose) {
- let message = { message: "Yanked " + str };
+ let message = { message: _("dactyl.yank", str) };
try {
message.domains = [util.newURI(str).host];
}
@@ -346,7 +406,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
echoerr: function echoerr(str, flags) {
flags |= commandline.APPEND_TO_MESSAGES;
- if (isinstance(str, ["Error", "Exception"]))
+ if (isinstance(str, ["DOMException", "Error", "Exception"]) || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
dactyl.reportError(str);
if (isObject(str) && "echoerr" in str)
str = str.echoerr;
@@ -414,7 +474,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
({ file: fileName, line: lineNumber, context: ctxt }) = info;
if (!context && fileName && fileName[0] !== "[")
- context = _userContext || ctxt;
+ context = ctxt || _userContext;
if (isinstance(context, ["Sandbox"]))
return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
@@ -559,7 +619,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
* @param {string} feature The feature name.
* @returns {boolean}
*/
- has: function (feature) set.has(config.features, feature),
+ has: function (feature) Set.has(config.features, feature),
/**
* Returns the URL of the specified help *topic* if it exists.
@@ -608,6 +668,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
* Initialize the help system.
*/
initHelp: function (force) {
+ // Waits for the add-on to become available, if necessary.
+ config.addon;
+ config.version;
+
if (force || !this.helpInitialized) {
if ("noscriptOverlay" in window) {
noscriptOverlay.safeAllow("chrome-data:", true, false);
@@ -665,8 +729,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
let body = XML();
for (let [, context] in Iterator(plugins.contexts))
- if (context && context.INFO instanceof XML) {
- let info = context.INFO;
+ try {
+ let info = contexts.getDocs(context);
+ if (info instanceof XML) {
if (info.*.@lang.length()) {
let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
@@ -677,22 +742,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
if (elem[attr].length())
info[attr] = elem[attr];
}
- body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
- context.INFO;
+ body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
+ info;
+ }
+ }
+ catch (e) {
+ util.reportError(e);
}
let help =
'<?xml version="1.0"?>\n' +
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
<document xmlns={NS}
name="plugins" title={config.appName + " Plugins"}>
- <h1 tag="using-plugins">Using Plugins</h1>
+ <h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
<toc start="2"/>
{body}
- </document>.toXMLString()));
+ </document>.toXMLString();
fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
fileMap["versions"] = function () {
@@ -725,6 +793,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
default xml namespace = NS;
function rec(text, level, li) {
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+
let res = <></>;
let list, space, i = 0;
@@ -735,7 +805,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
if (!list)
res += list = <ul/>;
let li = <li/>;
- li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
+ li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
list.* += li;
}
else if (match.par) {
@@ -751,8 +821,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}
list = null;
- if (level == 0 && /^.*:\n$/.test(match.par))
- res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
+ if (level == 0 && /^.*:\n$/.test(match.par)) {
+ let text = par.slice(0, -1);
+ res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
+ }
else {
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
res += <p highlight={group + " HelpNews"}>{
@@ -774,6 +846,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
return res;
}
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
let body = rec(NEWS, 0);
for each (let li in body..li) {
let list = li..li.(@NS::highlight == "HelpNewsOld");
@@ -784,19 +857,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}
}
- XML.prettyPrinting = XML.ignoreWhitespace = false;
return ["application/xml",
'<?xml version="1.0"?>\n' +
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
<document xmlns={NS} xmlns:dactyl={NS}
name="versions" title={config.appName + " Versions"}>
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
<toc start="2"/>
{body}
- </document>.toXMLString()))
+ </document>.toXMLString()
];
}
addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
@@ -806,15 +877,26 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
overlayMap["index"] = ['text/xml;charset=UTF-8',
'<?xml version="1.0"?>\n' +
- '<overlay xmlns="' + NS + '">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
+ <overlay xmlns={NS}>{
template.map(dactyl.indices, function ([name, iter])
<dl insertafter={name + "-index"}>{
template.map(iter(), util.identity)
- }</dl>, <>{"\n\n"}</>))) +
- '\n</overlay>'];
+ }</dl>, <>{"\n\n"}</>)
+ }</overlay>];
+ addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
- addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
+ overlayMap["gui"] = ['text/xml;charset=UTF-8',
+ '<?xml version="1.0"?>\n' +
+ <overlay xmlns={NS}>
+ <dl insertafter="dialog-list">{
+ template.map(config.dialogs, function ([name, val])
+ (!val[2] || val[2]())
+ ? <><dt>{name}</dt><dd>{val[0]}</dd></>
+ : undefined,
+ <>{"\n"}</>)
+ }</dl>
+ </overlay>];
+
this.helpInitialized = true;
}
@@ -850,7 +932,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data));
}
- let empty = set("area base basefont br col frame hr img input isindex link meta param"
+ let empty = Set("area base basefont br col frame hr img input isindex link meta param"
.split(" "));
function fix(node) {
switch(node.nodeType) {
@@ -860,16 +942,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
data.push("<"); data.push(node.localName);
if (node instanceof HTMLHtmlElement)
- data.push(" xmlns=" + XHTML.uri.quote());
+ data.push(" xmlns=" + XHTML.uri.quote(),
+ " xmlns:dactyl=" + NS.uri.quote());
for (let { name, value } in array.iterValues(node.attributes)) {
if (name == "dactyl:highlight") {
- set.add(styles, value);
+ Set.add(styles, value);
name = "class";
value = "hl-" + value;
}
if (name == "href") {
- value = node.href;
+ value = node.href || value;
if (value.indexOf("dactyl://help-tag/") == 0) {
let uri = services.io.newChannel(value, null, null).originalURI;
value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
@@ -881,11 +964,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
chromeFiles[value] = value.replace(/.*\//, "");
value = value.replace(/.*\//, "");
}
- data.push(" ");
- data.push(name);
- data.push('="');
- data.push(<>{value}</>.toXMLString());
- data.push('"');
+
+ data.push(" ", name, '="',
+ <>{value}</>.toXMLString().replace(/"/g, """),
+ '"');
}
if (node.localName in empty)
data.push(" />");
@@ -894,7 +976,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
if (node instanceof HTMLHeadElement)
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
Array.map(node.childNodes, fix);
- data.push("</"); data.push(node.localName); data.push(">");
+ data.push("</", node.localName, ">");
}
break;
case Node.TEXT_NODE:
@@ -905,9 +987,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
let chromeFiles = {};
let styles = {};
for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) {
- dactyl.open("dactyl://help/" + file);
- dactyl.modules.events.waitForPageLoad();
- let data = [
+ let url = "dactyl://help/" + file;
+ dactyl.open(url);
+ util.waitFor(function () content.location.href == url && buffer.loaded
+ && content.document.documentElement instanceof HTMLHtmlElement,
+ 15000);
+ events.waitForPageLoad();
+ var data = [
'<?xml version="1.0" encoding="UTF-8"?>\n',
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
@@ -916,7 +1002,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
addDataEntry(file + ".xhtml", data.join(""));
}
- let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))]
+ let data = [h for (h in highlight) if (Set.has(styles, h.class) || /^Help/.test(h.class))]
.map(function (h) h.selector
.replace(/^\[.*?=(.*?)\]/, ".hl-$1")
.replace(/html\|/g, "") + "\t" + "{" + h.cssText + "}")
@@ -954,7 +1040,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
if (obj instanceof Command) {
link = function (cmd) <ex>{cmd}</ex>;
args = obj.parseArgs("", CompletionContext(str || ""));
- spec = function (cmd) cmd + (obj.bang ? <oa>!</oa> : <></>);
+ spec = function (cmd) <>{
+ obj.count ? <oa>count</oa> : <></>
+ }{
+ cmd
+ }{
+ obj.bang ? <oa>!</oa> : <></>
+ }</>;
}
else if (obj instanceof Map) {
spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
@@ -969,7 +1061,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
};
}
else if (obj instanceof Option) {
+ tag = spec = function (name) <>'{name}'</>;
link = function (opt, name) <o>{name}</o>;
+ args = { value: "", values: [] };
}
XML.prettyPrinting = false;
@@ -1001,7 +1095,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
<description>{
obj.description ? br + <p>{template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}</p> : "" }{
extraHelp ? br + extraHelp : "" }{
- !(extraHelp || obj.description) ? br + <p>Sorry, no help available.</p> : "" }
+ !(extraHelp || obj.description) ? br + <p><!--L-->Sorry, no help available.</p> : "" }
</description>
</item></>;
@@ -1014,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}
if (obj.completer)
- add(completion._runCompleter(obj.completer, "", null, args).items
+ add(completion._runCompleter(obj.closure.completer, "", null, args).items
.map(function (i) [i.text, i.description]));
if (obj.options && obj.options.some(function (o) o.description))
@@ -1064,7 +1158,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
* These are set and accessed with the "g:" prefix.
*/
_globalVariables: {},
- globalVariables: deprecated("the options system", {
+ globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
get: function globalVariables() this._globalVariables
}),
@@ -1076,13 +1170,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
let loadplugins = options.get("loadplugins");
if (args)
- loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) }
+ loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
dir.readDirectory(true).forEach(function (file) {
- if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) {
+ if (file.isFile() && loadplugins.getKey(file.path)
+ && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
try {
io.source(file.path);
- dactyl.pluginFiles[file.path] = true;
+ dactyl.pluginFiles[file.path] = file.lastModifiedTime;
}
catch (e) {
dactyl.reportError(e);
@@ -1151,7 +1246,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
onExecute: function onExecute(event) {
let cmd = event.originalTarget.getAttribute("dactyl-execute");
commands.execute(cmd, null, false, null,
- { file: "[Command Line]", line: 1 });
+ { file: /*L*/"[Command Line]", line: 1 });
},
/**
@@ -1187,7 +1282,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
urls = dactyl.parseURLs(urls);
if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
- return commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ",
+ return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i))
dactyl.open(urls, params, true);
@@ -1270,7 +1365,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
* ['www.google.com/search?q=bla', 'www.osnews.com']
*
* @param {string} str
- * @returns {string[]}
+ * @returns {[string]}
*/
parseURLs: function parseURLs(str) {
let urls;
@@ -1283,7 +1378,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
return urls.map(function (url) {
url = url.trim();
- if (/^(\.{0,2}|~)(\/|$)/.test(url)) {
+ if (/^(\.{0,2}|~)(\/|$)/.test(url) || util.OS.isWindows && /^[a-z]:/i.test(url)) {
try {
// Try to find a matching file.
let file = io.File(url);
@@ -1296,7 +1391,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
// If it starts with a valid protocol, pass it through.
let proto = /^([-\w]+):/.exec(url);
if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc)
- return url.replace(/\s+/g, "");
+ return url;
// Check for a matching search keyword.
let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
@@ -1470,48 +1565,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
},
/**
- * @property {Window[]} Returns an array of all the host application's
+ * @property {[Window]} Returns an array of all the host application's
* open windows.
*/
- get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser")))],
+ get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)],
}, {
- // initially hide all GUI elements, they are later restored unless the user
- // has :set go= or something similar in his config
- hideGUI: function () {
- let guioptions = config.guioptions;
- for (let option in guioptions) {
- guioptions[option].forEach(function (elem) {
- try {
- document.getElementById(elem).collapsed = true;
- }
- catch (e) {}
- });
- }
- },
-
- // TODO: move this
- getMenuItems: function () {
- function addChildren(node, parent) {
- for (let [, item] in Iterator(node.childNodes)) {
- if (item.childNodes.length == 0 && item.localName == "menuitem"
- && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
- item.fullMenuPath = parent + item.getAttribute("label");
- items.push(item);
- }
- else {
- let path = parent;
- if (item.localName == "menu")
- path += item.getAttribute("label") + ".";
- addChildren(item, path);
- }
- }
- }
-
- let items = [];
- addChildren(document.getElementById(config.guioptions["m"][1]), "");
- return items;
- }
+ toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
}, {
events: function () {
events.listen(window, "click", dactyl.closure.onClick, true);
@@ -1542,7 +1602,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
M: ["Always show messages outside of the status line"]
},
setter: function (opts) {
- if (loaded.commandline)
+ if (loaded.commandline || ~opts.indexOf("c"))
commandline.widgets.updateVisibility();
}
},
@@ -1578,7 +1638,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
true);
prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
- "See 'guioptions' scrollbar flags.");
+ _("option.guioptions.safeSet"));
},
validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
UTF8("Only one of ‘l’ or ‘r’ allowed"))
@@ -1616,7 +1676,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
// FIXME: cleanup
cleanupValue: config.cleanups.guioptions ||
"r" + [k for ([k, v] in iter(groups[1].opts))
- if (!document.getElementById(v[1][0]).collapsed)].join(""),
+ if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
@@ -1684,18 +1744,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
{
setter: function (value) {
prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
- "See 'visualbell' option");
+ _("option.visualbell.safeSet"));
return value;
}
});
},
mappings: function () {
- mappings.add([modes.MAIN], ["<F1>"],
+ mappings.add([modes.MAIN], ["<open-help>", "<F1>"],
"Open the introductory help page",
function () { dactyl.help(); });
- mappings.add([modes.MAIN], ["<A-F1>"],
+ mappings.add([modes.MAIN], ["<open-single-help>", "<A-F1>"],
"Open the single, consolidated help page",
function () { ex.helpall(); });
@@ -1727,7 +1787,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}
}, {
argCount: "1",
- bang: true,
completer: function (context) {
context.ignoreCase = true;
completion.dialog(context);
@@ -1738,15 +1797,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
"Execute the specified menu item from the command line",
function (args) {
let arg = args[0] || "";
- let items = Dactyl.getMenuItems();
+ let items = dactyl.menuItems;
- dactyl.assert(items.some(function (i) i.fullMenuPath == arg),
+ dactyl.assert(items.some(function (i) i.dactylPath == arg),
_("emenu.notFound", arg));
for (let [, item] in Iterator(items)) {
- if (item.fullMenuPath == arg)
+ if (item.dactylPath == arg) {
+ dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
item.doCommand();
}
+ }
}, {
argCount: "1",
completer: function (context) completion.menuItem(context),
@@ -1793,7 +1854,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
});
commands.add(["loadplugins", "lpl"],
- "Load all plugins immediately",
+ "Load all or matching plugins",
function (args) {
dactyl.loadPlugins(args.length ? args : null, args.bang);
},
@@ -1819,6 +1880,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
literal: 0
});
+ commands.add(["exit", "x"],
+ "Quit " + config.appName,
+ function (args) {
+ dactyl.quit(false, args.bang);
+ }, {
+ argCount: "0",
+ bang: true
+ });
+
commands.add(["q[uit]"],
dactyl.has("tabs") ? "Quit current tab" : "Quit application",
function (args) {
@@ -1837,12 +1907,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
"Reload the " + config.appName + " add-on",
function (args) {
if (args.trailing)
- JSMLoader.rehashCmd = args.trailing; // Hack.
+ storage.session.rehashCmd = args.trailing; // Hack.
args.break = true;
util.rehash(args);
},
{
- argCount: "0",
+ argCount: "0", // FIXME
options: [
{
names: ["+u"],
@@ -1870,16 +1940,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
commands.add(["res[tart]"],
"Force " + config.appName + " to restart",
- function () { dactyl.restart(); });
+ function () { dactyl.restart(); },
+ { argCount: "0" });
function findToolbar(name) util.evaluateXPath(
- "//*[@toolbarname=" + util.escapeString(name, "'") + "]",
+ "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
+ "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
document).snapshotItem(0);
var toolbox = document.getElementById("navigator-toolbox");
if (toolbox) {
- let hidden = function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true";
-
let toolbarCommand = function (names, desc, action, filter) {
commands.add(names, desc,
function (args) {
@@ -1888,7 +1958,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
action(toolbar);
events.checkFocus();
}, {
- argcount: "1",
+ argCount: "1",
completer: function (context) {
completion.toolbar(context);
if (filter)
@@ -1900,12 +1970,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar",
function (toolbar) dactyl.setNodeVisible(toolbar, true),
- function ({ item }) hidden(item));
+ function ({ item }) Dactyl.toolbarHidden(item));
toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar",
function (toolbar) dactyl.setNodeVisible(toolbar, false),
- function ({ item }) !hidden(item));
+ function ({ item }) !Dactyl.toolbarHidden(item));
toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar",
- function (toolbar) dactyl.setNodeVisible(toolbar, hidden(toolbar)));
+ function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar)));
}
commands.add(["time"],
@@ -1916,9 +1986,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
args = args[0] || "";
if (args[0] == ":")
- var method = function () commands.execute(args, null, true);
+ var func = function () commands.execute(args, null, false);
else
- method = dactyl.userFunc(args);
+ func = dactyl.userFunc(args);
try {
if (count > 1) {
@@ -1927,7 +1997,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
for (let i in util.interruptibleRange(0, count, 500)) {
let now = Date.now();
- method();
+ func();
total += Date.now() - now;
}
@@ -1953,16 +2023,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
commandline.commandOutput(
<table>
<tr highlight="Title" align="left">
- <th colspan="3">Code execution summary</th>
+ <th colspan="3">{_("title.Code execution summary")}</th>
</tr>
- <tr><td>  Executed:</td><td align="right"><span class="times-executed">{count}</span></td><td>times</td></tr>
- <tr><td>  Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
- <tr><td>  Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
+ <tr><td>  {_("title.Executed")}:</td><td align="right"><span class="times-executed">{count}</span></td><td><!--L-->times</td></tr>
+ <tr><td>  {_("title.Average time")}:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
+ <tr><td>  {_("title.Total time")}:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
</table>);
}
else {
let beforeTime = Date.now();
- method();
+ func();
if (special)
return;
@@ -1979,7 +2049,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
dactyl.echoerr(e);
}
}, {
- argCount: "+",
+ argCount: "1",
bang: true,
completer: function (context) {
if (/^:/.test(context.filter))
@@ -2010,7 +2080,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
vbs.setFrom = setFrom;
}
}, {
- argCount: "+",
+ argCount: "1",
completer: function (context) completion.ex(context),
count: true,
literal: 0,
@@ -2052,8 +2122,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
completion.menuItem = function menuItem(context) {
context.title = ["Menu Path", "Label"];
context.anchored = false;
- context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
- context.completions = dactyl.menuItems;
+ context.keys = {
+ text: "dactylPath",
+ description: function (item) item.getAttribute("label"),
+ highlight: function (item) item.disabled ? "Disabled" : ""
+ };
+ context.generate = function () dactyl.menuItems;
};
var toolbox = document.getElementById("navigator-toolbox");
@@ -2076,7 +2150,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
dactyl.timeout(function () {
try {
- var args = JSMLoader.commandlineArgs || services.commandLineHandler.optionValue;
+ var args = storage.session.commandlineArgs || services.commandLineHandler.optionValue;
if (isString(args))
args = dactyl.parseCommandLine(args);
@@ -2107,7 +2181,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
}
// TODO: we should have some class where all this guioptions stuff fits well
- // Dactyl.hideGUI();
+ // dactyl.hideGUI();
if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
@@ -2167,9 +2241,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
dactyl.execute(cmd);
});
- if (JSMLoader.rehashCmd)
- dactyl.execute(JSMLoader.rehashCmd);
- JSMLoader.rehashCmd = null;
+ if (storage.session.rehashCmd)
+ dactyl.execute(storage.session.rehashCmd);
+ storage.session.rehashCmd = null;
dactyl.fullyInitialized = true;
dactyl.triggerObserver("enter", null);
diff --git a/common/content/disable-acr.jsm b/common/content/disable-acr.jsm
--- a/common/content/disable-acr.jsm
+++ b/common/content/disable-acr.jsm
@@ -14,16 +14,18 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+const TOPIC = "chrome-document-global-created";
+
function observe(window, topic, url) {
- if (topic === "chrome-document-global-created")
+ if (topic === TOPIC)
checkDocument(window.document);
}
function init(id) {
if (id)
ADDON_ID = id;
- Services.obs[id ? "addObserver" : "removeObserver"](observe, "chrome-document-global-created", false);
- for (let doc in chromeDocuments)
+ Services.obs[id ? "addObserver" : "removeObserver"](observe, TOPIC, false);
+ for (let doc in chromeDocuments())
checkDocument(doc, !id);
}
function cleanup() { init(null); }
@@ -55,7 +57,7 @@ function checkDocument(doc, disable, for
}
function chromeDocuments() {
- let windows = services.windowMediator.getXULWindowEnumerator(null);
+ let windows = Services.wm.getXULWindowEnumerator(null);
while (windows.hasMoreElements()) {
let window = windows.getNext().QueryInterface(Ci.nsIXULWindow);
for each (let type in ["typeChrome", "typeContent"]) {
diff --git a/common/content/editor.js b/common/content/editor.js
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -1,3 +1,4 @@
+// Copyright (c) 2008-2011 Kris Maglione <maglione.k at Gmail>
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
@@ -24,12 +25,6 @@ var Editor = Module("editor", {
selectedText: function () String(Editor.getEditor(null).selection),
pasteClipboard: function (clipboard, toStart) {
- // TODO: I don't think this is needed anymore? --djk
- if (util.OS.isWindows) {
- this.executeCommand("cmd_paste");
- return;
- }
-
let elem = dactyl.focusedElement;
if (elem.inputField)
elem = elem.inputField;
@@ -189,59 +184,50 @@ var Editor = Module("editor", {
}
},
- // returns the position of char
- findCharForward: function (ch, count) {
- if (!Editor.getEditor())
+ findChar: function (key, count, backward) {
+
+ let editor = Editor.getEditor();
+ if (!editor)
return -1;
- let text = Editor.getEditor().value;
// XXX
if (count == null)
count = 1;
- for (let i = Editor.getEditor().selectionEnd + 1; i < text.length; i++) {
- if (text[i] == "\n")
- break;
- if (text[i] == ch)
- count--;
- if (count == 0)
- return i + 1; // always position the cursor after the char
+ let code = events.fromString(key)[0].charCode;
+ util.assert(code);
+ let char = String.fromCharCode(code);
+
+ let text = editor.value;
+ let caret = editor.selectionEnd;
+ if (backward) {
+ let end = text.lastIndexOf("\n", caret);
+ while (caret > end && caret >= 0 && count--)
+ caret = text.lastIndexOf(char, caret - 1);
+ }
+ else {
+ let end = text.indexOf("\n", caret);
+ if (end == -1)
+ end = text.length;
+
+ while (caret < end && caret >= 0 && count--)
+ caret = text.indexOf(char, caret + 1);
}
+ if (count > 0)
+ caret = -1;
+ if (caret == -1)
dactyl.beep();
- return -1;
- },
-
- // returns the position of char
- findCharBackward: function (ch, count) {
- if (!Editor.getEditor())
- return -1;
-
- let text = Editor.getEditor().value;
- // XXX
- if (count == null)
- count = 1;
-
- for (let i = Editor.getEditor().selectionStart - 1; i >= 0; i--) {
- if (text[i] == "\n")
- break;
- if (text[i] == ch)
- count--;
- if (count == 0)
- return i;
- }
-
- dactyl.beep();
- return -1;
+ return caret;
},
/**
* Edits the given file in the external editor as specified by the
* 'editor' option.
*
- * @param {object|File|string} args An object specifying the file,
- * line, and column to edit. If a non-object is specified, it is
- * treated as the file parameter of the object.
+ * @param {object|File|string} args An object specifying the file, line,
+ * and column to edit. If a non-object is specified, it is treated as
+ * the file parameter of the object.
* @param {boolean} blocking If true, this function does not return
* until the editor exits.
*/
@@ -252,7 +238,7 @@ var Editor = Module("editor", {
let args = options.get("editor").format(args);
- dactyl.assert(args.length >= 1, _("editor.noEditor"));
+ dactyl.assert(args.length >= 1, _("option.notSet", "editor"));
io.run(args.shift(), args, blocking);
},
@@ -266,7 +252,7 @@ var Editor = Module("editor", {
let line, column;
if (!forceEditing && textBox && textBox.type == "password") {
- commandline.input("Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]): ",
+ commandline.input(_("editor.prompt.editPassword") + " ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i))
editor.editFieldExternally(true);
@@ -281,10 +267,10 @@ var Editor = Module("editor", {
column = 1 + pre.replace(/[^]*\n/, "").length;
}
else {
- var editor = window.GetCurrentEditor ? GetCurrentEditor()
+ var editor_ = window.GetCurrentEditor ? GetCurrentEditor()
: Editor.getEditor(document.commandDispatcher.focusedWindow);
- dactyl.assert(editor);
- text = Array.map(editor.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
+ dactyl.assert(editor_);
+ text = Array.map(editor_.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
}
let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
@@ -318,19 +304,25 @@ var Editor = Module("editor", {
lastUpdate = Date.now();
let val = tmpfile.read();
- if (textBox)
+ if (textBox) {
textBox.value = val;
+
+ textBox.setAttributeNS(NS, "modifiable", true);
+ util.computedStyle(textBox).MozUserInput;
+ events.dispatch(textBox, events.create(textBox.ownerDocument, "input", {}));
+ textBox.removeAttributeNS(NS, "modifiable");
+ }
else {
- while (editor.rootElement.firstChild)
- editor.rootElement.removeChild(editor.rootElement.firstChild);
- editor.rootElement.innerHTML = val;
+ while (editor_.rootElement.firstChild)
+ editor_.rootElement.removeChild(editor_.rootElement.firstChild);
+ editor_.rootElement.innerHTML = val;
}
}
try {
var tmpfile = io.createTempFile();
if (!tmpfile)
- throw Error("Couldn't create temporary file");
+ throw Error(_("io.cantCreateTempFile"));
if (textBox) {
highlight.highlightNode(textBox, origGroup + " EditorEditing");
@@ -338,8 +330,7 @@ var Editor = Module("editor", {
}
if (!tmpfile.write(text))
- throw Error("Input contains characters not valid in the current " +
- "file encoding");
+ throw Error(_("io.cantEncode"));
var lastUpdate = Date.now();
@@ -414,9 +405,9 @@ var Editor = Module("editor", {
elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
dactyl.assert(elem);
+ try {
if (elem instanceof Element)
return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
- try {
return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession)
.getEditorForWindow(elem);
@@ -658,19 +649,28 @@ var Editor = Module("editor", {
mappings.add([modes.INPUT],
["<C-t>"], "Edit text field in Vi mode",
function () {
+ dactyl.assert(dactyl.focusedElement);
dactyl.assert(!editor.isTextEdit);
modes.push(modes.TEXT_EDIT);
});
+ // Ugh.
+ mappings.add([modes.INPUT, modes.CARET],
+ ["<*-CR>", "<*-BS>", "<*-Del>", "<*-Left>", "<*-Right>", "<*-Up>", "<*-Down>",
+ "<*-Home>", "<*-End>", "<*-PageUp>", "<*-PageDown>",
+ "<M-c>", "<M-v>", "<*-Tab>"],
+ "Handled by " + config.host,
+ function () Events.PASS_THROUGH);
+
mappings.add([modes.INSERT],
- ["<Space>", "<Return>"], "Expand insert mode abbreviation",
+ ["<Space>", "<Return>"], "Expand Insert mode abbreviation",
function () {
editor.expandAbbreviation(modes.INSERT);
- return Events.PASS;
+ return Events.PASS_THROUGH;
});
mappings.add([modes.INSERT],
- ["<C-]>", "<C-5>"], "Expand insert mode abbreviation",
+ ["<C-]>", "<C-5>"], "Expand Insert mode abbreviation",
function () { editor.expandAbbreviation(modes.INSERT); });
// text edit mode
@@ -723,15 +723,15 @@ var Editor = Module("editor", {
// visual mode
mappings.add([modes.CARET, modes.TEXT_EDIT],
- ["v"], "Start visual mode",
+ ["v"], "Start Visual mode",
function () { modes.push(modes.VISUAL); });
mappings.add([modes.VISUAL],
- ["v", "V"], "End visual mode",
+ ["v", "V"], "End Visual mode",
function () { modes.pop(); });
mappings.add([modes.TEXT_EDIT],
- ["V"], "Start visual line mode",
+ ["V"], "Start Visual Line mode",
function () {
modes.push(modes.VISUAL, modes.LINE);
editor.executeCommand("cmd_beginLine", 1);
@@ -777,7 +777,7 @@ var Editor = Module("editor", {
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["f"], "Move to a character on the current line after the cursor",
function ({ arg, count }) {
- let pos = editor.findCharForward(arg, Math.max(count, 1));
+ let pos = editor.findChar(arg, Math.max(count, 1));
if (pos >= 0)
editor.moveToPosition(pos, true, modes.main == modes.VISUAL);
},
@@ -786,7 +786,7 @@ var Editor = Module("editor", {
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["F"], "Move to a character on the current line before the cursor",
function ({ arg, count }) {
- let pos = editor.findCharBackward(arg, Math.max(count, 1));
+ let pos = editor.findChar(arg, Math.max(count, 1), true);
if (pos >= 0)
editor.moveToPosition(pos, false, modes.main == modes.VISUAL);
},
@@ -795,7 +795,7 @@ var Editor = Module("editor", {
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["t"], "Move before a character on the current line",
function ({ arg, count }) {
- let pos = editor.findCharForward(arg, Math.max(count, 1));
+ let pos = editor.findChar(arg, Math.max(count, 1));
if (pos >= 0)
editor.moveToPosition(pos - 1, true, modes.main == modes.VISUAL);
},
@@ -804,7 +804,7 @@ var Editor = Module("editor", {
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["T"], "Move before a character on the current line, backwards",
function ({ arg, count }) {
- let pos = editor.findCharBackward(arg, Math.max(count, 1));
+ let pos = editor.findChar(arg, Math.max(count, 1), true);
if (pos >= 0)
editor.moveToPosition(pos + 1, false, modes.main == modes.VISUAL);
},
@@ -837,10 +837,10 @@ var Editor = Module("editor", {
function bind() mappings.add.apply(mappings,
[[modes.AUTOCOMPLETE]].concat(Array.slice(arguments)))
- bind(["<Esc>"], "Return to INSERT mode",
+ bind(["<Esc>"], "Return to Insert mode",
function () Events.PASS_THROUGH);
- bind(["<C-[>"], "Return to INSERT mode",
+ bind(["<C-[>"], "Return to Insert mode",
function () { events.feedkeys("<Esc>", { skipmap: true }); });
bind(["<Up>"], "Select the previous autocomplete result",
@@ -859,7 +859,7 @@ var Editor = Module("editor", {
options: function () {
options.add(["editor"],
"The external text editor",
- "string", "gvim -f +<line> <file>", {
+ "string", 'gvim -f +<line> +"sil! call cursor(0, <column>)" <file>', {
format: function (obj, value) {
let args = commands.parseArgs(value || this.value, { argCount: "*", allowUnknownOptions: true })
.map(util.compileMacro).filter(function (fmt) fmt.valid(obj))
@@ -868,7 +868,7 @@ var Editor = Module("editor", {
args.push(obj["file"]);
return args;
},
- has: function (key) set.has(util.compileMacro(this.value).seen, key),
+ has: function (key) Set.has(util.compileMacro(this.value).seen, key),
validator: function (value) {
this.format({}, value);
return Object.keys(util.compileMacro(value).seen).every(function (k) ["column", "file", "line"].indexOf(k) >= 0);
diff --git a/common/content/events.js b/common/content/events.js
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -16,22 +16,27 @@ var ProcessorStack = Class("ProcessorSta
this.buffer = "";
this.events = [];
+ events.dbg("STACK " + mode);
+
let main = { __proto__: mode.main, params: mode.params };
- let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact();
+ this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact();
if (builtin)
hives = hives.filter(function (h) h.name === "builtin");
- this.processors = keyModes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
+ this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
.flatten().array;
this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer);
for (let [i, input] in Iterator(this.processors)) {
let params = input.main.params;
+
if (params.preExecute)
input.preExecute = params.preExecute;
+
if (params.postExecute)
input.postExecute = params.postExecute;
+
if (params.onKeyPress && input.hive === mappings.builtin)
input.fallthrough = function fallthrough(events) {
return params.onKeyPress(events) === false ? Events.KILL : Events.PASS;
@@ -39,15 +44,17 @@ var ProcessorStack = Class("ProcessorSta
}
let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"];
- if (!builtin && hive.active
- && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
+ if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
this.processors.unshift(KeyProcessor(modes.BASE, hive));
},
+ passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)),
+
notify: function () {
+ events.dbg("NOTIFY()");
events.keyEvents = [];
events.processor = null;
- if (!this.execute(Events.KILL, true)) {
+ if (!this.execute(undefined, true)) {
events.processor = this;
events.keyEvents = this.keyEvents;
}
@@ -60,64 +67,91 @@ var ProcessorStack = Class("ProcessorSta
callable(result) ? result.toSource().substr(0, 50) : result),
execute: function execute(result, force) {
+ events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length
+ + " processors:" + this.processors.length + " actions:" + this.actions.length);
- if (force && this.actions.length)
- this.processors.length = 0;
+ let processors = this.processors;
+ let length = 1;
+
+ if (force)
+ this.processors = [];
if (this.ownsBuffer)
statusline.inputBuffer = this.processors.length ? this.buffer : "";
if (!this.processors.some(function (p) !p.extended) && this.actions.length) {
- if (this._actions.length == 0) {
- dactyl.beep();
- events.feedingKeys = false;
- }
+ // We have matching actions and no processors other than
+ // those waiting on further arguments. Execute actions as
+ // long as they continue to return PASS.
for (var action in values(this.actions)) {
while (callable(action)) {
+ length = action.eventLength;
action = dactyl.trapErrors(action);
- events.dbg("ACTION RES: " + this._result(action));
+ events.dbg("ACTION RES: " + length + " " + this._result(action));
}
if (action !== Events.PASS)
break;
}
+ // Result is the result of the last action. Unless it's
+ // PASS, kill any remaining argument processors.
result = action !== undefined ? action : Events.KILL;
if (action !== Events.PASS)
this.processors.length = 0;
}
else if (this.processors.length) {
+ // We're still waiting on the longest matching processor.
+ // Kill the event, set a timeout to give up waiting if applicable.
+
result = Events.KILL;
- if (this.actions.length && options["timeout"])
+ if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown)))
this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT);
}
else if (result !== Events.KILL && !this.actions.length &&
- (this.events.length > 1 ||
- this.processors.some(function (p) !p.main.passUnknown))) {
- result = Events.KILL;
+ !(this.events[0].isReplay || this.passUnknown
+ || this.modes.some(function (m) m.passEvent(this), this.events[0]))) {
+ // No patching processors, this isn't a fake, pass-through
+ // event, we're not in pass-through mode, and we're not
+ // choosing to pass unknown keys. Kill the event and beep.
+
+ result = Events.ABORT;
if (!Events.isEscape(this.events.slice(-1)[0]))
dactyl.beep();
events.feedingKeys = false;
}
else if (result === undefined)
+ // No matching processors, we're willing to pass this event,
+ // and we don't have a default action from a processor. Just
+ // pass the event.
result = Events.PASS;
- events.dbg("RESULT: " + this._result(result));
-
- if (result === Events.PASS || result === Events.PASS_THROUGH)
- if (this.events[0].originalTarget)
- this.events[0].originalTarget.dactylKeyPress = undefined;
+ events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n");
if (result !== Events.PASS || this.events.length > 1)
+ if (result !== Events.ABORT || !this.events[0].isReplay)
Events.kill(this.events[this.events.length - 1]);
+ if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown)
+ events.passing = true;
+
+ if (result === Events.PASS_THROUGH && this.keyEvents.length)
+ events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t"));
+
if (result === Events.PASS_THROUGH)
events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true });
- else if (result === Events.PASS || result === Events.ABORT) {
+ else {
let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented);
- if (list.length)
- events.dbg("REFEED: " + list.map(events.closure.toString).join(""));
- events.feedevents(null, list, { skipmap: true, isMacro: true, isReplay: true });
+
+ if (result === Events.PASS)
+ events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+ if (list.length > length)
+ events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+
+ if (result === Events.PASS)
+ events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true });
+ if (list.length > length && this.processors.length === 0)
+ events.feedevents(null, list.slice(length));
}
return this.processors.length === 0;
@@ -137,7 +171,7 @@ var ProcessorStack = Class("ProcessorSta
let actions = [];
let processors = [];
- events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
+ events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
for (let [i, input] in Iterator(this.processors)) {
let res = input.process(event);
@@ -149,8 +183,6 @@ var ProcessorStack = Class("ProcessorSta
if (res === Events.KILL)
break;
- buffer = buffer || input.inputBuffer;
-
if (callable(res))
actions.push(res);
@@ -162,11 +194,15 @@ var ProcessorStack = Class("ProcessorSta
events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result));
events.dbg("ACTIONS: " + actions.length + " " + this.actions.length);
- events.dbg("PROCESSORS:", processors);
+ events.dbg("PROCESSORS:", processors, "\n");
this._actions = actions;
this.actions = actions.concat(this.actions);
+ for (let action in values(actions))
+ if (!("eventLength" in action))
+ action.eventLength = this.events.length;
+
if (result === Events.KILL)
this.actions = [];
else if (!this.actions.length && !processors.length)
@@ -220,8 +256,10 @@ var KeyProcessor = Class("KeyProcessor",
function execute() {
if (self.preExecute)
self.preExecute.apply(self, args);
- let res = map.execute.call(map, update({ self: self.main.params.mappingSelf || self.main.mappingSelf || map },
- args));
+
+ args.self = self.main.params.mappingSelf || self.main.mappingSelf || map;
+ let res = map.execute.call(map, args);
+
if (self.postExecute)
self.postExecute.apply(self, args);
return res;
@@ -285,6 +323,10 @@ var KeyArgProcessor = Class("KeyArgProce
}
});
+/**
+ * A hive used mainly for tracking event listeners and cleaning them up when a
+ * group is destroyed.
+ */
var EventHive = Class("EventHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
@@ -315,6 +357,9 @@ var EventHive = Class("EventHive", Conte
[,, capture, allowUntrusted] = arguments;
}
+ if (Set.has(events, "input") && !Set.has(events, "dactyl-input"))
+ events["dactyl-input"] = events.input;
+
for (let [event, callback] in Iterator(events)) {
let args = [Cu.getWeakReference(target),
event,
@@ -354,7 +399,6 @@ var Events = Module("events", {
dbg: function () {},
init: function () {
- const self = this;
this.keyEvents = [];
update(this, {
@@ -369,9 +413,7 @@ var Events = Module("events", {
util.overlayWindow(window, {
append: <e4x xmlns={XUL}>
<window id={document.documentElement.id}>
- <!--this notifies us also of focus events in the XUL
- from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !-->
- <!-- I don't think we really need this. ––Kris -->
+ <!-- http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands -->
<commandset id="dactyl-onfocus" commandupdater="true" events="focus"
oncommandupdate="dactyl.modules.events.onFocusChange(event);"/>
<commandset id="dactyl-onselect" commandupdater="true" events="select"
@@ -411,11 +453,12 @@ var Events = Module("events", {
subtract: ["Minus", "Subtract"]
};
- this._pseudoKeys = set(["count", "leader", "nop", "pass"]);
+ this._pseudoKeys = Set(["count", "leader", "nop", "pass"]);
this._key_key = {};
this._code_key = {};
this._key_code = {};
+ this._code_nativeKey = {};
for (let list in values(this._keyTable))
for (let v in values(list)) {
@@ -425,6 +468,8 @@ var Events = Module("events", {
}
for (let [k, v] in Iterator(KeyEvent)) {
+ this._code_nativeKey[v] = k.substr(4);
+
k = k.substr(7).toLowerCase();
let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase())
.replace(/^NUMPAD/, "k")];
@@ -449,29 +494,18 @@ var Events = Module("events", {
}
this._activeMenubar = false;
- this.listen(window, this, "events", true);
-
- dactyl.registerObserver("modeChange", function () {
- delete self.processor;
- });
+ this.listen(window, this, "events");
},
signals: {
"browser.locationChange": function (webProgress, request, uri) {
options.get("passkeys").flush();
+ },
+ "modes.change": function (oldMode, newMode) {
+ delete this.processor;
}
},
- /**
- * 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.
- */
get listen() this.builtin.closure.listen,
addSessionListener: deprecated("events.listen", { get: function addSessionListener() this.listen }),
@@ -531,7 +565,7 @@ var Events = Module("events", {
timeRecorded: Date.now()
});
- dactyl.log("Recorded " + this.recording + ": " + this._macroKeys.join(""), 9);
+ dactyl.log(_("macro.recorded", this.recording, this._macroKeys.join("")), 9);
dactyl.echomsg(_("macro.recorded", this.recording));
}
this._recording = macro || null;
@@ -640,10 +674,15 @@ var Events = Module("events", {
if (quiet)
commandline.quiet = quiet;
+ keys = mappings.expandLeader(keys);
+
for (let [, evt_obj] in Iterator(events.fromString(keys))) {
let now = Date.now();
- for (let type in values(["keydown", "keyup", "keypress"])) {
+ let key = events.toString(evt_obj);
+ for (let type in values(["keydown", "keypress", "keyup"])) {
let evt = update({}, evt_obj, { type: type });
+ if (type !== "keypress" && !evt.keyCode)
+ evt.keyCode = evt._keyCode || 0;
if (isObject(noremap))
update(evt, noremap);
@@ -654,9 +693,18 @@ var Events = Module("events", {
evt.dactylSavedEvents = savedEvents;
this.feedingEvent = evt;
- let event = events.create(document.commandDispatcher.focusedWindow.document, type, evt);
- if (!evt_obj.dactylString && !evt_obj.dactylShift && !mode)
- events.dispatch(dactyl.focusedElement || buffer.focusedFrame, event, evt);
+ let doc = document.commandDispatcher.focusedWindow.document;
+ let event = events.create(doc, type, evt);
+ let target = dactyl.focusedElement
+ || ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
+ || doc.defaultView;
+
+ if (target instanceof Element && !Events.isInputElement(target) &&
+ ["<Return>", "<Space>"].indexOf(key) == -1)
+ target = target.ownerDocument.documentElement;
+
+ if (!evt_obj.dactylString && !mode)
+ events.dispatch(target, event, evt);
else if (type === "keypress")
events.events.keypress.call(events, event);
}
@@ -689,8 +737,7 @@ var Events = Module("events", {
* @param {Object} opts The pseudo-event. @optional
*/
create: function (doc, type, opts) {
- opts = opts || {};
- var DEFAULTS = {
+ const DEFAULTS = {
HTML: {
type: type, bubbles: true, cancelable: false
},
@@ -713,22 +760,31 @@ var Events = Module("events", {
relatedTarget: null
}
};
- const TYPES = {
- change: "", input: "", submit: "",
- click: "Mouse", mousedown: "Mouse", mouseup: "Mouse",
- mouseover: "Mouse", mouseout: "Mouse",
- keypress: "Key", keyup: "Key", keydown: "Key"
- };
- var t = TYPES[type];
+
+ opts = opts || {};
+
+ var t = this._create_types[type];
var evt = doc.createEvent((t || "HTML") + "Events");
let defaults = DEFAULTS[t || "HTML"];
- evt["init" + t + "Event"].apply(evt, Object.keys(defaults)
- .map(function (k) k in opts ? opts[k]
- : defaults[k]));
+
+ let args = Object.keys(defaults)
+ .map(function (k) k in opts ? opts[k] : defaults[k]);
+
+ evt["init" + t + "Event"].apply(evt, args);
return evt;
},
+ _create_types: Class.memoize(function () iter(
+ {
+ Mouse: "click mousedown mouseout mouseover mouseup",
+ Key: "keydown keypress keyup",
+ "": "change dactyl-input input submit"
+ }
+ ).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
+ .flatten()
+ .toObject()),
+
/**
* Converts a user-input string of keys into a canonical
* representation.
@@ -755,11 +811,11 @@ var Events = Module("events", {
return events.fromString(keys, unknownOk).map(events.closure.toString).join("");
},
- iterKeys: function (keys) {
+ iterKeys: function (keys) iter(function () {
let match, re = /<.*?>?>|[^<]/g;
while (match = re.exec(keys))
yield match[0];
- },
+ }()),
/**
* Dispatches an event to an element as if it were a native event.
@@ -829,34 +885,40 @@ var Events = Module("events", {
let out = [];
for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) {
let evt_str = match[0];
+
let evt_obj = { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false,
keyCode: 0, charCode: 0, type: "keypress" };
- if (evt_str.length > 1) { // <.*?>
- let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', ''];
- modifier = modifier.toUpperCase();
+ if (evt_str.length == 1) {
+ evt_obj.charCode = evt_str.charCodeAt(0);
+ evt_obj._keyCode = this._key_code[evt_str[0].toLowerCase()];
+ evt_obj.shiftKey = evt_str !== evt_str.toLowerCase();
+ }
+ else {
+ let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', ''];
+ modifier = Set(modifier.toUpperCase());
keyname = keyname.toLowerCase();
evt_obj.dactylKeyname = keyname;
if (/^u[0-9a-f]+$/.test(keyname))
keyname = String.fromCharCode(parseInt(keyname.substr(1), 16));
if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) ||
- this._key_code[keyname] || set.has(this._pseudoKeys, keyname))) {
- evt_obj.ctrlKey = /C-/.test(modifier);
- evt_obj.altKey = /A-/.test(modifier);
- evt_obj.shiftKey = /S-/.test(modifier);
- evt_obj.metaKey = /M-/.test(modifier);
+ this._key_code[keyname] || Set.has(this._pseudoKeys, keyname))) {
+ evt_obj.globKey ="*" in modifier;
+ evt_obj.ctrlKey ="C" in modifier;
+ evt_obj.altKey ="A" in modifier;
+ evt_obj.shiftKey ="S" in modifier;
+ evt_obj.metaKey ="M" in modifier || "⌘" in modifier;
+ evt_obj.dactylShift = evt_obj.shiftKey;
if (keyname.length == 1) { // normal characters
- if (evt_obj.shiftKey) {
+ if (evt_obj.shiftKey)
keyname = keyname.toUpperCase();
- if (keyname == keyname.toLowerCase())
- evt_obj.dactylShift = true;
- }
evt_obj.charCode = keyname.charCodeAt(0);
+ evt_obj._keyCode = this._key_code[keyname.toLowerCase()];
}
- else if (set.has(this._pseudoKeys, keyname)) {
+ else if (Set.has(this._pseudoKeys, keyname)) {
evt_obj.dactylString = "<" + this._key_key[keyname] + ">";
}
else if (/mouse$/.test(keyname)) { // mouse events
@@ -875,8 +937,6 @@ var Events = Module("events", {
continue;
}
}
- else // a simple key (no <...>)
- evt_obj.charCode = evt_str.charCodeAt(0);
// TODO: make a list of characters that need keyCode and charCode somewhere
if (evt_obj.keyCode == 32 || evt_obj.charCode == 32)
@@ -911,6 +971,8 @@ var Events = Module("events", {
let key = null;
let modifier = "";
+ if (event.globKey)
+ modifier += "*-";
if (event.ctrlKey)
modifier += "C-";
if (event.altKey)
@@ -931,6 +993,7 @@ var Events = Module("events", {
key = key.toUpperCase();
else
key = key.toLowerCase();
+
if (!modifier && /^[a-z0-9]$/i.test(key))
return key;
}
@@ -975,7 +1038,7 @@ var Events = Module("events", {
else {
// a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase,
// or if the shift has been forced for a non-alphabetical character by the user while :map-ping
- if (key != key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
+ if (key !== key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
modifier += "S-";
if (/^\s$/.test(key))
key = let (s = charCode.toString(16)) "U" + "0000".substr(4 - s.length) + s;
@@ -983,8 +1046,11 @@ var Events = Module("events", {
return key;
}
}
- if (key == null)
+ if (key == null) {
+ if (event.shiftKey)
+ modifier += "S-";
key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
+ }
if (key == null)
return null;
}
@@ -1015,8 +1081,71 @@ var Events = Module("events", {
},
/**
- * Whether *key* is a key code defined to accept/execute input on the
- * command line.
+ * Returns true if there's a known native key handler for the given
+ * event in the given mode.
+ *
+ * @param {Event} event A keypress event.
+ * @param {Modes.Mode} mode The main mode.
+ * @param {boolean} passUnknown Whether unknown keys should be passed.
+ */
+ hasNativeKey: function hasNativeKey(event, mode, passUnknown) {
+ if (mode.input && event.charCode && !(event.ctrlKey || event.metaKey))
+ return true;
+
+ if (!passUnknown)
+ return false;
+
+ var elements = document.getElementsByTagNameNS(XUL, "key");
+ var filters = [];
+
+ if (event.keyCode)
+ filters.push(["keycode", this._code_nativeKey[event.keyCode]]);
+ if (event.charCode) {
+ let key = String.fromCharCode(event.charCode);
+ filters.push(["key", key.toUpperCase()],
+ ["key", key.toLowerCase()]);
+ }
+
+ let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey";
+
+ let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
+ .filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess"))
+ .map(function ([k, v]) [v, true])
+ .toObject();
+
+ outer:
+ for (let [, key] in iter(elements))
+ if (filters.some(function ([k, v]) key.getAttribute(k) == v)) {
+ let keys = { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false };
+ let needed = { ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey };
+
+ let modifiers = (key.getAttribute("modifiers") || "").trim().split(/[\s,]+/);
+ for (let modifier in values(modifiers))
+ switch (modifier) {
+ case "access": update(keys, access); break;
+ case "accel": keys[accel] = true; break;
+ default: keys[modifier + "Key"] = true; break;
+ case "any":
+ if (!iter.some(keys, function ([k, v]) v && needed[k]))
+ continue outer;
+ for (let [k, v] in iter(keys)) {
+ if (v)
+ needed[k] = false;
+ keys[k] = false;
+ }
+ break;
+ }
+
+ if (iter(needed).every(function ([k, v]) v == keys[k]))
+ return key;
+ }
+
+ return false;
+ },
+
+ /**
+ * Returns true if *key* is a key code defined to accept/execute input on
+ * the command line.
*
* @param {string} key The key code to test.
* @returns {boolean}
@@ -1024,14 +1153,21 @@ var Events = Module("events", {
isAcceptKey: function (key) key == "<Return>" || key == "<C-j>" || key == "<C-m>",
/**
- * Whether *key* is a key code defined to reject/cancel input on the
- * command line.
+ * Returns true if *key* is a key code defined to reject/cancel input on
+ * the command line.
*
* @param {string} key The key code to test.
* @returns {boolean}
*/
isCancelKey: function (key) key == "<Esc>" || key == "<C-[>" || key == "<C-c>",
+ /**
+ * Returns true if *node* belongs to the current content document or any
+ * sub-frame thereof.
+ *
+ * @param {Node|Document|Window} node The node to test.
+ * @returns {boolean}
+ */
isContentNode: function isContentNode(node) {
let win = (node.ownerDocument || node).defaultView || node;
return XPCNativeWrapper(win).top == content;
@@ -1047,11 +1183,12 @@ var Events = Module("events", {
if (buffer.loaded)
return true;
- dactyl.echo(_("macro.loadWaiting"), commandline.DISALLOW_MULTILINE);
+ dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE);
const maxWaitTime = (time || 25);
- util.waitFor(function () !events.feedingKeys || buffer.loaded, this, maxWaitTime * 1000, true);
+ util.waitFor(function () buffer.loaded, this, maxWaitTime * 1000, true);
+ dactyl.echo("", commandline.FORCE_SINGLELINE);
if (!buffer.loaded)
dactyl.echoerr(_("macro.loadFailed", maxWaitTime));
@@ -1115,14 +1252,19 @@ var Events = Module("events", {
let win = (elem.ownerDocument || elem).defaultView || elem;
- if (events.isContentNode(elem) && !buffer.focusAllowed(elem)
- && !(services.focus.getLastFocusMethod(win) & 0x7000)
+ if (!(services.focus.getLastFocusMethod(win) & 0x7000)
+ && events.isContentNode(elem)
+ && !buffer.focusAllowed(elem)
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) {
+
if (elem.frameElement)
dactyl.focusContent(true);
else if (!(elem instanceof Window) || Editor.getEditor(elem))
dactyl.focus(window);
}
+
+ if (elem instanceof Element)
+ elem.dactylFocusAllowed = undefined;
},
/*
@@ -1178,7 +1320,7 @@ var Events = Module("events", {
elem.dactylKeyPress = elem.value;
util.timeout(function () {
if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
- events.dispatch(elem, events.create(elem.ownerDocument, "input"));
+ events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input"));
elem.dactylKeyPress = undefined;
});
}
@@ -1214,19 +1356,18 @@ var Events = Module("events", {
let ignore = false;
- if (modes.main == modes.PASS_THROUGH)
+ if (mode.main == modes.PASS_THROUGH)
ignore = !Events.isEscape(key) && key != "<C-v>";
- else if (modes.main == modes.QUOTE) {
+ else if (mode.main == modes.QUOTE) {
if (modes.getStack(1).main == modes.PASS_THROUGH) {
- mode.params.mainMode = modes.getStack(2).main;
+ mode = Modes.StackElement(modes.getStack(2).main);
ignore = Events.isEscape(key);
}
else if (events.shouldPass(event))
- mode.params.mainMode = modes.getStack(1).main;
+ mode = Modes.StackElement(modes.getStack(1).main);
else
ignore = true;
- if (ignore && !Events.isEscape(key))
modes.pop();
}
else if (!event.isMacro && !event.noremap && events.shouldPass(event))
@@ -1277,25 +1418,40 @@ var Events = Module("events", {
},
keyup: function onKeyUp(event) {
+ if (event.type == "keydown")
this.keyEvents.push(event);
+ else if (!this.processor)
+ this.keyEvents = [];
- let pass = this.feedingEvent && this.feedingEvent.isReplay ||
+ let pass = this.passing && !event.isMacro ||
+ this.feedingEvent && this.feedingEvent.isReplay ||
event.isReplay ||
modes.main == modes.PASS_THROUGH ||
modes.main == modes.QUOTE
&& modes.getStack(1).main !== modes.PASS_THROUGH
&& !this.shouldPass(event) ||
- !modes.passThrough && this.shouldPass(event);
+ !modes.passThrough && this.shouldPass(event) ||
+ !this.processor && event.type === "keydown"
+ && options.get("passunknown").getKey(modes.main.allBases)
+ && let (key = events.toString(event))
+ !modes.main.allBases.some(
+ function (mode) mappings.hives.some(
+ function (hive) hive.get(mode, key) || hive.getCandidates(mode, key)));
- events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass);
+ if (event.type === "keydown")
+ this.passing = pass;
+
+ events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass + " replay: " + event.isReplay + " macro: " + event.isMacro);
// Prevents certain sites from transferring focus to an input box
// before we get a chance to process our key bindings on the
// "keypress" event.
- if (!pass && !Events.isInputElement(dactyl.focusedElement))
+ if (!pass)
event.stopPropagation();
},
keydown: function onKeyDown(event) {
+ if (!event.isMacro)
+ this.passing = false;
this.events.keyup.call(this, event);
},
@@ -1303,8 +1459,11 @@ var Events = Module("events", {
let elem = event.target;
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
- for (; win; win = win != win.parent && win.parent)
+ for (; win; win = win != win.parent && win.parent) {
+ for (; elem instanceof Element; elem = elem.parentNode)
+ elem.dactylFocusAllowed = true;
win.document.dactylFocusAllowed = true;
+ }
},
popupshown: function onPopupShown(event) {
@@ -1322,8 +1481,7 @@ var Events = Module("events", {
modes.push(modes.MENU);
},
- popuphidden: function onPopupHidden() {
- // gContextMenu is set to NULL, when a context menu is closed
+ popuphidden: function onPopupHidden(event) {
if (window.gContextMenu == null && !this._activeMenubar)
modes.remove(modes.MENU, true);
modes.remove(modes.AUTOCOMPLETE);
@@ -1406,11 +1564,15 @@ var Events = Module("events", {
if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
util.threadYield(true); // Why? --Kris
- while (modes.main.ownsFocus && !modes.topOfStack.params.holdFocus)
+ while (modes.main.ownsFocus && modes.topOfStack.params.ownsFocus != elem
+ && !modes.topOfStack.params.holdFocus)
modes.pop(null, { fromFocus: true });
}
finally {
this._lastFocus = elem;
+
+ if (modes.main.ownsFocus)
+ modes.topOfStack.params.ownsFocus = elem;
}
},
@@ -1445,20 +1607,25 @@ var Events = Module("events", {
key === "<Esc>" || key === "<C-[>",
isHidden: function isHidden(elem, aggressive) {
+ if (util.computedStyle(elem).visibility !== "visible")
+ return true;
+
+ if (aggressive)
for (let e = elem; e instanceof Element; e = e.parentNode) {
- if (util.computedStyle(e).visibility !== "visible" ||
- aggressive && e.boxObject && e.boxObject.height === 0)
+ if (!/set$/.test(e.localName) && e.boxObject && e.boxObject.height === 0)
return true;
+ else if (e.namespaceURI == XUL && e.localName === "panel")
+ break;
}
return false;
},
isInputElement: function isInputElement(elem) {
- return elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type) ||
- isinstance(elem, [HTMLIsIndexElement, HTMLEmbedElement,
+ return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) ||
+ isinstance(elem, [HTMLEmbedElement,
HTMLObjectElement, HTMLSelectElement,
HTMLTextAreaElement,
- Ci.nsIDOMXULTreeElement, Ci.nsIDOMXULTextBoxElement]) ||
+ Ci.nsIDOMXULTextBoxElement]) ||
elem instanceof Window && Editor.getEditor(elem);
},
@@ -1480,12 +1647,13 @@ var Events = Module("events", {
else
dactyl.echoerr(_("error.argumentRequired"));
}, {
+ argCount: "?",
bang: true,
completer: function (context) completion.macro(context),
literal: 0
});
- commands.add(["macros"],
+ commands.add(["mac[ros]"],
"List all macros",
function (args) { completion.listCompleter("macro", args[0]); }, {
argCount: "?",
@@ -1501,7 +1669,7 @@ var Events = Module("events", {
mappings: function () {
mappings.add([modes.MAIN],
- ["<A-b>"], "Process the next key as a builtin mapping",
+ ["<A-b>", "<pass-next-key-builtin>"], "Process the next key as a builtin mapping",
function () {
events.processor = ProcessorStack(modes.getStack(0), mappings.hives.array, true);
events.processor.keyEvents = events.keyEvents;
@@ -1511,7 +1679,7 @@ var Events = Module("events", {
["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
function () { modes.push(modes.PASS_THROUGH); });
- mappings.add([modes.MAIN],
+ mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE],
["<C-v>", "<pass-next-key>"], "Pass through next key",
function () {
if (modes.main == modes.QUOTE)
@@ -1520,6 +1688,10 @@ var Events = Module("events", {
});
mappings.add([modes.BASE],
+ ["<CapsLock>"], "Do Nothing",
+ function () {});
+
+ mappings.add([modes.BASE],
["<Nop>"], "Do nothing",
function () {});
@@ -1594,12 +1766,12 @@ var Events = Module("events", {
"sitemap", "", {
flush: function flush() {
memoize(this, "filters", function () this.value.filter(function (f) f(buffer.documentURI)));
- memoize(this, "pass", function () set(array.flatten(this.filters.map(function (f) f.keys))));
+ memoize(this, "pass", function () Set(array.flatten(this.filters.map(function (f) f.keys))));
memoize(this, "commandHive", function hive() Hive(this.filters, "command"));
memoize(this, "inputHive", function hive() Hive(this.filters, "input"));
},
- has: function (key) set.has(this.pass, key) || set.has(this.commandHive.stack.mappings, key),
+ has: function (key) Set.has(this.pass, key) || Set.has(this.commandHive.stack.mappings, key),
get pass() (this.flush(), this.pass),
@@ -1611,7 +1783,7 @@ var Events = Module("events", {
filter.keys = events.fromString(vals[0]).map(events.closure.toString);
filter.commandKeys = vals.slice(1).map(events.closure.canonicalKeys);
- filter.inputKeys = filter.commandKeys.filter(/^<[ACM]-/);
+ filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/));
});
this.flush();
return values;
@@ -1620,7 +1792,14 @@ var Events = Module("events", {
options.add(["strictfocus", "sf"],
"Prevent scripts from focusing input elements without user intervention",
- "boolean", true);
+ "sitemap", "'chrome:*':laissez-faire,*:moderate",
+ {
+ values: {
+ despotic: "Only allow focus changes when explicitly requested by the user",
+ moderate: "Allow focus changes after user-initiated focus change",
+ "laissez-faire": "Always allow focus changes"
+ }
+ });
options.add(["timeout", "tmo"],
"Whether to execute a shorter key command after a timeout when a longer command exists",
diff --git a/common/content/help.xsl b/common/content/help.xsl
--- a/common/content/help.xsl
+++ b/common/content/help.xsl
@@ -185,7 +185,7 @@
<xsl:if test="//dactyl:toc[1 and self::*]">
<div dactyl:highlight="HelpTOC">
- <h2>Contents</h2>
+ <h2><!--L-->Contents</h2>
<xsl:if test="@start">
<xsl:call-template name="toc">
<xsl:with-param name="level" select="number(@start)"/>
@@ -240,7 +240,7 @@
</xsl:when>
<xsl:when test="contains($type, 'list') or contains($type, 'map')">
<span dactyl:highlight="HelpString" delim=""><xsl:apply-templates mode="help-1"/></span>
- <xsl:if test=". = ''">(empty)</xsl:if>
+ <xsl:if test=". = ''"><!--L-->(empty)</xsl:if>
</xsl:when>
<xsl:otherwise>
<span>
@@ -378,14 +378,14 @@
<a>
<xsl:choose>
<xsl:when test="not(@topic)"/>
- <xsl:when test="regexp:match(@topic, '^([a-zA-Z]*:|[^/]*#|/)', '')">
+ <xsl:when test="regexp:match(@topic, '^([a-zA-Z]+:|[^/]*#|/)', '')">
<xsl:attribute name="href"><xsl:value-of select="@topic"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="href"><xsl:value-of select="concat('dactyl://help-tag/', @topic)"/></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
- <xsl:if test="regexp:match(@topic, '^[a-zA-Z]*:', '')
+ <xsl:if test="regexp:match(@topic, '^[a-zA-Z]+:', '')
and not(starts-with(@topic, 'mailto:') or
starts-with(@topic, 'chrome:') or
starts-with(@topic, 'resource:') or
@@ -430,7 +430,7 @@
<xsl:template match="dactyl:deprecated" mode="help-2">
<p style="clear: both;">
<xsl:apply-templates select="@*" mode="help-1"/>
- <span dactyl:highlight="HelpWarning">Deprecated:</span>
+ <span dactyl:highlight="HelpWarning"><!--L-->Deprecated:</span>
<xsl:text> </xsl:text>
<xsl:apply-templates select="node()" mode="help-1"/>
</p>
@@ -439,7 +439,7 @@
<p style="clear: both;">
<xsl:apply-templates select="@*" mode="help-1"/>
<div style="clear: both;"/>
- <span dactyl:highlight="HelpNote">Note:</span>
+ <span dactyl:highlight="HelpNote"><!--L-->Note:</span>
<xsl:text> </xsl:text>
<xsl:apply-templates select="node()" mode="help-1"/>
</p>
@@ -448,7 +448,7 @@
<p style="clear: both;">
<xsl:apply-templates select="@*" mode="help-1"/>
<div style="clear: both;"/>
- <span dactyl:highlight="HelpWarning">Warning:</span>
+ <span dactyl:highlight="HelpWarning"><!--L-->Warning:</span>
<xsl:text> </xsl:text>
<xsl:apply-templates select="node()" mode="help-1"/>
</p>
@@ -511,10 +511,21 @@
<xsl:apply-templates select="@*|node()" mode="help-1"/>
</div>
</xsl:template>
- <xsl:template match="dactyl:str | dactyl:type" mode="help-2">
- <span>
- <xsl:if test="self::dactyl:str"><xsl:attribute name="dactyl:highlight">HelpString</xsl:attribute></xsl:if>
- <xsl:if test="self::dactyl:type"><xsl:attribute name="dactyl:highlight">HelpType</xsl:attribute></xsl:if>
+ <xsl:template match="dactyl:type" mode="help-2">
+ <a dactyl:highlight="HelpType">
+ <xsl:choose>
+ <xsl:when test="contains(ancestor::*/@document-tags, concat(' ', ., ' '))">
+ <xsl:attribute name="href">#<xsl:value-of select="."/></xsl:attribute>
+ </xsl:when>
+ <xsl:when test="not(. = 'boolean' or . = 'number' or . = 'string')">
+ <xsl:attribute name="href">dactyl://help-tag/<xsl:value-of select="."/></xsl:attribute>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:apply-templates select="@*|node()" mode="help-1"/>
+ </a>
+ </xsl:template>
+ <xsl:template match="dactyl:str" mode="help-2">
+ <span dactyl:highlight="HelpString">
<xsl:apply-templates select="@*|node()" mode="help-1"/>
</span>
</xsl:template>
diff --git a/common/content/hints.js b/common/content/hints.js
--- a/common/content/hints.js
+++ b/common/content/hints.js
@@ -38,12 +38,13 @@ var HintSession = Class("HintSession", C
this.open();
this.top = opts.window || content;
- this.top.addEventListener("resize", hints.resizeTimer.closure.tell, true);
- this.top.addEventListener("dactyl-commandupdate", hints.resizeTimer.closure.tell, false, true);
+ this.top.addEventListener("resize", this.closure._onResize, true);
+ this.top.addEventListener("dactyl-commandupdate", this.closure._onResize, false, true);
this.generate();
this.show();
+ this.magic = true;
if (this.validHints.length == 0) {
dactyl.beep();
@@ -51,7 +52,7 @@ var HintSession = Class("HintSession", C
}
else if (this.validHints.length == 1 && !this.continue)
this.process(false);
- else // Ticket #185
+ else
this.checkUnique();
},
@@ -69,6 +70,15 @@ var HintSession = Class("HintSession", C
hints.setClass(this.imgSpan, this.valid ? val : null);
},
+ get ambiguous() this.span.hasAttribute("ambiguous"),
+ set ambiguous(val) {
+ let meth = val ? "setAttribute" : "removeAttribute";
+ this.elem[meth]("ambiguous", "true");
+ this.span[meth]("ambiguous", "true");
+ if (this.imgSpan)
+ this.imgSpan[meth]("ambiguous", "true");
+ },
+
get valid() this._valid,
set valid(val) {
this._valid = val,
@@ -76,7 +86,6 @@ var HintSession = Class("HintSession", C
this.span.style.display = (val ? "" : "none");
if (this.imgSpan)
this.imgSpan.style.display = (val ? "" : "none");
-
this.active = this.active;
}
},
@@ -92,8 +101,8 @@ var HintSession = Class("HintSession", C
if (hints.hintSession == this)
hints.hintSession = null;
if (this.top) {
- this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true);
- this.top.removeEventListener("dactyl-commandupdate", hints.resizeTimer.closure.tell, true);
+ this.top.removeEventListener("resize", this.closure._onResize, true);
+ this.top.removeEventListener("dactyl-commandupdate", this.closure._onResize, true);
}
this.removeHints(0);
@@ -156,6 +165,21 @@ var HintSession = Class("HintSession", C
},
/**
+ * The reverse of {@link #getHintString}. Given a hint string,
+ * returns its index.
+ *
+ * @param {string} str The hint's string.
+ * @returns {number} The hint's index.
+ */
+ getHintNumber: function getHintNumber(str) {
+ let base = this.hintKeys.length;
+ let res = 0;
+ for (let char in values(str))
+ res = res * base + this.hintKeys.indexOf(char);
+ return res;
+ },
+
+ /**
* Returns true if the given key string represents a
* pseudo-hint-number.
*
@@ -255,6 +279,11 @@ var HintSession = Class("HintSession", C
let doc = win.document;
+ memoize(doc, "dactylLabels", function ()
+ iter([l.getAttribute("for"), l]
+ for (l in array.iterValues(doc.querySelectorAll("label[for]"))))
+ .toObject());
+
let [offsetX, offsetY] = this.getContainerOffsets(doc);
offsets = offsets || { left: 0, right: 0, top: 0, bottom: 0 };
@@ -263,11 +292,15 @@ var HintSession = Class("HintSession", C
function isVisible(elem) {
let rect = elem.getBoundingClientRect();
- if (!rect || !rect.width || !rect.height ||
+ if (!rect ||
rect.top > offsets.bottom || rect.bottom < offsets.top ||
rect.left > offsets.right || rect.right < offsets.left)
return false;
+ if (!rect.width || !rect.height)
+ if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && util.computedStyle(elem).float != "none" && isVisible(elem)))
+ return false;
+
let computedStyle = doc.defaultView.getComputedStyle(elem, null);
if (computedStyle.visibility != "visible" || computedStyle.display == "none")
return false;
@@ -281,17 +314,24 @@ var HintSession = Class("HintSession", C
util.computedStyle(fragment).height; // Force application of binding.
let container = doc.getAnonymousElementByAttribute(fragment, "anonid", "hints") || fragment;
- let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc);
+ let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none;"/>, doc);
let mode = this.hintMode;
let res = mode.matcher(doc);
let start = this.pageHints.length;
- for (let elem in res) {
- let hint = { elem: elem, showText: false, __proto__: this.Hint };
+ let _hints = [];
+ for (let elem in res)
+ if (isVisible(elem) && (!mode.filter || mode.filter(elem)))
+ _hints.push({
+ elem: elem,
+ rect: elem.getClientRects()[0] || elem.getBoundingClientRect(),
+ showText: false,
+ __proto__: this.Hint
+ });
- if (!isVisible(elem) || mode.filter && !mode.filter(elem))
- continue;
+ for (let hint in values(_hints)) {
+ let { elem, rect } = hint;
if (elem.hasAttributeNS(NS, "hint"))
[hint.text, hint.showText] = [elem.getAttributeNS(NS, "hint"), true];
@@ -302,17 +342,15 @@ var HintSession = Class("HintSession", C
else
hint.text = elem.textContent.toLowerCase();
- hint.span = baseNodeAbsolute.cloneNode(true);
+ hint.span = baseNodeAbsolute.cloneNode(false);
- let rect = elem.getClientRects()[0] || elem.getBoundingClientRect();
let leftPos = Math.max((rect.left + offsetX), offsetX);
let topPos = Math.max((rect.top + offsetY), offsetY);
if (elem instanceof HTMLAreaElement)
[leftPos, topPos] = this.getAreaOffset(elem, leftPos, topPos);
- hint.span.style.left = leftPos + "px";
- hint.span.style.top = topPos + "px";
+ hint.span.setAttribute("style", ["display: none; left:", leftPos, "px; top:", topPos, "px"].join(""));
container.appendChild(hint.span);
this.pageHints.push(hint);
@@ -358,7 +396,7 @@ var HintSession = Class("HintSession", C
},
/**
- * Handle a hint mode event.
+ * Handle a hints mode event.
*
* @param {Event} event The event to handle.
*/
@@ -399,12 +437,17 @@ var HintSession = Class("HintSession", C
return PASS;
},
- onResize: function () {
+ onResize: function onResize() {
this.removeHints(0);
this.generate(this.top);
this.show();
},
+ _onResize: function _onResize() {
+ if (this.magic)
+ hints.resizeTimer.tell();
+ },
+
/**
* Finish hinting.
*
@@ -420,15 +463,9 @@ var HintSession = Class("HintSession", C
// This "followhints" option is *too* confusing. For me, and
// presumably for users, too. --Kris
- if (options["followhints"] > 0) {
- if (!followFirst)
+ if (options["followhints"] > 0 && !followFirst)
return; // no return hit; don't examine uniqueness
- // OK. return hit. But there's more than one hint, and
- // there's no tab-selected current link. Do not follow in mode 2
- dactyl.assert(options["followhints"] != 2 || this.validHints.length == 1 || this.hintNumber);
- }
-
if (!followFirst) {
let firstHref = this.validHints[0].elem.getAttribute("href") || null;
if (firstHref) {
@@ -469,13 +506,14 @@ var HintSession = Class("HintSession", C
modes.push(modes.IGNORE, modes.HINTS);
}
- this.timeout(function () {
- if ((modes.extended & modes.HINTS) && !this.continue)
- modes.pop();
- commandline.lastEcho = null; // Hack.
dactyl.trapErrors("action", this.hintMode,
elem, elem.href || elem.src || "",
this.extendedhintCount, top);
+
+ this.timeout(function () {
+ if (modes.main == modes.IGNORE && !this.continue)
+ modes.pop();
+ commandline.lastEcho = null; // Hack.
if (this.continue && this.top)
this.show();
}, timeout);
@@ -491,11 +529,16 @@ var HintSession = Class("HintSession", C
*/
removeHints: function _removeHints(timeout) {
for (let { doc, start, end } in values(this.docs)) {
+ // Goddamn stupid fucking Gecko 1.x security manager bullshit.
+ try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; }
+
for (let elem in util.evaluateXPath("//*[@dactyl:highlight='hints']", doc))
elem.parentNode.removeChild(elem);
- for (let i in util.range(start, end + 1))
+ for (let i in util.range(start, end + 1)) {
+ this.pageHints[i].ambiguous = false;
this.pageHints[i].valid = false;
}
+ }
styles.system.remove("hint-positions");
this.reset();
@@ -561,19 +604,25 @@ var HintSession = Class("HintSession", C
text.push(UTF8(hint.elem.checked ? "⊙" : "○"));
else if (hint.elem.type === "checkbox")
text.push(UTF8(hint.elem.checked ? "☑" : "☐"));
- if (hint.showText)
+ if (hint.showText && !/^\s*$/.test(hint.text))
text.push(hint.text.substr(0, 50));
hint.span.setAttribute("text", str + (text.length ? ": " + text.join(" ") : ""));
hint.span.setAttribute("number", str);
if (hint.imgSpan)
hint.imgSpan.setAttribute("number", str);
+
hint.active = activeHint == hintnum;
+
this.validHints.push(hint);
hintnum++;
}
}
+ let base = this.hintKeys.length;
+ for (let [i, hint] in Iterator(this.validHints))
+ hint.ambiguous = (i + 1) * base <= this.validHints.length;
+
if (options["usermode"]) {
let css = [];
for (let hint in values(this.pageHints)) {
@@ -685,16 +734,15 @@ var Hints = Module("hints", {
events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false);
const Mode = Hints.Mode;
- Mode.defaultValue("tags", function () function () options.get("hinttags").matcher);
Mode.prototype.__defineGetter__("matcher", function ()
- options.get("extendedhinttags").getKey(this.name, this.tags()));
+ options.get("extendedhinttags").getKey(this.name, options.get("hinttags").matcher));
this.modes = {};
this.addMode(";", "Focus hint", buffer.closure.focusElement);
this.addMode("?", "Show information for hint", function (elem) buffer.showElementInfo(elem));
this.addMode("s", "Save hint", function (elem) buffer.saveLink(elem, false));
this.addMode("f", "Focus frame", function (elem) dactyl.focus(elem.ownerDocument.defaultView));
- this.addMode("F", "Focus frame or pseudo-frame", buffer.closure.focusElement, null, isScrollable);
+ this.addMode("F", "Focus frame or pseudo-frame", buffer.closure.focusElement, isScrollable);
this.addMode("o", "Follow hint", function (elem) buffer.followLink(elem, dactyl.CURRENT_TAB));
this.addMode("t", "Follow hint in a new tab", function (elem) buffer.followLink(elem, dactyl.NEW_TAB));
this.addMode("b", "Follow hint in a background tab", function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB));
@@ -719,21 +767,35 @@ var Hints = Module("hints", {
hintSession: Modes.boundProperty(),
/**
- * Creates a new hint mode.
+ * Creates a new hints mode.
*
* @param {string} mode The letter that identifies this mode.
* @param {string} prompt The description to display to the user
* about this mode.
* @param {function(Node)} action The function to be called with the
* element that matches.
- * @param {function():string} tags The function that returns an
- * XPath expression to decide which elements can be hinted (the
- * default returns options["hinttags"]).
+ * @param {function(Node):boolean} filter A function used to filter
+ * the returned node set.
+ * @param {[string]} tags A value to add to the default
+ * 'extendedhinttags' value for this mode.
* @optional
*/
- addMode: function (mode, prompt, action, tags) {
- arguments[1] = UTF8(prompt);
- this.modes[mode] = Hints.Mode.apply(Hints.Mode, arguments);
+ addMode: function (mode, prompt, action, filter, tags) {
+ function toString(regexp) RegExp.prototype.toString.call(regexp);
+
+ if (tags != null) {
+ let eht = options.get("extendedhinttags");
+ let update = eht.isDefault;
+
+ let value = eht.parse(Option.quote(util.regexp.escape(mode)) + ":" + tags.map(Option.quote))[0];
+ eht.defaultValue = eht.defaultValue.filter(function (re) toString(re) != toString(value))
+ .concat(value);
+
+ if (update)
+ eht.reset();
+ }
+
+ this.modes[mode] = Hints.Mode(mode, UTF8(prompt), action, filter);
},
/**
@@ -762,7 +824,7 @@ var Hints = Module("hints", {
let type = elem.type;
- if (elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type))
+ if (elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type))
return [elem.value, false];
else {
for (let [, option] in Iterator(options["hintinputs"])) {
@@ -776,15 +838,14 @@ var Hints = Module("hints", {
return [elem.alt.toLowerCase(), true];
}
else if (elem.value && type != "password") {
- // radio's and checkboxes often use internal ids as values - maybe make this an option too...
+ // radios and checkboxes often use internal ids as values - maybe make this an option too...
if (! ((type == "radio" || type == "checkbox") && !isNaN(elem.value)))
return [elem.value.toLowerCase(), (type == "radio" || type == "checkbox")];
}
}
else if (option == "label") {
if (elem.id) {
- // TODO: (possibly) do some guess work for label-like objects
- let label = util.evaluateXPath(["label[@for=" + elem.id.quote() + "]"], doc).snapshotItem(0);
+ let label = elem.ownerDocument.dactylLabels[elem.id];
if (label)
return [label.textContent.toLowerCase(), true];
}
@@ -971,6 +1032,7 @@ var Hints = Module("hints", {
open: function open(mode, opts) {
this._extendedhintCount = opts.count;
commandline.input(["Normal", mode], "", {
+ autocomplete: false,
completer: function (context) {
context.compare = function () 0;
context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints.modes))];
@@ -979,10 +1041,13 @@ var Hints = Module("hints", {
if (arg)
hints.show(arg, opts);
},
- onChange: function () {
+ onChange: function (arg) {
+ if (Object.keys(hints.modes).some(function (m) m != arg && m.indexOf(arg) == 0))
+ return;
+
this.accepted = true;
modes.pop();
- },
+ }
});
},
@@ -1112,14 +1177,14 @@ var Hints = Module("hints", {
return -1;
},
- Mode: Struct("HintMode", "name", "prompt", "action", "tags", "filter")
+ Mode: Struct("HintMode", "name", "prompt", "action", "filter")
.localize("prompt")
}, {
modes: function initModes() {
initModes.require("commandline");
modes.addMode("HINTS", {
extended: true,
- description: "Active when selecting elements in QuickHint or ExtendedHint mode",
+ description: "Active when selecting elements with hints",
bases: [modes.COMMAND_LINE],
input: true,
ownsBuffer: true
@@ -1128,20 +1193,20 @@ var Hints = Module("hints", {
mappings: function () {
var myModes = config.browserModes.concat(modes.OUTPUT_MULTILINE);
mappings.add(myModes, ["f"],
- "Start QuickHint mode",
+ "Start Hints mode",
function () { hints.show("o"); });
mappings.add(myModes, ["F"],
- "Start QuickHint mode, but open link in a new tab",
+ "Start Hints mode, but open link in a new tab",
function () { hints.show(options.get("activate").has("links") ? "t" : "b"); });
mappings.add(myModes, [";"],
- "Start an extended hint mode",
+ "Start an extended hints mode",
function ({ count }) { hints.open(";", { count: count }); },
{ count: true });
mappings.add(myModes, ["g;"],
- "Start an extended hint mode and stay there until <Esc> is pressed",
+ "Start an extended hints mode and stay there until <Esc> is pressed",
function ({ count }) { hints.open("g;", { continue: true, count: count }); },
{ count: true });
@@ -1172,7 +1237,7 @@ var Hints = Module("hints", {
"XPath or CSS selector strings of hintable elements for extended hint modes",
"regexpmap", {
"[iI]": "img",
- "[asOTivVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
+ "[asOTvVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
"[f]": "body",
"[F]": ["body", "code", "div", "html", "p", "pre", "span"],
"[S]": ["input:not([type=hidden])", "textarea", "button", "select"]
@@ -1180,7 +1245,7 @@ var Hints = Module("hints", {
{
keepQuotes: true,
getKey: function (val, default_)
- let (res = array.nth(this.value, function (re) re.test(val), 0))
+ let (res = array.nth(this.value, function (re) let (match = re.exec(val)) match && match[0] == val, 0))
res ? res.matcher : default_,
setter: function (vals) {
for (let value in values(vals))
@@ -1191,10 +1256,10 @@ var Hints = Module("hints", {
});
options.add(["hinttags", "ht"],
- "XPath string of hintable elements activated by 'f' and 'F'",
- "stringlist", "input:not([type=hidden]),a,area,iframe,textarea,button,select," +
+ "XPath or CSS selector strings of hintable elements for Hints mode",
+ "stringlist", "input:not([type=hidden]),a[href],area,iframe,textarea,button,select," +
"[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," +
- "[tabindex],[role=link],[role=button]",
+ "[tabindex],[role=link],[role=button],[contenteditable=true]",
{
setter: function (values) {
this.matcher = util.compileMatcher(values);
@@ -1213,8 +1278,8 @@ var Hints = Module("hints", {
},
validator: function (value) {
let values = events.fromString(value).map(events.closure.toString);
- return Option.validIf(array.uniq(values).length === values.length,
- "Duplicate keys not allowed");
+ return Option.validIf(array.uniq(values).length === values.length && values.length > 1,
+ _("option.hintkeys.duplicate"));
}
});
@@ -1224,15 +1289,12 @@ var Hints = Module("hints", {
{ validator: function (value) value >= 0 });
options.add(["followhints", "fh"],
- // FIXME: this description isn't very clear but I can't think of a
- // better one right now.
- "Change the behavior of <Return> in hint mode",
+ "Define the conditions under which selected hints are followed",
"number", 0,
{
values: {
"0": "Follow the first hint as soon as typed text uniquely identifies it. Follow the selected hint on <Return>.",
"1": "Follow the selected hint on <Return>.",
- "2": "Follow the selected hint on <Return> only it's been <Tab>-selected."
}
});
diff --git a/common/content/history.js b/common/content/history.js
--- a/common/content/history.js
+++ b/common/content/history.js
@@ -41,7 +41,7 @@ var History = Module("history", {
return {
url: node.uri,
title: node.title,
- icon: node.icon ? node.icon.spec : DEFAULT_FAVICON
+ icon: node.icon ? node.icon : DEFAULT_FAVICON
};
}).toArray();
root.containerOpen = false; // close a container after using it!
@@ -230,8 +230,8 @@ var History = Module("history", {
"uri",
"visitcount"
].map(function (order) [
- ["+" + order.replace(" ", ""), "Sort by " + order + " ascending"],
- ["-" + order.replace(" ", ""), "Sort by " + order + " descending"]
+ ["+" + order.replace(" ", ""), /*L*/"Sort by " + order + " ascending"],
+ ["-" + order.replace(" ", ""), /*L*/"Sort by " + order + " descending"]
]));
}
}
diff --git a/common/content/mappings.js b/common/content/mappings.js
--- a/common/content/mappings.js
+++ b/common/content/mappings.js
@@ -12,8 +12,8 @@
* A class representing key mappings. Instances are created by the
* {@link Mappings} class.
*
- * @param {number[]} modes The modes in which this mapping is active.
- * @param {string[]} keys The key sequences which are bound to
+ * @param {[Modes.Mode]} modes The modes in which this mapping is active.
+ * @param {[string]} keys The key sequences which are bound to
* *action*.
* @param {string} description A short one line description of the key mapping.
* @param {function} action The action invoked by each key sequence.
@@ -30,26 +30,21 @@
*/
var Map = Class("Map", {
init: function (modes, keys, description, action, extraInfo) {
- modes = Array.concat(modes);
- if (!modes.every(util.identity))
- throw TypeError("Invalid modes: " + modes);
-
this.id = ++Map.id;
this.modes = modes;
this._keys = keys;
this.action = action;
this.description = description;
- if (Object.freeze)
Object.freeze(this.modes);
if (extraInfo)
- update(this, extraInfo);
+ this.update(extraInfo);
},
name: Class.memoize(function () this.names[0]),
- /** @property {string[]} All of this mapping's names (key sequences). */
+ /** @property {[string]} All of this mapping's names (key sequences). */
names: Class.memoize(function () this._keys.map(function (k) events.canonicalKeys(k))),
get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)],
@@ -58,7 +53,7 @@ var Map = Class("Map", {
/** @property {number} A unique ID for this mapping. */
id: null,
- /** @property {number[]} All of the modes for which this mapping applies. */
+ /** @property {[Modes.Mode]} All of the modes for which this mapping applies. */
modes: null,
/** @property {function (number)} The function called to execute this mapping. */
action: null,
@@ -96,7 +91,7 @@ var Map = Class("Map", {
*/
hasName: function (name) this.keys.indexOf(name) >= 0,
- get keys() this.names.map(mappings.expandLeader),
+ get keys() array.flatten(this.names.map(mappings.closure.expand)),
/**
* Execute the action for this mapping.
@@ -119,7 +114,7 @@ var Map = Class("Map", {
mappings.repeat = repeat;
if (this.executing)
- util.dumpStack("Attempt to execute mapping recursively: " + args.command);
+ util.dumpStack(_("map.recursive", args.command));
dactyl.assert(!this.executing, _("map.recursive", args.command));
try {
@@ -159,10 +154,10 @@ var MapHive = Class("MapHive", Contexts.
},
/**
- * Adds a new default key mapping.
+ * Adds a new key mapping.
*
- * @param {number[]} modes The modes that this mapping applies to.
- * @param {string[]} keys The key sequences which are bound to *action*.
+ * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+ * @param {[string]} keys The key sequences which are bound to *action*.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash.
@@ -171,6 +166,10 @@ var MapHive = Class("MapHive", Contexts.
add: function (modes, keys, description, action, extra) {
extra = extra || {};
+ modes = Array.concat(modes);
+ if (!modes.every(util.identity))
+ throw TypeError(/*L*/"Invalid modes: " + modes);
+
let map = Map(modes, keys, description, action, extra);
map.definedAt = contexts.getCaller(Components.stack.caller);
map.hive = this;
@@ -307,13 +306,32 @@ var Mappings = Module("mappings", {
get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
- expandLeader: function (keyString) keyString.replace(/<Leader>/i, options["mapleader"]),
+ expandLeader: function expandLeader(keyString) keyString.replace(/<Leader>/i, function () options["mapleader"]),
+
+ prefixes: Class.memoize(function () {
+ let list = Array.map("CASM", function (s) s + "-");
+
+ return iter(util.range(0, 1 << list.length)).map(function (mask)
+ list.filter(function (p, i) mask & (1 << i)).join("")).toArray().concat("*-");
+ }),
+
+ expand: function expand(keys) {
+ keys = keys.replace(/<leader>/i, options["mapleader"]);
+ if (!/<\*-/.test(keys))
+ return keys;
+
+ return util.debrace(events.iterKeys(keys).map(function (key) {
+ if (/^<\*-/.test(key))
+ return ["<", this.prefixes, key.slice(3)];
+ return key;
+ }, this).flatten().array).map(function (k) events.canonicalKeys(k));
+ },
iterate: function (mode) {
let seen = {};
for (let hive in this.hives.iterValues())
for (let map in array(hive.getStack(mode)).iterValues())
- if (!set.add(seen, map.name))
+ if (!Set.add(seen, map.name))
yield map;
},
@@ -330,8 +348,8 @@ var Mappings = Module("mappings", {
/**
* Adds a new default key mapping.
*
- * @param {number[]} modes The modes that this mapping applies to.
- * @param {string[]} keys The key sequences which are bound to *action*.
+ * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+ * @param {[string]} keys The key sequences which are bound to *action*.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash.
@@ -352,8 +370,8 @@ var Mappings = Module("mappings", {
/**
* Adds a new user-defined key mapping.
*
- * @param {number[]} modes The modes that this mapping applies to.
- * @param {string[]} keys The key sequences which are bound to *action*.
+ * @param {[Modes.Mode]} modes The modes that this mapping applies to.
+ * @param {[string]} keys The key sequences which are bound to *action*.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash (see
@@ -369,7 +387,7 @@ var Mappings = Module("mappings", {
/**
* Returns the map from *mode* named *cmd*.
*
- * @param {number} mode The mode to search.
+ * @param {Modes.Mode} mode The mode to search.
* @param {string} cmd The map name to match.
* @returns {Map}
*/
@@ -379,9 +397,9 @@ var Mappings = Module("mappings", {
* Returns an array of maps with names starting with but not equal to
* *prefix*.
*
- * @param {number} mode The mode to search.
+ * @param {Modes.Mode} mode The mode to search.
* @param {string} prefix The map prefix string to match.
- * @returns {Map[]}
+ * @returns {[Map]}
*/
getCandidates: function (mode, prefix)
this.hives.map(function (h) h.getCandidates(mode, prefix))
@@ -389,15 +407,15 @@ var Mappings = Module("mappings", {
/**
* Lists all user-defined mappings matching *filter* for the specified
- * *modes*.
+ * *modes* in the specified *hives*.
*
- * @param {number[]} modes An array of modes to search.
- * @param {string} filter The filter string to match.
+ * @param {[Modes.Mode]} modes An array of modes to search.
+ * @param {string} filter The filter string to match. @optional
+ * @param {[MapHive]} hives The map hives to list. @optional
*/
list: function (modes, filter, hives) {
- let modeSign = "";
- modes.filter(function (m) m.char).forEach(function (m) { modeSign += m.char; });
- modes.filter(function (m) !m.char).forEach(function (m) { modeSign += " " + m.name; });
+ let modeSign = modes.map(function (m) m.char || "").join("")
+ + modes.map(function (m) !m.char ? " " + m.name : "").join("");
modeSign = modeSign.replace(/^ /, "");
hives = (hives || mappings.userHives).map(function (h) [h, maps(h)])
@@ -413,9 +431,9 @@ var Mappings = Module("mappings", {
let list = <table>
<tr highlight="Title">
<td/>
- <td style="padding-right: 1em;">Mode</td>
- <td style="padding-right: 1em;">Command</td>
- <td style="padding-right: 1em;">Action</td>
+ <td style="padding-right: 1em;">{_("title.Mode")}</td>
+ <td style="padding-right: 1em;">{_("title.Command")}</td>
+ <td style="padding-right: 1em;">{_("title.Action")}</td>
</tr>
<col style="min-width: 6em; padding-right: 1em;"/>
{
@@ -459,6 +477,11 @@ var Mappings = Module("mappings", {
return;
}
+ if (args[1] && !/^<nop>$/i.test(args[1])
+ && !args["-count"] && !args["-ex"] && !args["-javascript"]
+ && mapmodes.every(function (m) m.count))
+ args[1] = "<count>" + args[1];
+
let [lhs, rhs] = args;
if (noremap)
args["-builtin"] = true;
@@ -474,7 +497,7 @@ var Mappings = Module("mappings", {
contexts.bindMacro(args, "-keys", function (params) params),
{
arg: args["-arg"],
- count: args["-count"],
+ count: args["-count"] || !(args["-ex"] || args["-javascript"]),
noremap: args["-builtin"],
persist: !args["-nopersist"],
get rhs() String(this.action),
@@ -484,6 +507,7 @@ var Mappings = Module("mappings", {
}
const opts = {
+ identifier: "map",
completer: function (context, args) {
let mapmodes = array.uniq(args["-modes"].map(findMode));
if (args.length == 1)
@@ -513,7 +537,7 @@ var Mappings = Module("mappings", {
{
names: ["-description", "-desc", "-d"],
description: "A description of this mapping",
- default: "User-defined mapping",
+ default: /*L*/"User-defined mapping",
type: CommandOption.STRING
},
{
@@ -569,7 +593,7 @@ var Mappings = Module("mappings", {
let seen = {};
for (let stack in values(hive.stacks))
for (let map in array.iterValues(stack))
- if (!set.add(seen, map.id))
+ if (!Set.add(seen, map.id))
yield map;
}
@@ -606,6 +630,7 @@ var Mappings = Module("mappings", {
dactyl.echoerr(_("map.noSuch", args[0]));
},
{
+ identifier: "unmap",
argCount: "?",
bang: true,
completer: opts.completer,
@@ -627,7 +652,7 @@ var Mappings = Module("mappings", {
validator: function (value) Array.concat(value).every(findMode),
completer: function () [[array.compact([mode.name.toLowerCase().replace(/_/g, "-"), mode.char]), mode.description]
for (mode in values(modes.all))
- if (!mode.hidden)],
+ if (!mode.hidden)]
};
function findMode(name) {
@@ -641,7 +666,9 @@ var Mappings = Module("mappings", {
function uniqueModes(modes) {
let chars = [k for ([k, v] in Iterator(modules.modes.modeChars))
if (v.every(function (mode) modes.indexOf(mode) >= 0))];
- return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0).concat(chars));
+ return array.uniq(modes.filter(function (m) chars.indexOf(m.char) < 0)
+ .map(function (m) m.name.toLowerCase())
+ .concat(chars));
}
commands.add(["feedkeys", "fk"],
@@ -664,14 +691,14 @@ var Mappings = Module("mappings", {
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()]);
+ mode.displayName);
let args = {
getMode: function (args) findMode(args["-mode"]),
iterate: function (args, mainOnly) {
let modes = [this.getMode(args)];
if (!mainOnly)
- modes = modes.concat(modes[0].bases);
+ modes = modes[0].allBases;
let seen = {};
// Bloody hell. --Kris
@@ -679,7 +706,7 @@ var Mappings = Module("mappings", {
for (let hive in mappings.hives.iterValues())
for (let map in array.iterValues(hive.getStack(mode)))
for (let name in values(map.names))
- if (!set.add(seen, name)) {
+ if (!Set.add(seen, name)) {
yield {
name: name,
columns: [
@@ -725,8 +752,8 @@ var Mappings = Module("mappings", {
tags = services["dactyl:"].HELP_TAGS)
({ helpTag: prefix + map.name, __proto__: map }
for (map in self.iterate(args, true))
- if (map.hive === mappings.builtin || set.has(tags, prefix + map.name))),
- description: "List all " + mode.name + " mode mappings along with their short descriptions",
+ if (map.hive === mappings.builtin || Set.has(tags, prefix + map.name))),
+ description: "List all " + mode.displayName + " mode mappings along with their short descriptions",
index: mode.char + "-map",
getMode: function (args) mode,
options: []
diff --git a/common/content/marks.js b/common/content/marks.js
--- a/common/content/marks.js
+++ b/common/content/marks.js
@@ -53,14 +53,14 @@ var Marks = Module("marks", {
if (Marks.isURLMark(mark)) {
let res = this._urlMarks.set(mark, { location: doc.documentURI, position: position, tab: Cu.getWeakReference(tabs.getTab()), timestamp: Date.now()*1000 });
if (!silent)
- dactyl.log("Adding URL mark: " + Marks.markToString(mark, res), 5);
+ dactyl.log(_("mark.addURL", Marks.markToString(mark, res)), 5);
}
else if (Marks.isLocalMark(mark)) {
let marks = this._localMarks.get(doc.documentURI, {});
marks[mark] = { location: doc.documentURI, position: position, timestamp: Date.now()*1000 };
this._localMarks.changed();
if (!silent)
- dactyl.log("Adding local mark: " + Marks.markToString(mark, marks[mark]), 5);
+ dactyl.log(_("mark.addLocal", Marks.markToString(mark, marks[mark])), 5);
}
},
@@ -115,7 +115,7 @@ var Marks = Module("marks", {
tabs.select(tab);
let doc = tab.linkedBrowser.contentDocument;
if (doc.documentURI == mark.location) {
- dactyl.log("Jumping to URL mark: " + Marks.markToString(char, mark), 5);
+ dactyl.log(_("mark.jumpingToURL", Marks.markToString(char, mark)), 5);
buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
}
else {
@@ -146,7 +146,7 @@ var Marks = Module("marks", {
let mark = (this._localMarks.get(this.localURI) || {})[char];
dactyl.assert(mark, _("mark.unset", char));
- dactyl.log("Jumping to local mark: " + Marks.markToString(char, mark), 5);
+ dactyl.log(_("mark.jumpingToLocal", Marks.markToString(char, mark)), 5);
buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
}
else
@@ -249,7 +249,7 @@ var Marks = Module("marks", {
"Mark current location within the web page",
function (args) {
let mark = args[0] || "";
- dactyl.assert(mark.length <= 1, _("error.trailing"));
+ dactyl.assert(mark.length <= 1, _("error.trailingCharacters"));
dactyl.assert(/[a-zA-Z]/.test(mark), _("mark.invalid"));
marks.add(mark);
@@ -270,7 +270,6 @@ var Marks = Module("marks", {
completion.mark = function mark(context) {
function percent(i) Math.round(i * 100);
- // FIXME: Line/Column doesn't make sense with %
context.title = ["Mark", "HPos VPos File"];
context.keys.description = function ([, m]) percent(m.position.x) + "% " + percent(m.position.y) + "% " + m.location;
context.completions = marks.all;
diff --git a/common/content/modes.js b/common/content/modes.js
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -62,8 +62,7 @@ var Modes = Module("modes", {
description: "Active when text is selected",
display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : ""),
bases: [this.COMMAND],
- ownsFocus: true,
- passUnknown: false
+ ownsFocus: true
}, {
leave: function (stack, newMode) {
if (newMode.main == modes.CARET) {
@@ -77,7 +76,7 @@ var Modes = Module("modes", {
});
this.addMode("CARET", {
description: "Active when the caret is visible in the web content",
- bases: [this.COMMAND]
+ bases: [this.NORMAL]
}, {
get pref() prefs.get("accessibility.browsewithcaret"),
@@ -99,13 +98,22 @@ var Modes = Module("modes", {
char: "t",
description: "Vim-like editing of input elements",
bases: [this.COMMAND],
- input: true,
- ownsFocus: true,
- passUnknown: false
+ ownsFocus: true
+ }, {
+ onKeyPress: function (eventList) {
+ const KILL = false, PASS = true;
+
+ // Hack, really.
+ if (eventList[0].charCode || /^<(?:.-)*(?:BS|Del|C-h|C-w|C-u|C-k)>$/.test(events.toString(eventList[0]))) {
+ dactyl.beep();
+ return KILL;
+ }
+ return PASS;
+ }
});
this.addMode("OUTPUT_MULTILINE", {
description: "Active when the multi-line output buffer is open",
- bases: [this.COMMAND],
+ bases: [this.NORMAL]
});
this.addMode("INPUT", {
@@ -199,6 +207,52 @@ var Modes = Module("modes", {
}
});
+
+ function makeTree() {
+ let list = modes.all.filter(function (m) m.name !== m.description);
+
+ let tree = {};
+
+ for (let mode in values(list))
+ tree[mode.name] = {};
+
+ for (let mode in values(list))
+ for (let base in values(mode.bases))
+ tree[base.name][mode.name] = tree[mode.name];
+
+ let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
+
+ default xml namespace = NS;
+ function rec(obj) {
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+
+ let res = <ul dactyl:highlight="Dense" xmlns:dactyl={NS}/>;
+ Object.keys(obj).sort().forEach(function (name) {
+ let mode = modes.getMode(name);
+ res.* += <li><em>{mode.displayName}</em>: {mode.description}{
+ rec(obj[name])
+ }</li>;
+ });
+
+ if (res.*.length())
+ return res;
+ return <></>;
+ }
+
+ return rec(roots);
+ }
+
+ util.timeout(function () {
+ // Waits for the add-on to become available, if necessary.
+ config.addon;
+ config.version;
+
+ services["dactyl:"].pages["modes.dtd"] = services["dactyl:"].pages["modes.dtd"]();
+ });
+
+ services["dactyl:"].pages["modes.dtd"] = function () [null,
+ util.makeDTD(iter({ "modes.tree": makeTree() },
+ config.dtd))];
},
cleanup: function cleanup() {
modes.reset();
@@ -214,7 +268,7 @@ var Modes = Module("modes", {
let val = this._modeMap[this._main].display();
if (val)
- return "-- " + val + " --" + macromode;;
+ return "-- " + val + " --" + macromode;
return macromode;
},
@@ -245,7 +299,7 @@ var Modes = Module("modes", {
if (!mode.extended)
this._mainModes.push(mode);
- dactyl.triggerObserver("mode-add", mode);
+ dactyl.triggerObserver("modes.add", mode);
},
dumpStack: function dumpStack() {
@@ -270,9 +324,13 @@ var Modes = Module("modes", {
// show the current mode string in the command line
show: function show() {
+ if (!loaded.modes)
+ return;
+
let msg = null;
- if (options.get("showmode").getKey(this.main.name, true))
+ if (options.get("showmode").getKey(this.main.allBases, false))
msg = this._getModeMessage();
+
if (msg || loaded.commandline)
commandline.widgets.mode = msg || null;
},
@@ -354,6 +412,7 @@ var Modes = Module("modes", {
prev = stack && stack.pop || this.topOfStack;
if (push)
this._modeStack.push(push);
+ });
if (stack && stack.pop)
for (let { obj, prop, value, test } in values(this.topOfStack.saved))
@@ -361,16 +420,15 @@ var Modes = Module("modes", {
dactyl.trapErrors(function () { obj[prop] = value });
this.show();
- });
-
- delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
if (this.topOfStack.params.enter && prev)
dactyl.trapErrors("enter", this.topOfStack.params,
push ? { push: push } : stack || {},
prev);
- dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
+ delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
+
+ dactyl.triggerObserver("modes.change", [oldMain, oldExtended], [this._main, this._extended], stack);
this.show();
},
@@ -389,19 +447,22 @@ var Modes = Module("modes", {
while (this._modeStack.length > 1 && this.main != mode) {
let a = this._modeStack.pop();
this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params,
- update({ pop: a }, args || {}));
+ update({ pop: a },
+ args || {}));
if (mode == null)
return;
}
},
- replace: function replace(mode, oldMode) {
+ replace: function replace(mode, oldMode, args) {
while (oldMode && this._modeStack.length > 1 && this.main != oldMode)
this.pop();
if (this._modeStack.length > 1)
- this.set(mode, null, null, { push: this.topOfStack, pop: this._modeStack.pop() });
+ this.set(mode, null, null,
+ update({ push: this.topOfStack, pop: this._modeStack.pop() },
+ args || {}));
this.push(mode);
},
@@ -428,22 +489,27 @@ var Modes = Module("modes", {
init: function init(name, options, params) {
if (options.bases)
util.assert(options.bases.every(function (m) m instanceof this, this.constructor),
- "Invalid bases", true);
+ _("mode.invalidBases"), true);
- update(this, {
+ this.update({
id: 1 << Modes.Mode._id++,
+ description: name,
name: name,
params: params || {}
}, options);
},
+ description: Messages.Localized(""),
+
+ displayName: Class.memoize(function () this.name.split("_").map(util.capitalize).join(" ")),
+
isinstance: function isinstance(obj)
- this === obj || this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
+ this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
allBases: Class.memoize(function () {
- let seen = {}, res = [], queue = this.bases;
+ let seen = {}, res = [], queue = [this].concat(this.bases);
for (let mode in array.iterValues(queue))
- if (!set.add(seen, mode)) {
+ if (!Set.add(seen, mode)) {
res.push(mode);
queue.push.apply(queue, mode.bases);
}
@@ -454,8 +520,6 @@ var Modes = Module("modes", {
get count() !this.insert,
- get description() this._display,
-
_display: Class.memoize(function _display() this.name.replace("_", " ", "g")),
display: function display() this._display,
@@ -470,7 +534,9 @@ var Modes = Module("modes", {
ownsFocus: Class.memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)),
- get passUnknown() this.input,
+ passEvent: function passEvent(event) this.input && event.charCode && !(event.ctrlKey || event.altKey || event.metaKey),
+
+ passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.name)),
get mask() this,
@@ -521,7 +587,7 @@ var Modes = Module("modes", {
mappings: function initMappings() {
mappings.add([modes.BASE, modes.NORMAL],
["<Esc>", "<C-[>"],
- "Return to NORMAL mode",
+ "Return to Normal mode",
function () { modes.reset(); });
mappings.add([modes.INPUT, modes.COMMAND, modes.PASS_THROUGH, modes.QUOTE],
@@ -529,22 +595,60 @@ var Modes = Module("modes", {
"Return to the previous mode",
function () { modes.pop(); });
+ mappings.add([modes.MENU], ["<C-c>"],
+ "Leave Menu mode",
+ function () { modes.pop(); });
+
mappings.add([modes.MENU], ["<Esc>"],
"Close the current popup",
- function () {
- modes.pop();
- return Events.PASS_THROUGH;
- });
+ function () { return Events.PASS_THROUGH; });
mappings.add([modes.MENU], ["<C-[>"],
"Close the current popup",
function () { events.feedkeys("<Esc>"); });
},
options: function initOptions() {
+ let opts = {
+ completer: function completer(context, extra) {
+ if (extra.value && context.filter[0] == "!")
+ context.advance(1);
+ return completer.superapply(this, arguments);
+ },
+
+ getKey: function getKey(val, default_) {
+ if (isArray(val))
+ return (array.nth(this.value, function (v) val.some(function (m) m.name === v.mode), 0)
+ || { result: default_ }).result;
+
+ return Set.has(this.valueMap, val) ? this.valueMap[val] : default_;
+ },
+
+ setter: function (vals) {
+ &