From 70740024f9c028c1fd63e1a1850ab062ff956054 Mon Sep 17 00:00:00 2001 From: Michael Schutte Date: Sat, 8 Oct 2011 18:33:04 +0200 Subject: [PATCH] Import 1.0b7.1 supporting Firefox up to 8.* --- .hg_archival.txt | 4 - HACKING | 9 +- common/Makefile | 27 +- common/bootstrap.js | 43 +- common/components/protocols.js | 6 +- common/content/abbreviations.js | 95 ++- common/content/autocommands.js | 17 +- common/content/bookmarks.js | 48 +- common/content/browser.js | 48 +- common/content/buffer.js | 416 +++++++++---- common/content/commandline.js | 94 +-- common/content/dactyl.js | 434 ++++++++------ common/content/disable-acr.jsm | 10 +- common/content/editor.js | 146 ++--- common/content/events.js | 443 +++++++++----- common/content/help.xsl | 33 +- common/content/hints.js | 200 ++++--- common/content/history.js | 6 +- common/content/mappings.js | 269 +++++---- common/content/marks.js | 11 +- common/content/modes.js | 178 ++++-- common/content/mow.js | 20 +- common/content/quickmarks.js | 4 +- common/content/statusline.js | 90 ++- common/content/tabs.js | 315 ++++++---- common/javascript.vim | 343 +++++++++++ common/locale/en-US/autocommands.xml | 7 +- common/locale/en-US/browsing.xml | 88 +-- common/locale/en-US/buffer.xml | 53 +- common/locale/en-US/cmdline.xml | 12 +- common/locale/en-US/faq.xml | 48 +- common/locale/en-US/gui.xml | 31 +- common/locale/en-US/hints.xml | 26 +- common/locale/en-US/map.xml | 112 ++-- common/locale/en-US/messages.properties | 226 ++++++-- common/locale/en-US/options.xml | 377 +++++++----- common/locale/en-US/pattern.xml | 16 +- common/locale/en-US/privacy.xml | 4 +- common/locale/en-US/repeat.xml | 18 +- common/locale/en-US/starting.xml | 26 +- common/locale/en-US/styling.xml | 271 +++++++-- common/locale/en-US/tabs.xml | 62 +- common/locale/en-US/various.xml | 8 +- common/modules/addons.jsm | 114 ++-- common/modules/base.jsm | 296 ++++++---- common/modules/bookmarkcache.jsm | 3 +- common/modules/bootstrap.jsm | 109 +++- common/modules/commands.jsm | 180 +++--- common/modules/completion.jsm | 25 +- common/modules/config.jsm | 581 ++++++++++++------- common/modules/contexts.jsm | 83 ++- common/modules/downloads.jsm | 199 +++++-- common/modules/finder.jsm | 86 ++- common/modules/highlight.jsm | 57 +- common/modules/io.jsm | 74 +-- common/modules/javascript.jsm | 73 ++- common/modules/messages.jsm | 49 +- common/modules/options.jsm | 240 +++++--- common/modules/overlay.jsm | 12 +- common/modules/prefs.jsm | 160 +++-- common/modules/sanitizer.jsm | 110 ++-- common/modules/services.jsm | 12 +- common/modules/storage.jsm | 32 +- common/modules/styles.jsm | 68 ++- common/modules/template.jsm | 22 +- common/modules/util.jsm | 438 +++++++++++--- common/skin/dactyl.css | 5 + common/tests/functional/dactyl.jsm | 8 +- common/tests/functional/testCommands.js | 302 ++++++++-- common/tests/functional/testOptions.js | 2 +- melodactyl/content/config.js | 2 +- melodactyl/content/library.js | 6 +- melodactyl/content/player.js | 23 +- melodactyl/locale/en-US/gui.xml | 24 - melodactyl/locale/en-US/intro.xml | 2 +- melodactyl/locale/en-US/messages.properties | 10 + pentadactyl/AUTHORS | 6 +- pentadactyl/Makefile | 6 +- pentadactyl/NEWS | 46 +- pentadactyl/TODO | 38 +- pentadactyl/content/config.js | 10 +- pentadactyl/install.rdf | 8 +- pentadactyl/locale/en-US/autocommands.xml | 3 +- pentadactyl/locale/en-US/gui.xml | 41 -- pentadactyl/locale/en-US/intro.xml | 4 +- pentadactyl/locale/en-US/messages.properties | 2 + pentadactyl/locale/en-US/tutorial.xml | 22 +- teledactyl/bootstrap.js | 1 + teledactyl/content/addressbook.js | 10 +- teledactyl/content/config.js | 14 +- teledactyl/content/mail.js | 18 +- teledactyl/install.rdf | 8 +- teledactyl/locale/en-US/gui.xml | 30 - teledactyl/locale/en-US/intro.xml | 2 +- teledactyl/locale/en-US/messages.properties | 14 + 95 files changed, 5603 insertions(+), 2781 deletions(-) delete mode 100644 .hg_archival.txt create mode 100644 common/javascript.vim create mode 100644 melodactyl/locale/en-US/messages.properties delete mode 100644 pentadactyl/locale/en-US/gui.xml create mode 100644 pentadactyl/locale/en-US/messages.properties create mode 120000 teledactyl/bootstrap.js delete mode 100644 teledactyl/locale/en-US/gui.xml create mode 100644 teledactyl/locale/en-US/messages.properties diff --git a/.hg_archival.txt b/.hg_archival.txt deleted file mode 100644 index a19df6b..0000000 --- a/.hg_archival.txt +++ /dev/null @@ -1,4 +0,0 @@ -repo: 373f1649c80dea9be7b5bc9c57e8395f94f93ab1 -node: b83bb8e6d273f71b1278c4ee03376594ad6dd039 -branch: pentadactyl-1.0b6 -tag: pentadactyl-1.0b6 diff --git a/HACKING b/HACKING index 6b4ca69..76536fd 100644 --- a/HACKING +++ b/HACKING @@ -87,7 +87,7 @@ In general: Just look at the existing source code! 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 source code! 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 index 955d358..b9e8022 100644 --- 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)/\1/' 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)/\1/' 2>/dev/null) ]; \ - then echo sed -E; else echo sed -r; \ - fi) - .SILENT: #### rules @@ -121,7 +122,7 @@ install: done; \ \ profile=$$(sed 's/^$$/\#/' "$$dir/profiles.ini" | \ - awk -v"profile=$(PROFILE)" \ + awk -v "profile=$(PROFILE)" \ 'BEGIN { RS="#" } \ index($$0, "\nName=" profile "\n") { print; exit } \ !profile && /\nName=default\n/ { args["name=default"] = $$0 } \ @@ -177,11 +178,11 @@ test: xpi xpi: $(CHROME) @echo "Building XPI..." mkdir -p "$(XPI_PATH)" - + $(AWK) -v 'name=$(NAME)' -v 'suffix=$(MANGLE)' \ -f $(BASE)/process_manifest.awk \ "$(TOP)/chrome.manifest" >"$(XPI_PATH)/chrome.manifest" - + version="$(VERSION)"; \ hg root >/dev/null 2>&1 && \ case "$$version" in \ @@ -189,7 +190,7 @@ xpi: $(CHROME) esac; \ $(SED) -e 's/(em:version(>|="))([^"<]+)/\1'"$$version/" \ <"$(TOP)/install.rdf" >"$(XPI_PATH)/install.rdf" - + $(MAKE_JAR) "$(XPI)" "$(XPI_BASES)" "$(XPI_DIRS)" "$(XPI_TEXTS)" "$(XPI_BINS)" "$(XPI_FILES)" rm -r -- $(CHROME) @echo "Built XPI: $(XPI)" diff --git a/common/bootstrap.js b/common/bootstrap.js index b17cba4..ccf1007 100755 --- a/common/bootstrap.js +++ b/common/bootstrap.js @@ -29,6 +29,8 @@ const resourceProto = Services.io.getProtocolHandler("resource") 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/application;1" in Components.classes) .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 - Services.scriptloader.loadSubScript("resource://dactyl" + suffix + "/bootstrap.jsm", - Cu.import("resource://dactyl/bootstrap.jsm", global)); + 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(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 index d14b3e4..94eab5d 100644 --- 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 index 9a76fa3..71e6c93 100644 --- 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", Contexts.Hive, { 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", Contexts.Hive, { * * @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("abbreviations", { }, /** - * 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("abbreviations", { let list = - - + + + { @@ -224,7 +286,7 @@ var Abbreviations = Module("abbreviations", { // 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("abbreviations", { context.completions = group.merged.filter(fn); }; }, - commands: function () { function addAbbreviationCommands(modes, ch, modeDescription) { modes.sort(); @@ -258,14 +319,17 @@ var Abbreviations = Module("abbreviations", { 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("abbreviations", { 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("abbreviations", { } 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 index 2d960c8..beb8fc5 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -47,7 +47,7 @@ var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, { * * @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", { { - template.map(this.activeHives, function (hive) + template.map(hives, function (hive) + @@ -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 index ade210d..1ce6ee1 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -11,6 +11,10 @@ var DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png"; // 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; @@ -381,8 +403,8 @@ var Bookmarks = Module("bookmarks", { completer: function title(context, args) { let frames = buffer.allFrames(); if (!args.bang) - return [ - [win.document.title, frames.length == 1 ? "Current Location" : "Frame: " + win.location.href] + return [ + [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 index 746b46e..5a6ba5e 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -28,8 +28,11 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), "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.nsISupportsWeakReference, ModuleBase), 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.nsISupportsWeakReference, ModuleBase), 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.nsISupportsWeakReference, ModuleBase), ["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.nsISupportsWeakReference, ModuleBase), 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.nsISupportsWeakReference, ModuleBase), 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", function () { dactyl.open("~"); }); - mappings.add([modes.NORMAL], ["gh"], + mappings.add([modes.NORMAL], ["", "gh"], "Open homepage", function () { BrowserHome(); }); - mappings.add([modes.NORMAL], ["gH"], + mappings.add([modes.NORMAL], ["", "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], [""], + mappings.add([modes.MAIN], ["", ""], "Redraw the screen", function () { ex.redraw(); }); } diff --git a/common/content/buffer.js b/common/content/buffer.js index af099e7..d0c732c 100644 --- 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++, + {link.href}]; + } + + 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,12 +797,43 @@ 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 * contains at least one frame. * - * @param {number} count The number of frames to skip through. A negative + * @param {number} count The number of frames to skip through. A negative * count skips backwards. */ shiftFrameFocus: function shiftFrameFocus(count) { @@ -785,7 +885,7 @@ var Buffer = Module("buffer", { * @param {Node} elem The element to query. */ showElementInfo: function showElementInfo(elem) { - dactyl.echo(<>Element:
{util.objectToString(elem, true)}, commandline.FORCE_MULTILINE); + dactyl.echo(<>Element:
{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. - * @default The current buffer. + * @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_to_filename", io.File(arg.substr(1)).path); + 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], - [""], "Increment last number in URL", + ["", ""], "Increment last number in URL", function (args) { buffer.incrementURL(Math.max(args.count, 1)); }, { count: true }); mappings.add([modes.NORMAL], - [""], "Decrement last number in URL", + ["", ""], "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", ""], "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", ""], "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", ""], - "Start caret mode", + mappings.add([modes.NORMAL], ["i", ""], + "Start Caret mode", function () { modes.push(modes.CARET); }); - mappings.add([modes.COMMAND], [""], + mappings.add([modes.NORMAL], ["", ""], "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", ""], + mappings.add([modes.COMMAND], ["gg", "", ""], "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", ""], + mappings.add([modes.COMMAND], ["G", "", ""], "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], ["", "", "", ""], + mappings.add([modes.COMMAND], ["", "", "", ""], "Scroll up a full page", function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["", "", "", ""], + mappings.add([modes.COMMAND], [""], + "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], ["", "", ""], "Scroll down a full page", function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["]f", ""], + mappings.add([modes.NORMAL], ["]f", ""], "Focus next frame", function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["[f", ""], + mappings.add([modes.NORMAL], ["[f", ""], "Focus previous frame", function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["]]", ""], + 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], ["]]", ""], "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], ["[[", ""], + mappings.add([modes.NORMAL], ["[[", ""], "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", ""], + mappings.add([modes.NORMAL], ["gf", ""], "Toggle between rendered and source view", function () { buffer.viewSource(null, false); }); - mappings.add([modes.COMMAND], ["gF", ""], + mappings.add([modes.NORMAL], ["gF", ""], "View source with an external editor", function () { buffer.viewSource(null, true); }); - mappings.add([modes.COMMAND], ["gi", ""], + mappings.add([modes.NORMAL], ["gi", ""], "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 url() { + let url = dactyl.clipboardRead(); + dactyl.assert(url, _("error.clipboardEmpty")); + + 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 () { - let url = dactyl.clipboardRead(); - dactyl.assert(url, _("error.clipboardEmpty")); - dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true }); + dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB, background: true }); }); - mappings.add([modes.COMMAND], ["p", "", ""], + mappings.add([modes.NORMAL], ["p", "", ""], "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", ""], + mappings.add([modes.NORMAL], ["P", ""], "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", ""], + mappings.add([modes.NORMAL], ["r", ""], "Reload the current web page", function () { tabs.reload(tabs.getTab(), false); }); - mappings.add([modes.COMMAND], ["R", ""], + mappings.add([modes.NORMAL], ["R", ""], "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", "+", ""], + mappings.add([modes.NORMAL], ["zi", "+", ""], "Enlarge text zoom of current web page", function (args) { buffer.zoomIn(Math.max(args.count, 1), false); }, { count: true }); - mappings.add([modes.COMMAND], ["zm", ""], + mappings.add([modes.NORMAL], ["zm", ""], "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", "-", ""], + mappings.add([modes.NORMAL], ["zo", "-", ""], "Reduce text zoom of current web page", function (args) { buffer.zoomOut(Math.max(args.count, 1), false); }, { count: true }); - mappings.add([modes.COMMAND], ["zr", ""], + mappings.add([modes.NORMAL], ["zr", ""], "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", ""], + mappings.add([modes.NORMAL], ["zz", ""], "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", ""], + mappings.add([modes.NORMAL], ["ZI", "zI", ""], "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", ""], + mappings.add([modes.NORMAL], ["ZM", "zM", ""], "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", ""], + mappings.add([modes.NORMAL], ["ZO", "zO", ""], "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", ""], + mappings.add([modes.NORMAL], ["ZR", "zR", ""], "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", ""], + mappings.add([modes.NORMAL], ["zZ", ""], "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], ["", ""], + mappings.add([modes.NORMAL], ["", ""], "Print the current file name", function () { buffer.showPageInfo(false); }); - mappings.add([modes.COMMAND], ["g", ""], + mappings.add([modes.NORMAL], ["g", ""], "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 index 69e6a3c..cae9942 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -152,6 +152,7 @@ var CommandWidgets = Class("CommandWidgets", { return this.commandbar; } }); + this.updateVisibility(); }, addElement: function addElement(obj) { const self = this; @@ -174,7 +175,7 @@ var CommandWidgets = Class("CommandWidgets", { 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("CommandWidgets", { }); 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) {}, + + onSubmit: function (value) {}, - resetCompletions: function resetCompletions() { + resetCompletions: function CM_resetCompletions() { if (this.completions) { this.completions.context.cancelAll(); this.completions.wildIndex = -1; @@ -426,12 +428,12 @@ var CommandExMode = Class("CommandExMode", CommandMode, { 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("CommandPromptMode", CommandMode, { 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,11 +653,11 @@ 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(xml, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE); + this.echo(<>
:{this.command}
{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; + let self = { + end: "\n" + end + "\n", + callback: callback + }; + modes.push(modes.INPUT_MULTILINE, null, { - mappingSelf: { - end: "\n" + end + "\n", - callback: callback - } + 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(
-
No Completions
+
{_("completion.noCompletions")}
{ @@ -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 index 6296b9a..1a244ff 100644 --- 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.nsISupportsWeakReference, ModuleBase), { }; 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { }), /** - * @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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { }, 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { ({ 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.nsISupportsWeakReference, ModuleBase), { * @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.nsISupportsWeakReference, ModuleBase), { * 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,34 +729,38 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { let body = XML(); for (let [, context] in Iterator(plugins.contexts)) - if (context && context.INFO instanceof XML) { - let info = context.INFO; - if (info.*.@lang.length()) { - let lang = config.bestLocale(String(a) for each (a in info.*.@lang)); + 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)); - info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang); + info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang); - for each (let elem in info.NS::info) - for each (let attr in ["@name", "@summary", "@href"]) - if (elem[attr].length()) - info[attr] = elem[attr]; + for each (let elem in info.NS::info) + for each (let attr in ["@name", "@summary", "@href"]) + if (elem[attr].length()) + info[attr] = elem[attr]; + } + body +=

{info.@summary}

+ + info; } - body +=

{context.INFO.@summary}

+ - context.INFO; + } + catch (e) { + util.reportError(e); } let help = '\n' + '\n' + '\n' + - unescape(encodeURI( // UTF-8 handling hack. -

Using Plugins

+

{_("help.title.Using Plugins")}

{body} -
.toXMLString())); + .toXMLString(); fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help]; fileMap["versions"] = function () { @@ -725,6 +793,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { if (!list) res += list =
    ; let 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.nsISupportsWeakReference, ModuleBase), { } list = null; - if (level == 0 && /^.*:\n$/.test(match.par)) - res +=

    {template.linkifyHelp(par.slice(0, -1), true)}

    ; + if (level == 0 && /^.*:\n$/.test(match.par)) { + let text = par.slice(0, -1); + res +=

    {template.linkifyHelp(text, true)}

    ; + } else { let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par); res +=

    { @@ -774,6 +846,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { } } - XML.prettyPrinting = XML.ignoreWhitespace = false; return ["application/xml", '\n' + '\n' + '\n' + - unescape(encodeURI( // UTF-8 handling hack.

    {config.appName} Versions

    {body} - .toXMLString())) + .toXMLString() ]; } addTags("versions", util.httpGet("dactyl://help/versions").responseXML); @@ -806,16 +877,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { overlayMap["index"] = ['text/xml;charset=UTF-8', '\n' + - '\n' + - unescape(encodeURI( // UTF-8 handling hack. + { template.map(dactyl.indices, function ([name, iter])
    { template.map(iter(), util.identity) - }
    , <>{"\n\n"}))) + - '\n
    ']; - + }, <>{"\n\n"}) + }
    ]; addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML); + overlayMap["gui"] = ['text/xml;charset=UTF-8', + '\n' + + +
    { + template.map(config.dialogs, function ([name, val]) + (!val[2] || val[2]()) + ? <>
    {name}
    {val[0]}
    + : undefined, + <>{"\n"}) + }
    +
    ]; + + this.helpInitialized = true; } }, @@ -850,64 +932,68 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { - case Node.ELEMENT_NODE: - if (isinstance(node, [HTMLBaseElement])) - return; - - data.push("<"); data.push(node.localName); - if (node instanceof HTMLHtmlElement) - data.push(" xmlns=" + XHTML.uri.quote()); - - for (let { name, value } in array.iterValues(node.attributes)) { - if (name == "dactyl:highlight") { - set.add(styles, value); - name = "class"; - value = "hl-" + value; - } - if (name == "href") { - value = node.href; - 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); - } - if (!/^#|[\/](#|$)|^[a-z]+:/.test(value)) - value = value.replace(/(#|$)/, ".xhtml$1"); - } - if (name == "src" && value.indexOf(":") > 0) { - chromeFiles[value] = value.replace(/.*\//, ""); - value = value.replace(/.*\//, ""); + case Node.ELEMENT_NODE: + if (isinstance(node, [HTMLBaseElement])) + return; + + data.push("<"); data.push(node.localName); + if (node instanceof HTMLHtmlElement) + 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); + name = "class"; + value = "hl-" + value; + } + if (name == "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); } - data.push(" "); - data.push(name); - data.push('="'); - data.push(<>{value}.toXMLString()); - data.push('"'); + if (!/^#|[\/](#|$)|^[a-z]+:/.test(value)) + value = value.replace(/(#|$)/, ".xhtml$1"); } - if (node.localName in empty) - data.push(" />"); - else { - data.push(">"); - if (node instanceof HTMLHeadElement) - data.push(.toXMLString()); - Array.map(node.childNodes, fix); - data.push(""); + if (name == "src" && value.indexOf(":") > 0) { + chromeFiles[value] = value.replace(/.*\//, ""); + value = value.replace(/.*\//, ""); } - break; - case Node.TEXT_NODE: - data.push(<>{node.textContent}.toXMLString()); + + data.push(" ", name, '="', + <>{value}.toXMLString().replace(/"/g, """), + '"'); + } + if (node.localName in empty) + data.push(" />"); + else { + data.push(">"); + if (node instanceof HTMLHeadElement) + data.push(.toXMLString()); + Array.map(node.childNodes, fix); + data.push(""); + } + break; + case Node.TEXT_NODE: + data.push(<>{node.textContent}.toXMLString()); } } 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 = [ '\n', '\n' @@ -916,7 +1002,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { if (obj instanceof Command) { link = function (cmd) {cmd}; args = obj.parseArgs("", CompletionContext(str || "")); - spec = function (cmd) cmd + (obj.bang ? ! : <>); + spec = function (cmd) <>{ + obj.count ? count : <> + }{ + cmd + }{ + obj.bang ? ! : <> + }; } else if (obj instanceof Map) { spec = function (map) obj.count ? <>count{map} : <>{map}; @@ -969,7 +1061,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }; } else if (obj instanceof Option) { + tag = spec = function (name) <>'{name}'; link = function (opt, name) {name}; + args = { value: "", values: [] }; } XML.prettyPrinting = false; @@ -1001,7 +1095,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { { obj.description ? br +

    {template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}

    : "" }{ extraHelp ? br + extraHelp : "" }{ - !(extraHelp || obj.description) ? br +

    Sorry, no help available.

    : "" } + !(extraHelp || obj.description) ? br +

    Sorry, no help available.

    : "" }
    ; @@ -1014,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } 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.nsISupportsWeakReference, ModuleBase), { * 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { * ['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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { // 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.nsISupportsWeakReference, ModuleBase), { }, /** - * @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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { // 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(), @@ -1670,7 +1730,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { options.add(["urlseparator", "urlsep", "us"], "The regular expression used to separate multiple URLs in :open and friends", - "string", "\\|", + "string", " \\| ", { validator: function (value) RegExp(value) }); options.add(["verbose", "vbs"], @@ -1684,18 +1744,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { { setter: function (value) { prefs.safeSet("accessibility.typeaheadfind.enablesound", !value, - "See 'visualbell' option"); + _("option.visualbell.safeSet")); return value; } }); }, mappings: function () { - mappings.add([modes.MAIN], [""], + mappings.add([modes.MAIN], ["", ""], "Open the introductory help page", function () { dactyl.help(); }); - mappings.add([modes.MAIN], [""], + mappings.add([modes.MAIN], ["", ""], "Open the single, consolidated help page", function () { ex.helpall(); }); @@ -1727,7 +1787,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, { argCount: "1", - bang: true, completer: function (context) { context.ignoreCase = true; completion.dialog(context); @@ -1738,14 +1797,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { "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", @@ -1793,7 +1854,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }); commands.add(["loadplugins", "lpl"], - "Load all plugins immediately", + "Load all or matching plugins", function (args) { dactyl.loadPlugins(args.length ? args : null, args.bang); }, @@ -1802,7 +1863,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { bang: true, keepQuotes: true, serialGroup: 10, - serialize: function () [ + serialize: function () [ { command: this.name, literalArg: options["loadplugins"].join(" ") @@ -1819,6 +1880,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { "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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { commandline.commandOutput(
- ModeAbbrevReplacement{_("title.Mode")}{_("title.Abbrev")}{_("title.Replacement")}
----- Auto Commands -----
{hive.name}
- + - - - + + +
Code execution summary{_("title.Code execution summary")}
  Executed:{count}times
  Average time:{each.toFixed(2)}{eachUnits}
  Total time:{total.toFixed(2)}{totalUnits}
  {_("title.Executed")}:{count}times
  {_("title.Average time")}:{each.toFixed(2)}{eachUnits}
  {_("title.Total time")}:{total.toFixed(2)}{totalUnits}
); } else { let beforeTime = Date.now(); - method(); + func(); if (special) return; @@ -1979,7 +2049,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { 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.nsISupportsWeakReference, ModuleBase), { } // 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.nsISupportsWeakReference, ModuleBase), { 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 index 80b9032..3481cda 100644 --- 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, force) { } 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 index e1df653..d2fe322 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -1,3 +1,4 @@ +// Copyright (c) 2008-2011 Kris Maglione // Copyright (c) 2006-2009 by Martin Stubenschrott // // 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,61 +184,52 @@ 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); - 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; + 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; - 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; + while (caret < end && caret >= 0 && count--) + caret = text.indexOf(char, caret + 1); } - dactyl.beep(); - return -1; + if (count > 0) + caret = -1; + if (caret == -1) + dactyl.beep(); + 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. + * until the editor exits. */ editFileExternally: function (args, blocking) { if (!isObject(args) || args instanceof File) @@ -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() - : Editor.getEditor(document.commandDispatcher.focusedWindow); - dactyl.assert(editor); - text = Array.map(editor.rootElement.childNodes, function (e) util.domToString(e, true)).join(""); + 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(""); } 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); - if (elem instanceof Element) - return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor; try { + if (elem instanceof Element) + return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor; 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], [""], "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>", + "", "", "<*-Tab>"], + "Handled by " + config.host, + function () Events.PASS_THROUGH); + mappings.add([modes.INSERT], - ["", ""], "Expand insert mode abbreviation", + ["", ""], "Expand Insert mode abbreviation", function () { editor.expandAbbreviation(modes.INSERT); - return Events.PASS; + return Events.PASS_THROUGH; }); mappings.add([modes.INSERT], - ["", ""], "Expand insert mode abbreviation", + ["", ""], "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([""], "Return to INSERT mode", + bind([""], "Return to Insert mode", function () Events.PASS_THROUGH); - bind([""], "Return to INSERT mode", + bind([""], "Return to Insert mode", function () { events.feedkeys("", { skipmap: true }); }); bind([""], "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 + ", { + "string", 'gvim -f + +"sil! call cursor(0, )" ', { 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 index 0b279fd..117d64b 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -16,22 +16,27 @@ var ProcessorStack = Class("ProcessorStack", { 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))) - .flatten().array; + 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("ProcessorStack", { } 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("ProcessorStack", { 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); + + let processors = this.processors; + let length = 1; - if (force && this.actions.length) - this.processors.length = 0; + 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) - Events.kill(this.events[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("ProcessorStack", { 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("ProcessorStack", { if (res === Events.KILL) break; - buffer = buffer || input.inputBuffer; - if (callable(res)) actions.push(res); @@ -162,11 +194,15 @@ var ProcessorStack = Class("ProcessorStack", { 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("KeyArgProcessor", KeyProcessor, { } }); +/** + * 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); @@ -312,9 +354,12 @@ var EventHive = Class("EventHive", Contexts.Hive, { var [self, events] = [null, array.toObject([[event, callback]])]; else { [self, events] = [event, event[callback || "events"]]; - [,, capture, allowUntrusted] = arguments; + [, , 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: - - + = 0 && doc.documentElement + || doc.defaultView; + + if (target instanceof Element && !Events.isInputElement(target) && + ["", ""].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) @@ -884,10 +944,10 @@ var Events = Module("events", { if (evt_obj.keyCode == 60 || evt_obj.charCode == 60) evt_obj.charCode = evt_obj.keyCode = 60; // - evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK) - | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK) + evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK) + | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK) | (evt_obj.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK) - | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK); + | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK); out.push(evt_obj); } @@ -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 == "" || key == "" || key == "", /** - * 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 == "" || key == "" || key == "", + /** + * 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,20 +1356,19 @@ var Events = Module("events", { let ignore = false; - if (modes.main == modes.PASS_THROUGH) + if (mode.main == modes.PASS_THROUGH) ignore = !Events.isEscape(key) && key != ""; - 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(); + modes.pop(); } else if (!event.isMacro && !event.noremap && events.shouldPass(event)) ignore = true; @@ -1277,25 +1418,40 @@ var Events = Module("events", { }, keyup: function onKeyUp(event) { - this.keyEvents.push(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))); + + if (event.type === "keydown") + this.passing = pass; - events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + 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 === "" || key === "", isHidden: function isHidden(elem, aggressive) { - for (let e = elem; e instanceof Element; e = e.parentNode) { - if (util.computedStyle(e).visibility !== "visible" || - aggressive && e.boxObject && e.boxObject.height === 0) - return true; - } + if (util.computedStyle(elem).visibility !== "visible") + return true; + + if (aggressive) + for (let e = elem; e instanceof Element; e = e.parentNode) { + 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], - [""], "Process the next key as a builtin mapping", + ["", ""], "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", { ["", ""], "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], ["", ""], "Pass through next key", function () { if (modes.main == modes.QUOTE) @@ -1519,6 +1687,10 @@ var Events = Module("events", { modes.push(modes.QUOTE); }); + mappings.add([modes.BASE], + [""], "Do Nothing", + function () {}); + mappings.add([modes.BASE], [""], "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 index e70875b..a93f9a7 100644 --- a/common/content/help.xsl +++ b/common/content/help.xsl @@ -185,7 +185,7 @@
-

Contents

+

Contents

@@ -240,7 +240,7 @@ - (empty) + (empty) @@ -378,14 +378,14 @@ - + -

- Deprecated: + Deprecated:

@@ -439,7 +439,7 @@

- Note: + Note:

@@ -448,7 +448,7 @@

- Warning: + Warning:

@@ -511,10 +511,21 @@
- - - HelpString - HelpType + +
+ + + # + + + dactyl://help-tag/ + + + + + + + diff --git a/common/content/hints.js b/common/content/hints.js index bcf431c..a30ef29 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -38,12 +38,13 @@ var HintSession = Class("HintSession", CommandMode, { 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", CommandMode, { } 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", CommandMode, { 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", CommandMode, { 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", CommandMode, { 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); @@ -155,6 +164,21 @@ var HintSession = Class("HintSession", CommandMode, { return res.reverse().join(""); }, + /** + * 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", CommandMode, { 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", CommandMode, { 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", CommandMode, { util.computedStyle(fragment).height; // Force application of binding. let container = doc.getAnonymousElementByAttribute(fragment, "anonid", "hints") || fragment; - let baseNodeAbsolute = util.xmlToDom(, doc); + let baseNodeAbsolute = util.xmlToDom(, 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 }; - - if (!isVisible(elem) || mode.filter && !mode.filter(elem)) - continue; + 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 + }); + + 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", CommandMode, { 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", CommandMode, { }, /** - * 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", CommandMode, { 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,14 +463,8 @@ var HintSession = Class("HintSession", CommandMode, { // This "followhints" option is *too* confusing. For me, and // presumably for users, too. --Kris - if (options["followhints"] > 0) { - if (!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 (options["followhints"] > 0 && !followFirst) + return; // no return hit; don't examine uniqueness if (!followFirst) { let firstHref = this.validHints[0].elem.getAttribute("href") || null; @@ -457,7 +494,7 @@ var HintSession = Class("HintSession", CommandMode, { else if (n) hints.setClass(elem, n % 2); else - hints.setClass(elem, this.validHints[Math.max(0, this.hintNumber-1)].elem === elem); + hints.setClass(elem, this.validHints[Math.max(0, this.hintNumber - 1)].elem === elem); if (n--) this.timeout(next, 50); @@ -469,13 +506,14 @@ var HintSession = Class("HintSession", CommandMode, { modes.push(modes.IGNORE, modes.HINTS); } + dactyl.trapErrors("action", this.hintMode, + elem, elem.href || elem.src || "", + this.extendedhintCount, top); + this.timeout(function () { - if ((modes.extended & modes.HINTS) && !this.continue) + if (modes.main == modes.IGNORE && !this.continue) modes.pop(); commandline.lastEcho = null; // Hack. - dactyl.trapErrors("action", this.hintMode, - elem, elem.href || elem.src || "", - this.extendedhintCount, top); if (this.continue && this.top) this.show(); }, timeout); @@ -491,10 +529,15 @@ var HintSession = Class("HintSession", CommandMode, { */ 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"); @@ -561,19 +604,25 @@ var HintSession = Class("HintSession", CommandMode, { 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"]). - * @optional + * @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]; } @@ -820,7 +881,7 @@ var Hints = Module("hints", { * returns true if each set of characters typed can be found, in any * order, in the link. * - * @param {string} hintString The string typed by the user. + * @param {string} hintString The string typed by the user. * @returns {function(String):boolean} A function that takes the text * of a hint and returns true if all the (space-delimited) sets of * characters typed by the user can be found in it. @@ -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 is pressed", + "Start an extended hints mode and stay there until 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 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 .", "1": "Follow the selected hint on .", - "2": "Follow the selected hint on only it's been -selected." } }); diff --git a/common/content/history.js b/common/content/history.js index cf985d9..a4bb283 100644 --- 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 index 668d8ce..155849e 100644 --- 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); + 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.Hive, { }, /** - * 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.Hive, { 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(//i, options["mapleader"]), + expandLeader: function expandLeader(keyString) keyString.replace(//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(//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 = - - + + + { @@ -459,6 +477,11 @@ var Mappings = Module("mappings", { return; } + if (args[1] && !/^$/i.test(args[1]) + && !args["-count"] && !args["-ex"] && !args["-javascript"] + && mapmodes.every(function (m) m.count)) + args[1] = "" + 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,92 +507,93 @@ var Mappings = Module("mappings", { } const opts = { - completer: function (context, args) { - let mapmodes = array.uniq(args["-modes"].map(findMode)); - if (args.length == 1) - return completion.userMapping(context, mapmodes, args["-group"]); - if (args.length == 2) { - if (args["-javascript"]) - return completion.javascript(context); - if (args["-ex"]) - return completion.ex(context); - } + identifier: "map", + completer: function (context, args) { + let mapmodes = array.uniq(args["-modes"].map(findMode)); + if (args.length == 1) + return completion.userMapping(context, mapmodes, args["-group"]); + if (args.length == 2) { + if (args["-javascript"]) + return completion.javascript(context); + if (args["-ex"]) + return completion.ex(context); + } + }, + hereDoc: true, + literal: 1, + options: [ + { + names: ["-arg", "-a"], + description: "Accept an argument after the requisite key press", }, - hereDoc: true, - literal: 1, - options: [ - { - names: ["-arg", "-a"], - description: "Accept an argument after the requisite key press", - }, - { - names: ["-builtin", "-b"], - description: "Execute this mapping as if there were no user-defined mappings" - }, - { - names: ["-count", "-c"], - description: "Accept a count before the requisite key press" - }, - { - names: ["-description", "-desc", "-d"], - description: "A description of this mapping", - default: "User-defined mapping", - type: CommandOption.STRING - }, - { - names: ["-ex", "-e"], - description: "Execute this mapping as an Ex command rather than keys" - }, - contexts.GroupFlag("mappings"), - { - names: ["-javascript", "-js", "-j"], - description: "Execute this mapping as JavaScript rather than keys" - }, - update({}, modeFlag, { - names: ["-modes", "-mode", "-m"], - type: CommandOption.LIST, - description: "Create this mapping in the given modes", - default: mapmodes || ["n", "v"] - }), - { - names: ["-nopersist", "-n"], - description: "Do not save this mapping to an auto-generated RC file" - }, - { - names: ["-silent", "-s", "", ""], - description: "Do not echo any generated keys to the command line" - } - ], - serialize: function () { - return this.name != "map" ? [] : - array(mappings.userHives) - .filter(function (h) h.persist) - .map(function (hive) [ - { - command: "map", - options: array([ - hive.name !== "user" && ["-group", hive.name], - ["-modes", uniqueModes(map.modes)], - ["-description", map.description], - map.silent && ["-silent"]]) - - .filter(util.identity) - .toObject(), - arguments: [map.names[0]], - literalArg: map.rhs, - ignoreDefaults: true - } - for (map in userMappings(hive)) - if (map.persist) - ]) - .flatten().array; + { + names: ["-builtin", "-b"], + description: "Execute this mapping as if there were no user-defined mappings" + }, + { + names: ["-count", "-c"], + description: "Accept a count before the requisite key press" + }, + { + names: ["-description", "-desc", "-d"], + description: "A description of this mapping", + default: /*L*/"User-defined mapping", + type: CommandOption.STRING + }, + { + names: ["-ex", "-e"], + description: "Execute this mapping as an Ex command rather than keys" + }, + contexts.GroupFlag("mappings"), + { + names: ["-javascript", "-js", "-j"], + description: "Execute this mapping as JavaScript rather than keys" + }, + update({}, modeFlag, { + names: ["-modes", "-mode", "-m"], + type: CommandOption.LIST, + description: "Create this mapping in the given modes", + default: mapmodes || ["n", "v"] + }), + { + names: ["-nopersist", "-n"], + description: "Do not save this mapping to an auto-generated RC file" + }, + { + names: ["-silent", "-s", "", ""], + description: "Do not echo any generated keys to the command line" } + ], + serialize: function () { + return this.name != "map" ? [] : + array(mappings.userHives) + .filter(function (h) h.persist) + .map(function (hive) [ + { + command: "map", + options: array([ + hive.name !== "user" && ["-group", hive.name], + ["-modes", uniqueModes(map.modes)], + ["-description", map.description], + map.silent && ["-silent"]]) + + .filter(util.identity) + .toObject(), + arguments: [map.names[0]], + literalArg: map.rhs, + ignoreDefaults: true + } + for (map in userMappings(hive)) + if (map.persist) + ]) + .flatten().array; + } }; function userMappings(hive) { 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 index aedbfc0..8e56328 100644 --- 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 index 55fa19f..c498830 100644 --- 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 =
    ; + Object.keys(obj).sort().forEach(function (name) { + let mode = modes.getMode(name); + res.* +=
  • {mode.displayName}: {mode.description}{ + rec(obj[name]) + }
  • ; + }); + + 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,23 +412,23 @@ 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)) - if (!test || !test(stack, prev)) - dactyl.trapErrors(function () { obj[prop] = value }); - - this.show(); }); - delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self)); + if (stack && stack.pop) + for (let { obj, prop, value, test } in values(this.topOfStack.saved)) + if (!test || !test(stack, prev)) + dactyl.trapErrors(function () { obj[prop] = value }); + + this.show(); 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], ["", ""], - "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], [""], + "Leave Menu mode", + function () { modes.pop(); }); + mappings.add([modes.MENU], [""], "Close the current popup", - function () { - modes.pop(); - return Events.PASS_THROUGH; - }); + function () { return Events.PASS_THROUGH; }); mappings.add([modes.MENU], [""], "Close the current popup", function () { events.feedkeys(""); }); }, 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) { + modes.all.forEach(function (m) { delete m.passUnknown }); + + vals = vals.map(function (v) update(new String(v.toLowerCase()), { + mode: v.replace(/^!/, "").toUpperCase(), + result: v[0] !== "!" + })); + + this.valueMap = values(vals).map(function (v) [v.mode, v.result]).toObject(); + return vals; + }, + + validator: function validator(vals) vals.map(function (v) v.replace(/^!/, "")).every(Set.has(this.values)), + + get values() array.toObject([[m.name.toLowerCase(), m.description] for (m in values(modes._modes)) if (!m.hidden)]) + }; + + options.add(["passunknown", "pu"], + "Pass through unknown keys in these modes", + "stringlist", "!text_edit,base", + opts); + options.add(["showmode", "smd"], "Show the current mode in the command line when it matches this expression", - "regexplist", "!^normal$", - { regexpFlags: "i" }); + "stringlist", "caret,output_multiline,!normal,base", + opts); }, prefs: function initPrefs() { prefs.watch("accessibility.browsewithcaret", function () modes.onCaretChange.apply(modes, arguments)); diff --git a/common/content/mow.js b/common/content/mow.js index 9ace65a..5c7a28c 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -53,16 +53,16 @@ var MOW = Module("mow", { @@ -131,8 +131,8 @@ var MOW = Module("mow", { catch (e) { util.reportError(e); util.dump(data); - this.messages.push(data); } + this.messages.push(data); } else { let style = isString(data) ? "pre" : "nowrap"; @@ -278,11 +278,11 @@ var MOW = Module("mow", { let elem = this.widget.contentDocument.documentElement; if (showHelp) - this.widgets.message = ["MoreMsg", "-- More -- SPACE//j: screen/page/line down, //k: up, q: quit"]; + this.widgets.message = ["MoreMsg", _("mow.moreHelp")]; else if (force || (options["more"] && Buffer.isScrollable(elem, 1))) - this.widgets.message = ["MoreMsg", "-- More --"]; + this.widgets.message = ["MoreMsg", _("mow.more")]; else - this.widgets.message = ["Question", "Press ENTER or type command to continue"]; + this.widgets.message = ["Question", _("mow.continue")]; }, visible: Modes.boundProperty({ @@ -311,7 +311,7 @@ var MOW = Module("mow", { mappings.add([modes.COMMAND], ["g"], "Redisplay the last command output", function () { - dactyl.assert(commandline.lastOutput, _("mow.noPreviousOutput")); + dactyl.assert(mow.lastOutput, _("mow.noPreviousOutput")); mow.echo(mow.lastOutput, "Normal"); }); @@ -393,3 +393,5 @@ var MOW = Module("mow", { "boolean", true); } }); + +// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js index c12e768..74a34c7 100644 --- a/common/content/quickmarks.js +++ b/common/content/quickmarks.js @@ -35,7 +35,7 @@ var QuickMarks = Module("quickmarks", { * Returns a list of QuickMarks associates with the given URL. * * @param {string} url The url to find QuickMarks for. - * @return {[string]} + * @returns {[string]} */ find: function find(url) { let res = []; @@ -157,7 +157,7 @@ var QuickMarks = Module("quickmarks", { context.fork("current", 0, this, function (context) { context.title = ["Extra Completions"]; context.completions = [ - [quickmarks.get(args[0]), "Current Value"] + [quickmarks.get(args[0]), _("option.currentValue")] ].filter(function ([k, v]) k); }); context.fork("url", 0, completion, "url"); diff --git a/common/content/statusline.js b/common/content/statusline.js index 5494342..b5dbee7 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -26,25 +26,28 @@ var StatusLine = Module("statusline", { util.overlayWindow(window, { append: <> }); highlight.loadCSS(util.compileMacro( } - !AddonButton;#addon-bar xul|toolbarbutton { + !AddonButton;#addon-bar xul|toolbarbutton { + /* An Add-on Bar button */ -moz-appearance: none !important; padding: 0 !important; border-width: 0px !important; min-width: 0 !important; color: inherit !important; } - AddonButton:not(:hover) background: transparent !important; + AddonButton:not(:hover) background: transparent; ]]>)({ padding: util.OS.isMacOSX ? "padding-right: 10px !important;" : "" })); if (document.getElementById("appmenu-button")) highlight.loadCSS(); } @@ -60,7 +63,7 @@ var StatusLine = Module("statusline", { @@ -96,7 +99,7 @@ var StatusLine = Module("statusline", { signals: { "browser.locationChange": function (webProgress, request, uri) { let win = webProgress.DOMWindow; - this.status = buffer.uri; + this.status = uri; this.progress = uri && win && win.dactylProgress || ""; // if this is not delayed we get the position of the old buffer @@ -142,12 +145,14 @@ var StatusLine = Module("statusline", { this.progress = 0; if (flags & Ci.nsIWebProgressListener.STATE_STOP) { this.progress = ""; - this.status = buffer.uri; + this.updateStatus(); } }, "browser.statusChange": function onStatusChange(webProgress, request, status, message) { - this.status = message || buffer.uri; - }, + this.timeout(function () { + this.status = message || buffer.uri; + }); + } }, /** @@ -175,7 +180,7 @@ var StatusLine = Module("statusline", { // update all fields of the statusline update: function update() { - this.status = buffer.uri; + this.updateStatus(); this.inputBuffer = ""; this.progress = ""; this.updateTabCount(); @@ -183,34 +188,8 @@ var StatusLine = Module("statusline", { this.updateZoomLevel(); }, - // ripped from Firefox; modified - unsafeURI: util.regexp(String.replace(, /U/g, "\\u"), - "gx"), - losslessDecodeURI: function losslessDecodeURI(url) { - return url.split("%25").map(function (url) { - // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors. - try { - return decodeURI(url).replace(this.unsafeURI, encodeURIComponent); - } - catch (e) { - return url; - } - }, this).join("%25"); - }, + unsafeURI: deprecated("util.unsafeURI", { get: function unsafeURI() util.unsafeURI }), + losslessDecodeURI: deprecated("util.losslessDecodeURI", function losslessDecodeURI() util.losslessDecodeURI.apply(util, arguments)), /** * Update the URL displayed in the status line. Also displays status @@ -230,29 +209,26 @@ var StatusLine = Module("statusline", { if (uri.equals(buffer.uri) && window.getWebNavigation) { let sh = window.getWebNavigation().sessionHistory; if (sh && sh.index > 0) - modified += "+"; - if (sh && sh.index < sh.count - 1) modified += "-"; - } - - if (modules.bookmarkcache) { - if (bookmarkcache.isBookmarked(uri)) + if (sh && sh.index < sh.count - 1) + modified += "+"; + if (this.bookmarked) modified += UTF8("❤"); } if (modules.quickmarks) modified += quickmarks.find(uri.spec.replace(/#.*/, "")).join(""); - url = this.losslessDecodeURI(uri.spec); + url = util.losslessDecodeURI(uri.spec); } if (url == "about:blank") { if (!buffer.title) - url = "[No Name]"; + url = _("buffer.noName"); } else { - url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " [Help]") - .replace(RegExp("^dactyl://help/(\\S+)"), "$1 [Help]"); + url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " " + _("buffer.help")) + .replace(RegExp("^dactyl://help/(\\S+)"), "$1 " + _("buffer.help")); } if (modified) @@ -260,10 +236,20 @@ var StatusLine = Module("statusline", { this.widgets.url.value = url; this._status = uri; + }, + get bookmarked() this._bookmarked, + set bookmarked(val) { + this._bookmarked = val; + if (this.status) + this.status = this.status; }, - updateStatus: function updateStatus() { this.status = buffer.uri; }, + updateStatus: function updateStatus() { + this.timeout(function () { + this.status = buffer.uri; + }); + }, updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri }), @@ -291,15 +277,15 @@ var StatusLine = Module("statusline", { */ progress: Modes.boundProperty({ get: function progress() this._progress, - set: function progress(progress) { + set: function progress(progress) { this._progress = progress || ""; - if (typeof progress == "string") + if (isinstance(progress, ["String", _])) this.widgets.progress.value = this._progress; else if (typeof progress == "number") { let progressStr = ""; if (this._progress <= 0) - progressStr = "[ Loading... ]"; + progressStr = /*L*/"[ Loading... ]"; else if (this._progress < 1) { let progress = Math.round(this._progress * 20); progressStr = "[" diff --git a/common/content/tabs.js b/common/content/tabs.js index f8584da..c621080 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -19,6 +19,8 @@ var Tabs = Module("tabs", { this._lastBufferSwitchArgs = ""; this._lastBufferSwitchSpecial = true; + this.xulTabs = document.getElementById("tabbrowser-tabs"); + // hide tabs initially to prevent flickering when 'stal' would hide them // on startup if (config.hasTabbrowser) @@ -42,7 +44,17 @@ var Tabs = Module("tabs", { for (let { linkedBrowser: { contentDocument } } in values(this.allTabs)) if (contentDocument.readyState === "complete") dactyl.initDocument(contentDocument); - }); + }, 1000); + + if (window.TabsInTitlebar) + window.TabsInTitlebar.allowedBy("dactyl", false); + }, + + signals: { + enter: function enter() { + if (window.TabsInTitlebar) + window.TabsInTitlebar.allowedBy("dactyl", true); + } }, _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]), @@ -56,7 +68,7 @@ var Tabs = Module("tabs", { } }, - updateTabCount: function () { + updateTabCount: function updateTabCount() { for (let [i, tab] in Iterator(this.visibleTabs)) { if (dactyl.has("Gecko2")) { let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_); @@ -80,7 +92,7 @@ var Tabs = Module("tabs", { statusline.updateTabCount(true); }, - _onTabSelect: function () { + _onTabSelect: function _onTabSelect() { // TODO: is all of that necessary? // I vote no. --Kris modes.reset(); @@ -103,7 +115,8 @@ var Tabs = Module("tabs", { get browsers() { let browsers = config.tabbrowser.browsers; for (let i = 0; i < browsers.length; i++) - yield [i, browsers[i]]; + if (browsers[i] !== undefined) // Bug in Google's Page Speed add-on. + yield [i, browsers[i]]; }, /** @@ -135,7 +148,7 @@ var Tabs = Module("tabs", { // property doesn't. And the property is so oft-used that it's // convenient. To the former question, because I think this is mainly // useful for autocommands, and they get index arguments. --Kris - getLocalStore: function (tabIndex) { + getLocalStore: function getLocalStore(tabIndex) { let tab = this.getTab(tabIndex); if (!tab.dactylStore) tab.dactylStore = {}; @@ -149,10 +162,10 @@ var Tabs = Module("tabs", { get localStore() this.getLocalStore(), /** - * @property {Object[]} The array of closed tabs for the current + * @property {[Object]} The array of closed tabs for the current * session. */ - get closedTabs() services.json.decode(services.sessionStore.getClosedTabData(window)), + get closedTabs() JSON.parse(services.sessionStore.getClosedTabData(window)), /** * Clones the specified *tab* and append it to the tab list. @@ -160,8 +173,8 @@ var Tabs = Module("tabs", { * @param {Object} tab The tab to clone. * @param {boolean} activate Whether to select the newly cloned tab. */ - cloneTab: function (tab, activate) { - let newTab = config.tabbrowser.addTab(); + cloneTab: function cloneTab(tab, activate) { + let newTab = config.tabbrowser.addTab("about:blank", { ownerTab: tab }); Tabs.copyTab(newTab, tab); if (activate) @@ -176,7 +189,7 @@ var Tabs = Module("tabs", { * * @param {Object} tab The tab to detach. */ - detachTab: function (tab) { + detachTab: function detachTab(tab) { if (!tab) tab = config.tabbrowser.mTabContainer.selectedItem; @@ -191,7 +204,7 @@ var Tabs = Module("tabs", { * document. */ // FIXME: Only called once...necessary? - getContentIndex: function (content) { + getContentIndex: function getContentIndex(content) { for (let [i, browser] in this.browsers) { if (browser.contentWindow == content || browser.contentDocument == content) return i; @@ -206,7 +219,7 @@ var Tabs = Module("tabs", { * * @returns {Window} */ - getGroups: function () { + getGroups: function getGroups() { if ("_groups" in this) return this._groups; @@ -225,13 +238,15 @@ var Tabs = Module("tabs", { * if *index* is not specified. This is a 0-based index. * * @param {number|Node} index The index of the tab required or the tab itself + * @param {boolean} visible If true, consider only visible tabs rather than + * all tabs. * @returns {Object} */ - getTab: function (index) { + getTab: function getTab(index, visible) { if (index instanceof Node) return index; if (index != null) - return config.tabbrowser.mTabs[index]; + return this[visible ? "visibleTabs" : "allTabs"][index]; return config.tabbrowser.mCurrentTab; }, @@ -243,7 +258,7 @@ var Tabs = Module("tabs", { * @param {boolean} visible Whether to consider only visible tabs. * @returns {number} */ - index: function (tab, visible) { + index: function index(tab, visible) { let tabs = this[visible ? "visibleTabs" : "allTabs"]; return tabs.indexOf(tab || config.tabbrowser.mCurrentTab); }, @@ -256,24 +271,25 @@ var Tabs = Module("tabs", { * - "-3" for the tab, which is 3 positions left of the current * - "$" for the last tab */ - indexFromSpec: function (spec, wrap) { + indexFromSpec: function indexFromSpec(spec, wrap, offset) { if (spec instanceof Node) return this.allTabs.indexOf(spec); let tabs = this.visibleTabs; let position = this.index(null, true); - if (spec == null || spec === "") + if (spec == null) + return -1; + + if (spec === "") return position; - if (typeof spec === "number") - position = spec; + if (/^\d+$/.test(spec)) + position = parseInt(spec, 10) + (offset || 0); else if (spec === "$") position = tabs.length - 1; else if (/^[+-]\d+$/.test(spec)) position += parseInt(spec, 10); - else if (/^\d+$/.test(spec)) - position = parseInt(spec, 10); else return -1; @@ -290,7 +306,7 @@ var Tabs = Module("tabs", { * * @param {Object} tab The tab to keep. */ - keepOnly: function (tab) { + keepOnly: function keepOnly(tab) { config.tabbrowser.removeAllTabsBut(tab); }, @@ -300,7 +316,7 @@ var Tabs = Module("tabs", { * @param {string} filter A filter matching a substring of the tab's * document title or URL. */ - list: function (filter) { + list: function list(filter) { completion.listCompleter("buffer", filter); }, @@ -312,8 +328,8 @@ var Tabs = Module("tabs", { * @param {boolean} wrap Whether an out of bounds *spec* causes the * destination position to wrap around the start/end of the tab list. */ - move: function (tab, spec, wrap) { - let index = tabs.indexFromSpec(spec, wrap); + move: function move(tab, spec, wrap) { + let index = tabs.indexFromSpec(spec, wrap, -1); config.tabbrowser.moveTabTo(tab, index); }, @@ -324,7 +340,7 @@ var Tabs = Module("tabs", { * @param {number} count How many tabs to remove. * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab. */ - remove: function (tab, count, focusLeftTab) { + remove: function remove(tab, count, focusLeftTab) { count = count || 1; let res = this.count > count; @@ -355,7 +371,7 @@ var Tabs = Module("tabs", { * @param {boolean} bypassCache Whether to bypass the cache when * reloading. */ - reload: function (tab, bypassCache) { + reload: function reload(tab, bypassCache) { try { if (bypassCache) { const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; @@ -373,20 +389,15 @@ var Tabs = Module("tabs", { * @param {boolean} bypassCache Whether to bypass the cache when * reloading. */ - reloadAll: function (bypassCache) { - if (bypassCache) { - for (let i = 0; i < config.tabbrowser.mTabs.length; i++) { - try { - this.reload(config.tabbrowser.mTabs[i], bypassCache); - } - catch (e) { - // FIXME: can we do anything useful here without stopping the - // other tabs from reloading? - } + reloadAll: function reloadAll(bypassCache) { + this.visibleTabs.forEach(function (tab) { + try { + tabs.reload(tab, bypassCache); } - } - else - config.tabbrowser.reloadAllTabs(); + catch (e) { + dactyl.reportError(e, true); + } + }); }, /** @@ -396,7 +407,7 @@ var Tabs = Module("tabs", { * @param {boolean} wrap Whether an out of bounds *spec* causes the * selection position to wrap around the start/end of the tab list. */ - select: function (spec, wrap) { + select: function select(spec, wrap) { let index = tabs.indexFromSpec(spec, wrap); if (index == -1) dactyl.beep(); @@ -407,7 +418,7 @@ var Tabs = Module("tabs", { /** * Selects the alternate tab. */ - selectAlternateTab: function () { + selectAlternateTab: function selectAlternateTab() { dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate, _("buffer.noAlternate")); tabs.select(tabs.alternate); @@ -418,7 +429,7 @@ var Tabs = Module("tabs", { * * @param {Object} tab The tab to stop loading. */ - stop: function (tab) { + stop: function stop(tab) { if (config.stop) config.stop(tab); else @@ -428,7 +439,7 @@ var Tabs = Module("tabs", { /** * Stops loading all tabs. */ - stopAll: function () { + stopAll: function stopAll() { for (let [, browser] in this.browsers) browser.stop(); }, @@ -447,7 +458,7 @@ var Tabs = Module("tabs", { * */ // FIXME: help! - switchTo: function (buffer, allowNonUnique, count, reverse) { + switchTo: function switchTo(buffer, allowNonUnique, count, reverse) { if (buffer != null) { // store this command, so it can be repeated with "B" this._lastBufferSwitchArgs = buffer; @@ -502,7 +513,7 @@ var Tabs = Module("tabs", { * @param {Array(Object)} tabs The current and alternate tab. * @see tabs#alternate */ - updateSelectionHistory: function (tabs) { + updateSelectionHistory: function updateSelectionHistory(tabs) { if (!tabs) { if (this.getTab() == this._alternates[0] || this.alternate && this.allTabs.indexOf(this._alternates[0]) == -1 @@ -527,50 +538,17 @@ var Tabs = Module("tabs", { commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"], "Delete current buffer", function (args) { - let special = args.bang; - let count = args.count; - let arg = args[0] || ""; - - if (arg) { - let removed = 0; - let matches = arg.match(/^(\d+):?/); - - if (matches) { - config.removeTab(tabs.getTab(parseInt(matches[1], 10) - 1)); - removed = 1; - } - else { - let str = arg.toLowerCase(); - let browsers = config.tabbrowser.browsers; - - for (let i = browsers.length - 1; i >= 0; i--) { - let host, title, uri = browsers[i].currentURI.spec; - if (browsers[i].currentURI.schemeIs("about")) { - host = ""; - title = "(Untitled)"; - } - else { - host = browsers[i].currentURI.host; - title = browsers[i].contentTitle; - } - - [host, title, uri] = [host, title, uri].map(String.toLowerCase); - - if (host.indexOf(str) >= 0 || uri == str || - (special && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) { - config.removeTab(tabs.getTab(i)); - removed++; - } - } - } + let removed = 0; + for (let tab in matchTabs(args, args.bang, true)) { + config.removeTab(tab); + removed++; + } + if (args[0]) if (removed > 0) - dactyl.echomsg(_("buffer.fewer", removed, removed == 1 ? "" : "s"), 9); + dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9); else dactyl.echoerr(_("buffer.noMatching", arg)); - } - else // just remove the current tab - tabs.remove(tabs.getTab(), Math.max(count, 1), special); }, { argCount: "?", bang: true, @@ -580,19 +558,87 @@ var Tabs = Module("tabs", { privateData: true }); + function matchTabs(args, substr, all) { + let filter = args[0]; + + if (!filter && args.count == null) + yield tabs.getTab(); + else if (!filter) + yield dactyl.assert(tabs.getTab(args.count - 1)); + else { + let matches = /^(\d+)(?:$|:)/.exec(filter); + if (matches) + yield dactyl.assert(args.count == null && + tabs.getTab(parseInt(matches[1], 10) - 1, !all)); + else { + let str = filter.toLowerCase(); + for (let tab in values(tabs[all ? "allTabs" : "visibleTabs"])) { + let host, title; + let browser = tab.linkedBrowser; + let uri = browser.currentURI.spec; + if (browser.currentURI.schemeIs("about")) { + host = ""; + title = "(Untitled)"; + } + else { + host = browser.currentURI.host; + title = browser.contentTitle; + } + + [host, title, uri] = [host, title, uri].map(String.toLowerCase); + + if (host.indexOf(str) >= 0 || uri == str || + (substr && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) + if (args.count == null || --args.count == 0) + yield tab; + } + } + } + } + + commands.add(["pin[tab]"], + "Pin tab as an application tab", + function (args) { + for (let tab in matchTabs(args)) + config.browser[!args.bang || !tab.pinned ? "pinTab" : "unpinTab"](tab); + }, + { + argCount: "?", + bang: true, + count: true, + completer: function (context, args) { + if (!args.bang) + context.filters.push(function ({ item }) !item.tab.pinned); + completion.buffer(context); + } + }); + + commands.add(["unpin[tab]"], + "Unpin tab as an application tab", + function (args) { + for (let tab in matchTabs(args)) + config.browser.unpinTab(tab); + }, + { + argCount: "?", + count: true, + completer: function (context, args) { + context.filters.push(function ({ item }) item.tab.pinned); + completion.buffer(context); + } + }); + commands.add(["keepa[lt]"], "Execute a command without changing the current alternate buffer", function (args) { - let alternate = tabs.alternate; - try { - commands.execute(args[0] || "", null, true); + dactyl.execute(args[0], null, true); } finally { - tabs.updateSelectionHistory([tabs.getTab(), alternate]); + tabs.updateSelectionHistory([tabs.getTab(), tabs.alternate]); } }, { - argCount: "+", + argCount: "1", completer: function (context) completion.ex(context), literal: 0, subCommand: 0 @@ -603,10 +649,10 @@ var Tabs = Module("tabs", { function (args) { dactyl.withSavedValues(["forceNewTab"], function () { this.forceNewTab = true; - commands.execute(args[0] || "", null, true); + dactyl.execute(args[0], null, true); }); }, { - argCount: "+", + argCount: "1", completer: function (context) completion.ex(context), literal: 0, subCommand: 0 @@ -643,7 +689,7 @@ var Tabs = Module("tabs", { if (/^\d+$/.test(arg)) tabs.select("-" + arg, true); else - dactyl.echoerr(_("error.trailing")); + dactyl.echoerr(_("error.trailingCharacters")); } else if (count > 0) tabs.select("-" + count, true); @@ -666,7 +712,7 @@ var Tabs = Module("tabs", { // count is ignored if an arg is specified, as per Vim if (arg) { - dactyl.assert(/^\d+$/.test(arg), _("error.trailing")); + dactyl.assert(/^\d+$/.test(arg), _("error.trailingCharacters")); index = arg - 1; } else @@ -709,11 +755,9 @@ var Tabs = Module("tabs", { }); commands.add(["quita[ll]", "qa[ll]"], - "Quit " + config.appName, - function (args) { dactyl.quit(false, args.bang); }, { - argCount: "0", - bang: true - }); + "Quit this " + config.appName + " window", + function (args) { window.close(); }, + { argCount: "0" }); commands.add(["reloada[ll]"], "Reload all tab pages", @@ -727,21 +771,24 @@ var Tabs = Module("tabs", { function () { tabs.stopAll(); }, { argCount: "0" }); - // TODO: add count support + // TODO: add count and bang multimatch support - unify with :buffer nonsense commands.add(["tabm[ove]"], - "Move the current tab after tab N", + "Move the current tab to the position of tab N", function (args) { let arg = args[0]; - // FIXME: tabmove! N should probably produce an error - dactyl.assert(!arg || /^([+-]?\d+)$/.test(arg), - _("error.trailing")); - - // if not specified, move to after the last tab - tabs.move(config.tabbrowser.mCurrentTab, arg || "$", args.bang); + if (tabs.indexFromSpec(arg) == -1) { + let tabs = [tab for (tab in matchTabs(args, true))]; + dactyl.assert(tabs.length, _("error.invalidArgument", arg)); + dactyl.assert(tabs.length == 1, _("buffer.multipleMatching", arg)); + arg = tabs[0]; + } + tabs.move(tabs.getTab(), arg, args.bang); }, { - argCount: "?", - bang: true + argCount: "1", + bang: true, + completer: function (context, args) completion.buffer(context, true), + literal: 0 }); commands.add(["tabo[nly]"], @@ -789,16 +836,23 @@ var Tabs = Module("tabs", { commands.add(["taba[ttach]"], "Attach the current tab to another window", function (args) { - dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+$/.test(i)), - _("error.trailing")); + dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+(?:$|:)/.test(i)), + _("error.trailingCharacters")); - let [winIndex, tabIndex] = args.map(parseInt); + let [winIndex, tabIndex] = args.map(function (arg) parseInt(arg)); let win = dactyl.windows[winIndex - 1]; dactyl.assert(win, _("window.noIndex", winIndex)); dactyl.assert(win != window, _("window.cantAttachSame")); let browser = win.getBrowser(); + + if (args[1]) { + let tabList = browser.visibleTabs || browser.mTabs; + let target = dactyl.assert(tabList[tabIndex]); + tabIndex = Array.indexOf(browser.mTabs, target) - 1; + } + let dummy = browser.addTab("about:blank"); browser.stop(); // XXX: the implementation of DnD in tabbrowser.xml suggests @@ -808,15 +862,26 @@ var Tabs = Module("tabs", { let last = browser.mTabs.length - 1; - browser.moveTabTo(dummy, Math.constrain(tabIndex || last, 0, last)); + if (args[1]) + browser.moveTabTo(dummy, tabIndex); browser.selectedTab = dummy; // required browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab); }, { argCount: "+", + literal: 1, completer: function (context, args) { - if (args.completeArg == 0) { + switch (args.completeArg) { + case 0: context.filters.push(function ({ item }) item != window); completion.window(context); + break; + case 1: + let win = dactyl.windows[Number(args[0]) - 1]; + if (!win || !win.dactyl) + context.message = _("Error", _("window.noIndex", winIndex)); + else + win.dactyl.modules.commands.get("tabmove").completer(context); + break; } } }); @@ -977,11 +1042,16 @@ var Tabs = Module("tabs", { tabs.tabStyle.enabled = true; else { prefs.safeSet("browser.tabs.autoHide", value === "multitab", - "See 'showtabline' option."); + _("option.showtabline.safeSet")); tabs.tabStyle.enabled = false; } + if (value !== "multitab" || !dactyl.has("Gecko2")) - config.tabStrip.collapsed = false; + if (tabs.xulTabs) + tabs.xulTabs.visible = value !== "never"; + else + config.tabStrip.collapsed = false; + if (config.tabbrowser.tabContainer._positionPinnedTabs) config.tabbrowser.tabContainer._positionPinnedTabs(); return value; @@ -1015,12 +1085,12 @@ var Tabs = Module("tabs", { values: activateGroups, has: Option.has.toggleAll, setter: function (newValues) { - let valueSet = set(newValues); + let valueSet = Set(newValues); for (let group in values(activateGroups)) if (group[2]) prefs.safeSet("browser.tabs." + group[2], !(valueSet["all"] ^ valueSet[group[0]]), - "See the 'activate' option"); + _("option.activate.safeSet")); return newValues; } }); @@ -1031,11 +1101,8 @@ var Tabs = Module("tabs", { { values: { "all": "All commands", - "addons": ":addo[ns] command", - "downloads": ":downl[oads] command", "extoptions": ":exto[ptions] command", "help": ":h[elp] command", - "javascript": ":javascript! or :js! command", "prefs": ":pref[erences]! or :prefs! command" }, has: Option.has.toggleAll @@ -1058,9 +1125,9 @@ var Tabs = Module("tabs", { } prefs.safeSet("browser.link.open_newwindow", open, - "See 'popups' option."); + _("option.popups.safeSet")); prefs.safeSet("browser.link.open_newwindow.restriction", restriction, - "See 'popups' option."); + _("option.popups.safeSet")); return values; }, values: { diff --git a/common/javascript.vim b/common/javascript.vim new file mode 100644 index 0000000..c1bfe25 --- /dev/null +++ b/common/javascript.vim @@ -0,0 +1,343 @@ +" Vim syntax file +" Language: JavaScript +" Maintainer: Yi Zhao (ZHAOYI) +" Last Change: May 17, 2007 +" Version: 0.7.5 +" Changes: 1, Get the vimdiff problem fixed finally. +" Matthew Gallant reported the problem and test the fix. ;) +" 2, Follow the suggestioin from Ingo Karkat. +" The 'foldtext' and 'foldlevel' settings should only be +" changed if the file being edited is pure JavaScript, +" not if JavaScript syntax is embedded inside other syntaxes. +" 3, Remove function FT_JavaScriptDoc(). +" Since VIM do the better than me. +" +" TODO: +" - Add the HTML syntax inside the JSDoc + +if !exists("main_syntax") + if version < 600 + syntax clear + elseif exists("b:current_syntax") + finish + endif + let main_syntax = 'javascript' +endif + +"" Drop fold if it set but VIM doesn't support it. +let b:javascript_fold='true' +if version < 600 " Don't support the old version + unlet! b:javascript_fold +endif + +syn include @xmlTop syntax/xml.vim +unlet b:current_syntax + +syn include @cssTop syntax/css.vim +unlet b:current_syntax + +"" dollar sigh is permittd anywhere in an identifier +setlocal iskeyword+=$ + +syntax sync fromstart +syntax sync maxlines=200 + +"" JavaScript comments +syntax keyword javaScriptCommentTodo TODO FIXME XXX TBD contained +syntax region javaScriptLineComment start=+\/\/+ end="\v$|(\)@=" keepend contains=javaScriptCommentTodo,@Spell +syntax region javaScriptLineComment start=+^\s*\/\/+ skip=+\n\s*\/\/+ end="\v$|(\)@=" keepend contains=javaScriptCommentTodo,@Spell fold +syntax region javaScriptCvsTag start="\$\cid:" end="\$" oneline contained +syntax region javaScriptComment start="/\*" end="\v\*/|(\)@=" contains=javaScriptCommentTodo,javaScriptCvsTag,@Spell fold + +"" JSDoc support start +if !exists("javascript_ignore_javaScriptdoc") + syntax case ignore + + "" syntax coloring for javadoc comments (HTML) + "syntax include @javaHtml :p:h/html.vim + "unlet b:current_syntax + + syntax region javaScriptDocComment matchgroup=javaScriptComment start="/\*\*\s*$" end="\*/" contains=javaScriptDocTags,javaScriptCommentTodo,javaScriptCvsTag,@javaScriptHtml,@Spell fold + syntax match javaScriptDocTags contained "@\(param\|argument\|requires\|exception\|throws\|type\|class\|extends\|see\|link\|member\|module\|method\|title\|namespace\|optional\|default\|base\|file\)\>" nextgroup=javaScriptDocParam,javaScriptDocSeeTag skipwhite + syntax match javaScriptDocTags contained "@\(beta\|deprecated\|description\|fileoverview\|author\|license\|version\|returns\=\|constructor\|private\|protected\|final\|ignore\|addon\|exec\)\>" + syntax match javaScriptDocParam contained "\%(#\|\w\|\.\|:\|\/\)\+" + syntax region javaScriptDocSeeTag contained matchgroup=javaScriptDocSeeTag start="{" end="}" contains=javaScriptDocTags + + syntax case match +endif "" JSDoc end + +syntax case match + +"" Syntax in the JavaScript code +syntax match javaScriptSpecial "\\\d\d\d\|\\x\x\{2\}\|\\u\x\{4\}\|\\." +syntax region javaScriptStringD start=+"+ skip=+\\\\\|\\$"+ end=+"+ contains=javaScriptSpecial,@htmlPreproc +syntax region javaScriptStringS start=+'+ skip=+\\\\\|\\$'+ end=+'+ contains=javaScriptSpecial,@htmlPreproc +syntax region javaScriptRegexpString start=+/\(\*\|/\)\@!+ skip=+\\\\\|\\/+ end=+/[gim]\{-,3}+ contains=javaScriptSpecial,@htmlPreproc oneline +syntax match javaScriptNumber /\<-\=\d\+L\=\>\|\<0[xX]\x\+\>/ +syntax match javaScriptFloat /\<-\=\%(\d\+\.\d\+\|\d\+\.\|\.\d\+\)\%([eE][+-]\=\d\+\)\=\>/ +syntax match javaScriptLabel /\(?\s*\)\@/ + syntax match javaScriptDomElemFuncs contained /\%(insertBefore\|replaceChild\|removeChild\|appendChild\|hasChildNodes\|cloneNode\|normalize\|isSupported\|hasAttributes\|getAttribute\|setAttribute\|removeAttribute\|getAttributeNode\|setAttributeNode\|removeAttributeNode\|getElementsByTagName\|getAttributeNS\|setAttributeNS\|removeAttributeNS\|getAttributeNodeNS\|setAttributeNodeNS\|getElementsByTagNameNS\|hasAttribute\|hasAttributeNS\)\>/ nextgroup=javaScriptParen skipwhite + " HTML things + syntax match javaScriptHtmlElemAttrs contained /\%(className\|clientHeight\|clientLeft\|clientTop\|clientWidth\|dir\|id\|innerHTML\|lang\|length\|offsetHeight\|offsetLeft\|offsetParent\|offsetTop\|offsetWidth\|scrollHeight\|scrollLeft\|scrollTop\|scrollWidth\|style\|tabIndex\|title\)\>/ + syntax match javaScriptHtmlElemFuncs contained /\%(blur\|click\|focus\|scrollIntoView\|addEventListener\|dispatchEvent\|removeEventListener\|item\)\>/ nextgroup=javaScriptParen skipwhite + + " CSS Styles in JavaScript + syntax keyword javaScriptCssStyles contained color font fontFamily fontSize fontSizeAdjust fontStretch fontStyle fontVariant fontWeight letterSpacing lineBreak lineHeight quotes rubyAlign rubyOverhang rubyPosition + syntax keyword javaScriptCssStyles contained textAlign textAlignLast textAutospace textDecoration textIndent textJustify textJustifyTrim textKashidaSpace textOverflowW6 textShadow textTransform textUnderlinePosition + syntax keyword javaScriptCssStyles contained unicodeBidi whiteSpace wordBreak wordSpacing wordWrap writingMode + syntax keyword javaScriptCssStyles contained bottom height left position right top width zIndex + syntax keyword javaScriptCssStyles contained border borderBottom borderLeft borderRight borderTop borderBottomColor borderLeftColor borderTopColor borderBottomStyle borderLeftStyle borderRightStyle borderTopStyle borderBottomWidth borderLeftWidth borderRightWidth borderTopWidth borderColor borderStyle borderWidth borderCollapse borderSpacing captionSide emptyCells tableLayout + syntax keyword javaScriptCssStyles contained margin marginBottom marginLeft marginRight marginTop outline outlineColor outlineStyle outlineWidth padding paddingBottom paddingLeft paddingRight paddingTop + syntax keyword javaScriptCssStyles contained listStyle listStyleImage listStylePosition listStyleType + syntax keyword javaScriptCssStyles contained background backgroundAttachment backgroundColor backgroundImage gackgroundPosition backgroundPositionX backgroundPositionY backgroundRepeat + syntax keyword javaScriptCssStyles contained clear clip clipBottom clipLeft clipRight clipTop content counterIncrement counterReset cssFloat cursor direction display filter layoutGrid layoutGridChar layoutGridLine layoutGridMode layoutGridType + syntax keyword javaScriptCssStyles contained marks maxHeight maxWidth minHeight minWidth opacity MozOpacity overflow overflowX overflowY verticalAlign visibility zoom cssText + syntax keyword javaScriptCssStyles contained scrollbar3dLightColor scrollbarArrowColor scrollbarBaseColor scrollbarDarkShadowColor scrollbarFaceColor scrollbarHighlightColor scrollbarShadowColor scrollbarTrackColor + + " Highlight ways + syntax match javaScriptDotNotation "\." nextgroup=javaScriptPrototype,javaScriptDomElemAttrs,javaScriptDomElemFuncs,javaScriptHtmlElemAttrs,javaScriptHtmlElemFuncs + syntax match javaScriptDotNotation "\.style\." nextgroup=javaScriptCssStyles + +endif "DOM/HTML/CSS + +"" end DOM/HTML/CSS specified things + + +"" Code blocks +syntax cluster javaScriptAll contains=javaScriptComment,javaScriptLineComment,javaScriptDocComment,javaScriptStringD,javaScriptStringS,javaScriptRegexpString,javaScriptNumber,javaScriptFloat,javaScriptLabel,javaScriptSource,javaScriptType,javaScriptOperator,javaScriptBoolean,javaScriptNull,javaScriptFunction,javaScriptConditional,javaScriptRepeat,javaScriptBranch,javaScriptStatement,javaScriptGlobalObjects,javaScriptExceptions,javaScriptFutureKeys,javaScriptDomErrNo,javaScriptDomNodeConsts,javaScriptHtmlEvents,javaScriptDotNotation,javascriptE4X,javascriptCSS,javascriptCDATA +syntax region javaScriptBracket matchgroup=javaScriptBracket transparent start="\[" end="\]" contains=@javaScriptAll,javaScriptParensErrB,javaScriptParensErrC,javaScriptBracket,javaScriptParen,javaScriptBlock,@htmlPreproc +syntax region javaScriptParen matchgroup=javaScriptParen transparent start="(" end=")" contains=@javaScriptAll,javaScriptParensErrA,javaScriptParensErrC,javaScriptParen,javaScriptBracket,javaScriptBlock,@htmlPreproc +syntax region javaScriptBlock matchgroup=javaScriptBlock transparent start="{" end="}" contains=@javaScriptAll,javaScriptParensErrA,javaScriptParensErrB,javaScriptParen,javaScriptBracket,javaScriptBlock,@htmlPreproc + +syntax region javascriptCDATA matchgroup=javascriptCDATA start="<\!\[CDATA\[" end="\]\]>" keepend contains=javascriptCSS +syntax region javascriptCSS matchgroup=javascriptCSSDelimiter start="" end="" contains=@cssTop +syntax region javascriptE4X matchgroup=javascriptE4XDelimiter start="" end="" contains=@xmlTop +syntax region javascriptE4X matchgroup=javascriptE4XDelimiter start="<>" end="" contains=@xmlTop oneline + +"" catch errors caused by wrong parenthesis +syntax match javaScriptParensError ")\|}\|\]" +syntax match javaScriptParensErrA contained "\]" +syntax match javaScriptParensErrB contained ")" +syntax match javaScriptParensErrC contained "}" + +if main_syntax == "javascript" + syntax sync ccomment javaScriptComment +endif + +"" Fold control +if exists("b:javascript_fold") + syntax match javaScriptFunction /\/ nextgroup=javaScriptFuncName skipwhite + syntax match javaScriptOpAssign /=\@= 508 || !exists("did_javascript_syn_inits") + if version < 508 + let did_javascript_syn_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + HiLink javascriptCDATA String + HiLink javaScriptComment Comment + HiLink javaScriptLineComment Comment + HiLink javaScriptDocComment Comment + HiLink javaScriptCommentTodo Todo + HiLink javaScriptCvsTag Function + HiLink javaScriptDocTags Special + HiLink javaScriptDocSeeTag Function + HiLink javaScriptDocParam Function + HiLink javaScriptStringS String + HiLink javaScriptStringD String + HiLink javaScriptRegexpString String + HiLink javaScriptCharacter Character + HiLink javaScriptPrototype Type + HiLink javaScriptConditional Conditional + HiLink javaScriptBranch Conditional + HiLink javaScriptRepeat Repeat + HiLink javaScriptStatement Statement + HiLink javaScriptFunction Function + HiLink javaScriptError Error + HiLink javaScriptParensError Error + HiLink javaScriptParensErrA Error + HiLink javaScriptParensErrB Error + HiLink javaScriptParensErrC Error + HiLink javaScriptOperator Operator + HiLink javaScriptType Type + HiLink javaScriptNull Type + HiLink javaScriptNumber Number + HiLink javaScriptFloat Number + HiLink javaScriptBoolean Boolean + HiLink javaScriptLabel Label + HiLink javaScriptSpecial Special + HiLink javaScriptSource Special + HiLink javaScriptGlobalObjects Special + HiLink javaScriptExceptions Special + + HiLink javaScriptDomErrNo Constant + HiLink javaScriptDomNodeConsts Constant + HiLink javaScriptDomElemAttrs Label + HiLink javaScriptDomElemFuncs PreProc + + HiLink javaScriptHtmlEvents Special + HiLink javaScriptHtmlElemAttrs Label + HiLink javaScriptHtmlElemFuncs PreProc + + HiLink javaScriptCssStyles Label + + delcommand HiLink +endif + +" Define the htmlJavaScript for HTML syntax html.vim +"syntax clear htmlJavaScript +"syntax clear javaScriptExpression +syntax cluster htmlJavaScript contains=@javaScriptAll,javaScriptBracket,javaScriptParen,javaScriptBlock,javaScriptParenError +syntax cluster javaScriptExpression contains=@javaScriptAll,javaScriptBracket,javaScriptParen,javaScriptBlock,javaScriptParenError,@htmlPreproc + +let b:current_syntax = "javascript" +if main_syntax == 'javascript' + unlet main_syntax +endif + + +if exists('b:did_indent') + finish +endif +let b:did_indent = 1 + +setlocal indentexpr=GetJsIndent() +setlocal indentkeys=0{,0},0),:,!^F,O,e,=*/ +" Clean CR when the file is in Unix format +if &fileformat == "unix" + silent! %s/\r$//g +endif +" Only define the functions once per Vim session. +"if exists("*GetJsIndent") +" finish +"endif +"function! GetJsIndent() +" let pnum = prevnonblank(v:lnum - 1) +" if pnum == 0 +" return 0 +" endif +" let line = getline(v:lnum) +" let pline = getline(pnum) +" let ind = indent(pnum) +" +" if pline =~ '{\s*$\|[\s*$\|(\s*$' +" let ind = ind + &sw +" endif +" +" if pline =~ ';\s*$' && line =~ '^\s*}' +" let ind = ind - &sw +" endif +" +" if pline =~ '\s*]\s*$' && line =~ '^\s*),\s*$' +" let ind = ind - &sw +" endif +" +" if pline =~ '\s*]\s*$' && line =~ '^\s*}\s*$' +" let ind = ind - &sw +" endif +" +" if line =~ '^\s*});\s*$\|^\s*);\s*$' && pline !~ ';\s*$' +" let ind = ind - &sw +" endif +" +" if line =~ '^\s*})' && pline =~ '\s*,\s*$' +" let ind = ind - &sw +" endif +" +" if line =~ '^\s*}();\s*$' && pline =~ '^\s*}\s*$' +" let ind = ind - &sw +" endif +" +" if line =~ '^\s*}),\s*$' +" let ind = ind - &sw +" endif +" +" if pline =~ '^\s*}\s*$' && line =~ '),\s*$' +" let ind = ind - &sw +" endif +" +" if pline =~ '^\s*for\s*' && line =~ ')\s*$' +" let ind = ind + &sw +" endif +" +" if line =~ '^\s*}\s*$\|^\s*]\s*$\|\s*},\|\s*]);\s*\|\s*}]\s*$\|\s*};\s*$\|\s*})$\|\s*}).el$' && pline !~ '\s*;\s*$\|\s*]\s*$' && line !~ '^\s*{' && line !~ '\s*{\s*}\s*' +" let ind = ind - &sw +" endif +" +" if pline =~ '^\s*/\*' +" let ind = ind + 1 +" endif +" +" if pline =~ '\*/$' +" let ind = ind - 1 +" endif +" return ind +"endfunction + +" vim: ts=4 diff --git a/common/locale/en-US/autocommands.xml b/common/locale/en-US/autocommands.xml index 260eb07..b2719c0 100644 --- a/common/locale/en-US/autocommands.xml +++ b/common/locale/en-US/autocommands.xml @@ -42,7 +42,8 @@

    If the -group=group flag is given, add this autocmd to the named group. Any filters for group apply in - addition to filter. + addition to filter. When listing commands this limits the + output to the specified group.

    Available events:

    @@ -88,11 +89,11 @@

    Examples

    -

    Enable passthrough mode on all Google sites:

    +

    Enable Pass Through mode on all Google sites:

    :autocmd LocationChange google.com :normal! -

    Enable passthrough mode on some Google sites:

    +

    Enable Pass Through mode on some Google sites:

    :autocmd LocationChange ^https?://(www|mail)\.google\.com/ :normal! diff --git a/common/locale/en-US/browsing.xml b/common/locale/en-US/browsing.xml index cbb31f1..0e0c407 100644 --- a/common/locale/en-US/browsing.xml +++ b/common/locale/en-US/browsing.xml @@ -17,10 +17,10 @@ &dactyl.appName; overrides nearly all &dactyl.host; keys in order to make browsing more pleasant for Vim users. On the occasions when you want to bypass &dactyl.appName;'s key handling and pass keys directly to -&dactyl.host; or to a web page, you have two options: +&dactyl.host; or to a web page, you have several options: - ]]> + ]]> ]]>

    @@ -53,6 +53,11 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to +

    + See also passkeys and passunknown for ways to permanently pass + all or particular keys under certain conditions. +

    +

    Opening web pages

    @@ -101,7 +106,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to :open Linus Torvalds
  • - Any other value is passed directly &dactyl.host; and + Any other value is passed directly to &dactyl.host; and must be a valid URL or hostname.
  • @@ -223,7 +228,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - ]]> + ]]> count<C-x> @@ -237,7 +242,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to - ]]> + ]]> count<C-a> @@ -249,7 +254,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to - ~ + ~]]> ~

    Open home directory. Equivalent to :open ~/

    @@ -296,14 +301,14 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to

    Current history position is marked with >. - Jump numbers may be used as counts for with + Jump numbers may be used as counts for :back or :forward.

    - gh + gh]]> gh

    Go home. Opens the homepage in the current tab.

    @@ -311,7 +316,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - gH + gH]]> gH @@ -324,7 +329,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to - gu + gu]]> countgu

    Go to countth parent directory.

    @@ -338,7 +343,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - gU + gU]]> gU

    Go to the root of the web site.

    @@ -369,28 +374,6 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - - :reh :rehash - :rehash arg … - -

    - Reload the &dactyl.appName; add-on, including all code, plugins, - and configuration. For users running directly from the development - repository, this is a good way to update to the latest version or - to test your changes. -

    -

    - Any arguments supplied are parsed as command-line arguments as - specified in startup-options. -

    - - Not all plugins are designed to cleanly un-apply during a rehash. - While official plugins are safe, beware of possible instability - if you rehash while running third-party plugins. - -
    -
    - :re :reload :reload! @@ -416,7 +399,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to

    Stopping

    - :st :stop]]> + :st :stop]]> <C-c> :stop @@ -471,6 +454,18 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to

    Quitting

    + + ZQ :x :exit + + :exit! + +

    + Quit &dactyl.appName;, no matter how many tabs/windows are open. + The session is not stored. Use ! to forcibly quit. +

    +
    +
    + :q :quit @@ -489,8 +484,8 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to :quitall

    - Quit &dactyl.appName;, no matter how many tabs/windows - are open. The session is not stored. + Close the current &dactyl.appName; window, no matter how + many tabs are open.

    @@ -512,7 +507,7 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - :xa :xall :wq :wqa :wqall + ZZ :xa :xall :wq :wqa :wqall :wqall :xall @@ -525,25 +520,6 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to
    - - ZQ - ZQ - -

    Quit and don't save the session. Works like :qall.

    -
    -
    - - - ZZ - ZZ - -

    - Quit &dactyl.appName; and save the session. Works like - :xall. -

    -
    -
    -

    The current directory

    diff --git a/common/locale/en-US/buffer.xml b/common/locale/en-US/buffer.xml index 66d0d97..7f514a9 100644 --- a/common/locale/en-US/buffer.xml +++ b/common/locale/en-US/buffer.xml @@ -55,7 +55,7 @@ - gf + gf]]> gf @@ -67,7 +67,7 @@ - gF + gF]]> gF @@ -115,7 +115,7 @@ - gg]]> + gg]]> countgg @@ -128,7 +128,7 @@ - G]]> + G]]> countG @@ -148,7 +148,7 @@ - h]]> + h]]> counth @@ -160,7 +160,7 @@ - j]]> + j]]> countj @@ -172,7 +172,7 @@ - k]]> + k]]> countk @@ -184,7 +184,7 @@ - l]]> + l]]> countl @@ -222,7 +222,7 @@ - ]]> + ]]> count<C-b> @@ -234,7 +234,7 @@ - ]]> + ]]> count<C-f> @@ -328,6 +328,39 @@ + + [h [p [ + count[arg + +

    Jump to the previous element as defined by jumptags.

    +
    +
    + + + ]h ]p ] + count]arg + +

    Jump to the next element as defined by jumptags.

    +
    +
    + + + { + count{ + +

    Jump to the previous paragraph. Identical to [p.

    +
    +
    + + + } + count} + +

    Jump to the next paragraph. Identical to ]p.

    +
    +
    + +

    Zooming

    diff --git a/common/locale/en-US/cmdline.xml b/common/locale/en-US/cmdline.xml index 0ad3332..b44c8bb 100644 --- a/common/locale/en-US/cmdline.xml +++ b/common/locale/en-US/cmdline.xml @@ -9,11 +9,11 @@ xmlns="&xmlns.dactyl;" xmlns:html="&xmlns.html;"> -

    Command-line mode

    +

    Command Line mode

    - &dactyl.appName;'s command-line mode is perhaps its most + &dactyl.appName;'s Command Line mode is perhaps its most powerful interface. In this mode, the command input bar at the bottom of the window is given the keyboard focus for any of a variety of required inputs. In addition to access to almost @@ -27,9 +27,9 @@

    Included among the several command-line modes are Ex command - mode (the standard mode for entering commands), find mode (for - searching the current page), prompt mode (for selecting files, - confirming actions), and hint mode (for selecting links and + mode (the standard mode for entering commands), Find mode (for + searching the current page), Prompt mode (for selecting files, + confirming actions), and Hints mode (for selecting links and other items on a page).

    @@ -60,7 +60,7 @@ ]]> <C-c> -

    Quit Command-line mode without executing.

    +

    Quit Command Line mode without executing.

    diff --git a/common/locale/en-US/faq.xml b/common/locale/en-US/faq.xml index ecc3bec..2c78e9c 100644 --- a/common/locale/en-US/faq.xml +++ b/common/locale/en-US/faq.xml @@ -65,8 +65,8 @@

    What do the "[-+♥]" symbols in the status bar mean?

    These indicate that you can move backward through history, - that you can move forward through history, and that the page - is bookmarked, respectively. See also :help + that you can move forward through history, and that the page is + bookmarked, respectively. See also :help status-line.

    @@ -81,37 +81,37 @@ :help pattern.

    -

    How can I prevent the command line completion list showing until I press ?

    +

    How can I prevent the command line completion list showing until I press ?

    - You can disable it entirely with :set autocomplete= - or for specific types of command completion by - choosing more restrictive values. See :help - autocomplete and wildmode. + You can disable it entirely with or for + specific types of command completion by choosing more + restrictive values. See :help autocomplete and + wildmode.

    Why doesn't external input field editing work with my editor setting?

    - Unfortunately, external editors which return immediately, - before editing is complete, are not supported. This means that - gvim, for instance, must be run with the -f flag, and - editors run from a terminal must not connect to a remote - process. In the case of Rxvt-unicode, this means that the - urxvtc program is not an option, and Gnome Terminal is very - likely not useable under any circumstances. + Unfortunately, external editors which return immediately, before + editing is complete, are not supported. This means that + gvim, for instance, must be run with the -f + flag, and editors run from a terminal must not connect to a + remote process. In the case of Rxvt-unicode, this means that the + urxvtc program is not an option, and Gnome Terminal is + very likely not useable under any circumstances.

    - If you are using a version of Firefox newer than 4.0 - beta 7 and a version of Pentadactyl less than 1.0 - βeta 4, you'll need to upgrade the latter. + If you are using a version of Firefox newer than 4.0 beta 7 and + a version of Pentadactyl less than 1.0 βeta 4, you'll need to + upgrade the latter.

    Why can't I build/install from the Mercurial repository on Windows®?

    We use symbolic links in our repository to deal with certain - files which are common across projects. Mercurial for - Windows®, unfortunately, doesn't deal with these very well. - However, adding the following lines to the .hg\hgrc - file in your repository should make things work: + files which are common across projects. Mercurial for Windows®, + unfortunately, doesn't deal with these very well. However, + adding the following lines to the .hg\hgrc file in your + repository should make things work:

    [hooks] update = python:common/contrib/fix_symlinks.py:fix_symlinks @@ -140,7 +140,7 @@ precommit = python:common/contrib/fix_symlinks.py:fix_symlinks:js services.get(browserSearch).getEngines().forEach(function (e) e.hidden = false) + :js services.browserSearch.getEngines().forEach(function (e) e.hidden = false)

    Key bindings

    @@ -148,7 +148,7 @@ precommit = python:common/contrib/fix_symlinks.py:fix_symlinks See the passkeys option to automatically pass specific keys on sites of your choosing, or autocmd-examples to - automatically enter PASS THROUGH mode for certain websites. + automatically enter Pass Through mode for certain websites.

    Why doesn't my modes.passAllKeys autocmd work anymore?

    @@ -158,7 +158,7 @@ precommit = python:common/contrib/fix_symlinks.py:fix_symlinksHow can I use keys other than numbers for hinting?

    Use the hintkeys option.

    -

    How can I display my hints in upper case but type them in lower case?

    +

    How can I display my hints in upper case but type them in lower case?

    If you use alphabetic characters for your hintkeys and would like to be able to type them in lower case but still have diff --git a/common/locale/en-US/gui.xml b/common/locale/en-US/gui.xml index 9bb1151..4c3d200 100644 --- a/common/locale/en-US/gui.xml +++ b/common/locale/en-US/gui.xml @@ -68,11 +68,38 @@ Show progress of current downloads. Here, downloads can be paused, resumed, and canceled.

    + +

    Available options include:

    + +
    +
    -sort
    Sort order (see downloadsort) (short name: -s)
    +
    + + + + + :dlc :dlclear + + :dlclear + +

    Clear completed downloads.

    Add-ons

    +

    + The following commands manipulate the currently installed + add-ons. With the exception of :extadd, they all except + the following arguments: +

    + +
    +
    -types
    The types of add-ons to operate on, the most + common types being extension, theme, and plugin (short names -type, + -t)
    +
    + :exta :extadd :extadd file|url @@ -228,8 +255,8 @@
  • History and bookmark status ([+-❤⋯]): The position - of the current page in the tab's session history; + and - - indicate that it is possible to move backwards and forwards + of the current page in the tab's session history; - and + + indicate that it is possible to move backwards and forwards through the history respectively. ❤ indicates that the current page is bookmarked. Any other character indicates a QuickMark matching the current page. diff --git a/common/locale/en-US/hints.xml b/common/locale/en-US/hints.xml index 68ce2f3..e44a12a 100644 --- a/common/locale/en-US/hints.xml +++ b/common/locale/en-US/hints.xml @@ -9,12 +9,12 @@ xmlns="&xmlns.dactyl;" xmlns:html="&xmlns.html;"> -

    Hints

    +

    Hints

    Hints are an easy way to interact with web pages without using - your mouse. In hint mode, &dactyl.appName; highlights and + your mouse. In Hints mode, &dactyl.appName; highlights and numbers all clickable elements. The elements can be selected either by typing their numbers, or typing parts of their text to narrow them down. While the default action is to click the @@ -25,20 +25,20 @@

    - quick-hints - f QuickHint + quick-hints hint-mode + f fhint

    - Start QuickHint mode. In this mode, every clickable + Start hint-mode. In this mode, every clickable element (as defined by the hinttags option) is highlighted and numbered. Elements can be selected either by typing their number, or by typing part of their text to narrow down the result. When an element has been selected, it is automatically clicked and hint mode ends. Additionally, the following keys have - special meanings in QuickHint mode: + special meanings in Hints mode:

    @@ -53,7 +53,7 @@ value of hintkeys) as ordinary text
    -
    Exits hint mode without selecting an element
    +
    Exits Hints mode without selecting an element
    @@ -64,7 +64,7 @@ Fhint

    - Start QuickHint mode, but the selected elements + Start hint-mode, but the selected elements are clicked with the key pressed, which has the normal effect of opening it in a new tab (depending on the value of the @@ -75,17 +75,17 @@ extended-hints - ; ExtendedHint + ; ;modehint

    - Start an extended hint mode. ExtendedHint mode is exactly - like QuickHint mode, except that each sub-mode highlights a + Start an extended hints mode. Extended hints are exactly like + quick-hints, except that each sub-mode highlights a more specialized set of elements, and performs a unique action on the selected link. Because of the panoply of extended hint modes available, after pressing ;, pressing brings - up the completion list with each hint mode and its description. + up the completion list with each hints mode and its description.

    mode may be one of:

    @@ -129,7 +129,7 @@ g;modehint

    - Start an extended hint mode and stay there until + Start an extended hints mode and stay there until is pressed. Like ;, except that after a hint is selected, hints remain visible so that another one can be selected with the same action as the diff --git a/common/locale/en-US/map.xml b/common/locale/en-US/map.xml index 640b321..8921e81 100644 --- a/common/locale/en-US/map.xml +++ b/common/locale/en-US/map.xml @@ -1,7 +1,7 @@ - + Key mappings are the most basic means &dactyl.appName; provides for altering the actions of key presses. Each key mapping is - associated with a mode, such as insert, - normal, or - command-line, and only + associated with a mode, such as Insert, + Normal, or + Command Line, and only has effect when that mode is active. Although each mode has a full suite of internal mappings, they may be easily augmented, altered, or removed with the :map command and its @@ -54,17 +54,17 @@

    n
    Normal mode: When browsing normally
    v
    Visual mode: When selecting text with the cursor keys
    i
    Insert mode: When interacting with text fields on a website
    -
    t
    TextEdit mode: When editing text fields in Vim-like NORMAL mode
    -
    c
    Command-line mode: When typing into the &dactyl.appName; command line
    +
    t
    Text Edit mode: When editing text fields in Vim-like Normal mode
    +
    c
    Command Line mode: When typing into the &dactyl.appName; command line

    The ordinary :map and :noremap commands - add mappings for normal and visual mode. In order to map key + add mappings for Normal and Visual mode. In order to map key bindings in a different mode, any of the mapping commands may be prefixed with one of the above letters. For instance, - :imap creates a new key mapping in insert mode, while - :cunmap removes a key mapping from command-line mode. + :imap creates a new key mapping in Insert mode, while + :cunmap removes a key mapping from Command Line mode. Other modes can be specified using the -modes option described below.

    @@ -75,6 +75,15 @@ saved via the :mk&dactyl.name;rc command. +

    + The following tree represents all of the modes understood by + dactyl. Mappings for a mode also apply to its children and + descendants. So a mapping in the Base mode, for instance, is + also active in Normal and Ex mode. +

    + +&modes.tree; +

    Map commands

    @@ -106,6 +115,24 @@
    +

    + Below is an overview of which modes each map command applies to: +

    + +
    +
    :map :noremap :unmap
    Normal and Visual modes
    +
    :nmap :nnoremap :nunmap
    Normal mode
    +
    :vmap :vnoremap :vunmap
    Visual mode
    +
    :imap :inoremap :iunmap
    Insert mode
    +
    :tmap :tnoremap :tunmap
    Text Edit mode
    +
    :cmap :cnoremap :cunmap
    Command Line mode
    +
    + + + The -modes option, described below, provides a more flexible way + to specify the applicable modes. + +

    Map options

    Any of the map commands may be given the following options: @@ -119,10 +146,10 @@

    -count
    Accept a count before the requisite key press. Sets the count parameter to the result. (short name -c)
    -description
    A description of this mapping (short name -d)
    -ex
    Execute rhs as an Ex command rather than keys (short name -e)
    -
    -group=group
    Add this command to the given group (short name -g)
    +
    -group=group
    Add this command to the given group (short name -g). When listing commands this limits the output to the specified group.
    -javascript
    Execute rhs as JavaScript rather than keys (short names -js, -j)
    -literal=n
    Parse the nth argument without specially processing any quote or meta characters. (short name -l)
    -
    -modes
    Create this mapping in the given modes (short names -mode, -m)
    +
    -modes=modes
    Create this mapping in the given modes (short names -mode, -m)
    -nopersist
    Do not save this mapping to an auto-generated rc file (short name -n)
    -silent
    Do not echo any generated keys to the command line (short name -s, also <silent> for Vim compatibility)
    @@ -130,10 +157,10 @@ :no :noremap :noremap lhs rhs - :nn :nnoremap - :nnoremap lhs rhs - :vn :vnoremap - :vnoremap lhs rhs + :nno :nnoremap + :nnoremap lhs rhs + :vno :vnoremap + :vnoremap lhs rhs :ino :inoremap :inoremap lhs rhs :tno :tnoremap @@ -219,23 +246,6 @@ documentation of those options for more information.

    -

    Special arguments

    - -

    - Below is an overview of which modes each map command applies to: -

    - - - - -:map :noremap :unmap :mapclear – both Normal and Visual modes -:nmap :nnoremap :nunmap :nmapclear – Normal mode -:vmap :vnoremap :vunmap :vmapclear – Visual mode -:imap :inoremap :iunmap :imapclear – Insert mode -:tmap :tnoremap :tunmap :tmapclear – Text Edit mode -:cmap :cnoremap :cunmap :cmapclear – Command-line mode - -

    Key sequences

    @@ -293,6 +303,7 @@

  • : The control or ctrl key.
  • : The alt key.
  • : The meta key, windows key, or command key.
  • +
  • : Same as .
  • : The shift key.
  • @@ -311,31 +322,31 @@
    xc
    -
    Type the ‘X’ key followed by the ‘C’ key.
    +
    Press the ‘X’ key followed by the ‘C’ key.
    c
    - Type the ‘X’ key while holding the ‘Control’ key, followed + Press the ‘X’ key while holding the ‘Control’ key, followed by the ‘C’ key.
    -
    Type the ‘2’ while holding the ‘Control’ key.
    +
    Type ‘2’ while holding the ‘Control’ key.
    -
    Type the ‘@’ key while holding the ‘Control’ key.
    +
    Press the ‘@’ key while holding the ‘Control’ key.
    Press the space bar while holding the ‘Shift’ key.
    -
    Type the ‘J’ key while while holding both the ‘Control’ and ‘Alt’ keys.
    +
    Press the ‘J’ key while holding both the ‘Control’ and ‘Alt’ keys.
    Exactly the same as above.
    -
    Type the ‘J’ key while while holding both the ‘Control’, ‘Alt’, and ‘Shift’ keys.
    +
    Press the ‘J’ key while holding all of ‘Control’, ‘Alt’, and ‘Shift’ keys.

    Special characters

    @@ -424,7 +435,7 @@ :ab :abbreviate - :abbreviate -group=group lhs rhs + :abbreviate -group=group -js lhs rhs :abbreviate -group=group lhs :abbreviate -group=group @@ -460,9 +471,9 @@ :cabbreviate

    - Abbreviate a key sequence for Command-line mode. Same as - :abbreviate, but for - command-line mode only. + Abbreviate a key sequence for Command Line mode. Same as + :abbreviate, but for &mode.command-line; mode + only.

    @@ -475,7 +486,7 @@

    Abbreviate a key sequence for Insert mode. Same as - :abbreviate, but for insert mode only. + :abbreviate, but for Insert mode only.

    @@ -495,9 +506,9 @@ :cunabbreviate!

    - Remove abbreviation(s) for Command-line mode. Same as - :unabbreviate, but for - command-line mode only. + Remove abbreviation(s) for Command Line mode. Same as + :unabbreviate, but for &mode.command-line; mode + only.

    @@ -581,7 +592,8 @@

    The -group flag (short name: -g) can be used to - assign this command to a specific group. + assign this command to a specific group. When listing + commands this limits the output to the specified group.

    Argument handling

    @@ -629,7 +641,7 @@

    completions is a two-dimensional array of the form: - [[arg1, description1], [arg2, description2], …] + [[val1, description1], [val2, description2], …]

    @@ -640,11 +652,11 @@

    Example: :command foo -nargs=? -complete custom, - \ function (context) context.completions = [["arg1", "description1"], ["arg2", "description2"]] + \ function (context) context.completions = [["val1", "description1"], ["val2", "description2"]] \ :echo Useless + <q-args> :command foo -nargs=? - \ -complete custom,[["arg1", "description1"], ["arg2, "description2"]] + \ -complete custom,[["val1", "description1"], ["val2", "description2"]] \ :echo Same as above but simpler + <q-args>

    diff --git a/common/locale/en-US/messages.properties b/common/locale/en-US/messages.properties index 662286b..e9c2991 100644 --- a/common/locale/en-US/messages.properties +++ b/common/locale/en-US/messages.properties @@ -1,12 +1,27 @@ # TODO: normalize this debacle of Vim legacy messages -# : are we losing the error code prefixes? --djk -abbrev.noSuch = No such abbreviation -abbrev.none = No abbreviations found +abbreviation.noSuch = No such abbreviation +abbreviation.none = No abbreviations found addon.check-1 = Checking updates for addons: %S addon.cantInstallDir-1 = Cannot install a directory: %S addon.unavailable = Don't have add-on yet +addon.unknownCommand = Unknown command +addon.commandNotAllowed = Command not allowed +addon.installingUpdates-1 = Installing updates for addons: %S +addon.noUpdates = No addon updates found +addon.error-3 = Add-on %S %S: %S + +addon.action.On = On  +addon.action.Off = Off +addon.action.Delete = Del +addon.action.Update = Upd +addon.action.Options = Opt + +AddonManager.ERROR_NETWORK_FAILURE = A network error occurred +AddonManager.ERROR_INCORRECT_HASH = The downloaded file did not match the expected hash +AddonManager.ERROR_CORRUPT_FILE = The file appears to be corrupt +AddonManager.ERROR_FILE_ACCESS = There was an error accessing the filesystem autocmd.executing-2 = Executing %S Auto commands for %S autocmd.autocommand-1 = autocommand %S @@ -19,12 +34,15 @@ bookmark.noMatchingTags-1 = No bookmarks matching tags %S bookmark.noMatchingString-1 = No bookmarks matching string %S bookmark.none = No bookmarks set bookmark.cantAdd-1 = Could not add bookmark %S -bookmark.allGone = All bookmarks deleted +bookmark.allDeleted = All bookmarks deleted bookmark.removed-1 = Removed bookmark: %S bookmark.added-1 = Added bookmark: %S bookmark.deleted-1 = %S bookmark(s) deleted +bookmark.prompt.deleteAll = This will delete all bookmarks. Would you like to continue? (yes/[no]): -buffer.fewer-2 = %S fewer tab%S +# TODO: add plurals support +buffer.fewerTab-1 = %S fewer tab +buffer.fewerTabs-1 = %S fewer tabs buffer.cantDetatchLast = Can't detach the last tab buffer.noMatching-1 = No matching buffer for %S buffer.multipleMatching-1 = More than one match for %S @@ -32,6 +50,18 @@ buffer.noClosed = No matching closed tab buffer.noAlternate = No alternate page buffer.backgroundLoaded = Background tab loaded: %S +# TODO: categorise these +buffer.noTitle = [No Title] +buffer.noName = [No Name] +buffer.help = [Help] + +buffer.bookmarked = bookmarked + +buffer.prompt.uploadFile = Upload file: +buffer.prompt.saveLink = Save link: + +context.scriptGroup-1 = Script group for %S + command.commands = commands command.cantDelete = Cannot delete non-user commands @@ -48,21 +78,38 @@ command.invalidOptArg-2 = Invalid argument for option %S: %S command.invalidOptTypeArg-3 = Invalid argument for %S option %S: %S command.parsing-1 = Error parsing arguments: %S command.none = No user-defined commands found -command.unknownCompleter-1 = E117: Unknown function: %S command.exists = E174: Command already exists: add ! to replace it command.noPrevious = E30: No previous command line -command.noRange = E481: No range allowed +command.noCount = E481: No count allowed command.noBang = E477: No ! allowed command.colorscheme.notFound-1 = E185: Cannot find color scheme %S command.conditional.illegal = Invalid use of conditional command.finish.illegal = E168: :finish used outside of a sourced file -command.let.noSuch-1 = E108: No such variable: %S -command.let.unexpectedChar = E18: Unexpected characters in :let command.let.illegalVar-1 = E461: Illegal variable name: %S -command.let.undefinedVar-1 = E121: Undefined variable: %S command.let.invalidExpression-1 = E15: Invalid expression: %S - +command.let.noSuch-1 = E108: No such variable: %S +command.let.undefinedVar-1 = E121: Undefined variable: %S +command.let.unexpectedChar = E18: Unexpected characters in :let +command.run.noPrevious = E34: No previous command +command.sanitize.privateMode = Cannot sanitize items in private mode +command.sanitize.allDeleted = All items sanitized +command.sanitize.noneDeleted = No items sanitized +command.scriptnames.none = No sourced scripts found +command.set.errorParsing-1 = Error parsing :set command: %S +command.set.numberRequired-2 = E521: Number required after =: %S=%S +command.set.unknownOption-1 = E518: Unknown option: %S +command.yank.yankedLine-1 = Yanked %S line +command.yank.yankedLines-1 = Yanked %S lines + +completion.additional = (additional) +completion.generating = Generating results... +completion.noCompletions = No Completions +completion.waitingFor-1 = Waiting for %S +completion.waitingForKeyPress = Waiting for key press +completion.matchIndex-2 = match %S of %S + +dactyl.created-1 = "(created %S)" dactyl.parsingCommandLine-1 = Parsing command line options: %S dactyl.notCommand-2 = E492: Not a %S command: %S dactyl.sourcingPlugins-1 = Sourcing plugin directory: %S... @@ -71,17 +118,32 @@ dactyl.modulesLoaded = All modules loaded dactyl.commandlineOpts-1 = Command-line options: %S dactyl.noRCFile = No user RC file found dactyl.initialized-1 = %S fully initialized +dactyl.sourced-1 = Sourced: %S +dactyl.prompt.openMany-1 = This will open %S new tabs. Would you like to continue? (yes/[no]): +dactyl.yank-1 = Yank %S + +deprecated.for.theOptionsSystem = the options system dialog.notAvailable-1 = Dialog %S not available -group.cantChangeBuiltin-1 = Cannot change %S in the builtin group -group.cantModifyBuiltin = Cannot modify builtin group -group.cantRemoveBuiltin = Cannot remove builtin group -group.noSuch-1 = No such group: %S -group.invalidName-1 = Invalid group name: %S -group.noCurrent = No current group +# TODO: merge with addon.*? +download.unknownCommand = Unknown command +download.commandNotAllowed = Command not allowed +download.prompt.launchExecutable = This will launch an executable download. Would you like to continue? (yes/[no]/always): -editor.noEditor = No editor specified +download.nActive-1 = %S active +download.almostDone = ~1 second +download.unknown = Unknown + +download.action.Cancel = Cancel +download.action.Clear = Clear +download.action.Delete = Delete +download.action.Pause = Pause +download.action.Remove = Remove +download.action.Resume = Resume +download.action.Retry = Retry + +editor.prompt.editPassword = Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]): emenu.notFound-1 = Menu not found: %S @@ -89,38 +151,55 @@ event.error-2 = Processing %S event: %S event.nothingToPass = No events to pass finder.notFound-1 = E486: Pattern not found: %S -finder.atTop = find hit TOP, continuing at BOTTOM -finder.atBottom = find hit BOTTOM, continuing at TOP +finder.atTop = Find hit TOP, continuing at BOTTOM +finder.atBottom = Find hit BOTTOM, continuing at TOP + +group.cantChangeBuiltin-1 = Cannot change %S in the builtin group +group.cantModifyBuiltin = Cannot modify builtin group +group.cantRemoveBuiltin = Cannot remove builtin group +group.noSuch-1 = No such group: %S +group.invalidName-1 = Invalid group name: %S +group.noCurrent = No current group help.dontPanic = E478: Don't panic! +help.Example = Example help.noFile-1 = Sorry, help file %S not found help.noTopic-1 = Sorry, no help for %S +help.title.Using Plugins = Using Plugins -hints.noMatcher-1 = Invalid hintmatching type: %S +hints.noMatcher-1 = Invalid 'hintmatching' type: %S history.noMatching-1 = No history matching %S history.none = No history set history.noURL = URL not found in history -io.noSuchDir-1 = E344: Can't find directory %S -io.noPrevDir = E186: No previous directory -io.notReadable-1 = Can't open file %S -io.notWriteable-1 = Can't open file %S for writing +io.callingShell-1 = Calling shell to execute: %S +io.cantCreateTempFile = Couldn't create temporary file +io.cantEncode = Input contains characters not valid in the current file encoding +io.commandFailed = E472: Command failed +io.definedAt = Defined at +io.downloadFinished-2 = Download of %S to %S finished +io.eNotDir = Not a directory io.exists = File exists (add ! to override) io.exists-1 = File %S exists (add ! to override) io.noCommand-1 = Command not found: %S -io.commandFailed = E472: Command failed -io.oneFileAllowed = E172: Only one file name allowed -io.callingShell-1 = Calling shell to execute: %S -io.sourcing-1 = sourcing %S -io.sourcingEnd-1 = finished sourcing %S +io.noPrevDir = E186: No previous directory +io.noSuchDir-1 = E344: Can't find directory %S +io.noSuchFile = File does not exist io.notInRTP-1 = not found in 'runtimepath': %S +io.notReadable-1 = Can't open file %S +io.notWriteable-1 = Can't open file %S for writing +io.oneFileAllowed = E172: Only one file name allowed io.searchingFor-1 = Searching for %S io.searchingFor-2 = Searching for %S in %S -io.downloadFinished-2 = Download of %S to %S finished +io.shellReturn-1 = shell returned %S +io.sourcing-1 = sourcing %S +io.sourcingEnd-1 = finished sourcing %S +io.sourcingError-1 = Sourcing file: %S macro.canceled-1 = Canceled playback of macro '%S' macro.recorded-1 = Recorded macro '%S' +macro.recorded-2 = Recorded macro %S: %S macro.loadFailed-1 = Page did not load completely in %S seconds macro.loadWaiting = Waiting for page to load... macro.noSuch-1 = Macro '%S' not set @@ -136,19 +215,56 @@ mark.none = No marks set mark.invalid = Invalid mark mark.unset-1 = Mark not set: %S mark.noMatching-1 = E283: No marks matching %S +# TODO: relies on mark formatter +mark.addURL-1 = Adding URL mark: %S +mark.addLocal-1 = Adding local mark: %S +mark.jumpingToURL-1 = Jumping to URL mark: %S +mark.jumpingToLocal-1 = Jumping to local mark: %S mode.recursiveSet = Not executing modes.set recursively +mode.invalidBases = Invalid bases mow.noPreviousOutput = No previous command output +mow.continue = Press ENTER or type command to continue +mow.more = -- More -- +mow.moreHelp = -- More -- SPACE//j: screen/page/line down, //k: up, q: quit + +mow.contextMenu.copyLink = Copy Link Location +mow.contextMenu.copyPath = Copy File Path +mow.contextMenu.copy = Copy +mow.contextMenu.selectAll = Select All option.noSuch = No such option option.noSuch-1 = No such option: %S option.replaceExisting-1 = Warning: %S already exists: replacing existing option +option.intRequired = Integer value required +option.operatorNotSupported-2 = Operator %S not supported for option type %S +option.notSet-1 = E764: Option '%S' is not set + +option.currentValue = Current Value +option.defaultValue = Default Value + +option.bufferLocal = buffer local + +option.activate.safeSet = See the 'activate' option. +option.guioptions.safeSet = See 'guioptions' scrollbar flags. +option.hintkeys.duplicate = Duplicate keys not allowed +# The string 'passkeys' must appear exactly, including U0027 apostrophes +option.passkeys.passedBy = passed by 'passkeys' +option.popups.safeSet = See the 'activate' option. +option.showtabline.safeSet = See 'showtabline' option. +option.visualbell.safeSet = See 'visualbell' option. + +pageinfo.s.ownerUnverified = %S (unverified) plugin.searchingFor-1 = Searching for %S plugin.searchingForIn-2 = Searching for %S in %S plugin.notReplacingContext-1 = Not replacing plugin context for %S +pref.hostPreferences-1 = %S Preferences +pref.prompt.resetAll-1 = Warning: Resetting all preferences may make %S unusable. Would you like to continue (yes/[no]): +pref.safeSet.warnChanged-1 = Warning: Setting preference %S, but it's changed from its default value. + print.toFile-1 = Printing to file: %S print.sending = Sending to printer... print.sent = Print job sent @@ -160,14 +276,49 @@ quickmark.notSet = QuickMark not set quickmark.invalid = Argument must be an ASCII letter or digit quickmark.added-2 = Added Quick Mark '%S': %S +sanitize.prompt.deleteAll = This will sanitize all items. Would you like to continue? (yes/[no]): + save.invalidDestination-1 = Invalid destination: %S +sort.ascending = ascending +sort.descending = descending + status.link-1 = Link: %S style.none = No style found +style.styles = styles +style.inline = inline time.total-1 = Total time: %S +title.Abbrev = Abbrev +title.Action = Action +title.Args = Args +title.Average time = Average time +title.CSS = CSS +title.Code execution summary = Code execution summary +title.Command = Command +title.Complete = Complete +title.Definition = Definition +title.Description = Description +title.Executed = Executed +title.Filter = Filter +title.Jump = Jump +title.Mode = Mode +title.Name = Name +title.Progress = Progress +title.Range = Range +title.Replacement = Replacement +title.Source = Source +title.Speed = Speed +title.Status = Status +title.Time remaining = Time remaining +title.Title = Title +title.Total time = Total time +title.Totals = Totals +title.URI = URI +title.Version = Version + variable.none = No variables found window.cantAttachSame = Can't reattach to the same window @@ -179,10 +330,14 @@ zoom.illegal = Illegal zoom value error.clipboardEmpty = No clipboard data error.countRequired-1 = Count required for %S error.cantOpen-2 = Error opening %S: %S +error.error-1 = Error: %S +error.disabled = Item is disabled +error.disabled-1 = '%S' is disabled error.interrupted = Interrupted error.invalidSort-1 = Invalid sort order: %S -error.trailing = Trailing characters -error.trailing-1 = Trailing characters: %S +error.missingQuote-1 = E114: Missing quote: %S +error.trailingCharacters = Trailing characters +error.trailingCharacters-1 = Trailing characters: %S error.invalid-1 = Invalid %S error.invalidArgument = Invalid argument error.invalidArgument-1 = Invalid argument: %S @@ -192,7 +347,12 @@ error.argumentOrBang = Argument or ! required error.invalidOperation = Invalid operation error.monkeyPatchOverlay-1 = Not replacing property with eval-generated overlay by %S error.nullComputedStyle-1 = Computed style is null: %S +error.syntaxError = Syntax error +error.charactersOutsideRange-1 = Character list outside the range %S +error.invalidCharacterRange = Invalid character range: %S +error.notWriteable-2 = Could not write to %S: %S +warn.deprecated-2 = %S is deprecated: Please use %S instead. warn.notDefaultBranch-2 = You are running %S from a testing branch: %S. Please do not report errors which do not also occur in the default branch. # vim:se ft=jproperties tw=0: diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index 9f911ab..3aa6f5a 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -1,7 +1,7 @@ - + <e-name>, its value is never shown but may be used to test whether the given parameter is empty.

    +

    + Array elements, such as in the <args> parameter + of :command macros, may be accessed by appending + [n], where n is the one-based array + index, to the macro name. The first argument of a command is + therefore accessed with <args[1]>. +

    Any substring enclosed by <{ and }> is automatically elided if any of the contained macros aren't currently @@ -151,6 +158,7 @@ + :set!= :set invoption=value ... :set option!=value ... @@ -182,6 +190,7 @@ + :set-default-all :set all&

    Set all options to their default value.

    @@ -300,14 +309,17 @@ :set! :set-! - :set! preference=value - :set! preference& + :set! …

    - Change any &dactyl.host; preference (those on the about:config - page). You can also reset/delete these preferences with - :set! preference&. + The same as :set but operates on &dactyl.host; preferences + (those on the about:config page). See :set for operator + details.

    + + The no and inv prefix operators are not available + for setting preferences. +
    @@ -329,7 +341,7 @@ 'act' 'activate' 'activate' 'act' - stringlist + &option.activate.type; addons,bookmarks,diverted,downloads,extoptions, help,homepage,quickmark,tabopen,paste @@ -358,8 +370,8 @@ 'awim' 'altwildmode' 'altwildmode' 'awim' - stringlist - list:full + &option.altwildmode.type; + &option.altwildmode.default;

    Like wildmode, but when the key @@ -371,8 +383,8 @@ 'au' 'autocomplete' 'autocomplete' 'au' - regexplist - .* + &option.autocomplete.type; + &option.autocomplete.default;

    Enables automatic completion for completion contexts (see @@ -413,8 +425,8 @@ 'bh' 'banghist' 'banghist' 'bh' - boolean - on + &option.banghist.type; + &option.banghist.default;

    Replace occurrences of ! with the previous command when @@ -427,7 +439,7 @@ $CDPATH 'cd' 'cdpath' 'cdpath' 'cd' - stringlist + &option.cdpath.type; equivalent to . or .,$CDPATH

    @@ -445,8 +457,8 @@ 'ca' 'cookieaccept' 'cookieaccept' 'ca' - string - all + &option.cookieaccept.type; + &option.cookieaccept.default;

    When to accept cookies.

    @@ -461,8 +473,8 @@ 'cl' 'cookielifetime' 'cookielifetime' - string - default + &option.cookielifetime.type; + &option.cookielifetime.default;

    The lifetime for which to accept cookies. The available @@ -482,7 +494,8 @@ 'ck' 'cookies' 'cookies' 'ck' - stringlist session + &option.cookies.type; + &option.cookies.default;

    The default action for the :cookies command.

    @@ -491,8 +504,8 @@ 'cpt' 'complete' 'complete' 'cpt' - charlist - slf + &option.complete.type; + &option.complete.default;

    Items which are completed at the :open prompts. Available items:

    @@ -518,11 +531,38 @@
    + + 'dls' 'dlsort' 'downloadsort' + 'downloadsort' + stringlist + -active,+filename + +

    + :downloads sort order, in order of precedence. + Each element must be preceded by a + or + -, indicating ascending or descending sorting, + respectively. Valid sort orders are: +

    + +
    +
    active
    Whether download is active
    +
    complete
    Percent complete
    +
    date
    Date and time the download began
    +
    filename
    Target filename
    +
    size
    File size
    +
    speed
    Download speed
    +
    time
    Time remaining
    +
    url
    Source URL
    +
    +
    +
    + + 'ds' 'defsearch' 'defsearch' 'ds' - string - google + &option.defsearch.type; + &option.defsearch.default;

    Sets the default search engine. The default search engine is @@ -543,8 +583,8 @@ 'editor' 'editor' - string - ]]> + &option.editor.type; + &option.editor.default;

    Set the external text editor. @@ -576,8 +616,8 @@ 'enc' 'encoding' 'encoding' 'enc' - string - UTF-8 + &option.encoding.type; + &option.encoding.default;

    Changes the character encoding of the current buffer. Valid only @@ -590,8 +630,8 @@ 'noeb' 'noerrorbells' 'eb' 'errorbells' 'errorbells' 'eb' - boolean - off + &option.errorbells.type; + &option.errorbells.default;

    Ring the bell when an error message is displayed. See also @@ -603,8 +643,8 @@ 'ei' 'eventignore' 'eventignore' 'ei' - stringlist - + &option.eventignore.type; + &option.eventignore.default;

    A list of autocommand event names which should be ignored. If the @@ -618,8 +658,8 @@ 'noex' 'noexrc' 'ex' 'exrc' 'exrc' 'ex' - boolean - off + &option.exrc.type; + &option.exrc.default;

    Allow reading of an RC file in the current directory. This file is @@ -633,8 +673,8 @@ 'eht' 'extendedhinttags' 'extendedhinttags' 'eht' - regexpmap - [asOTivVWy]:a[href],area[href],img[src],iframe[src], + &option.extendedhinttags.type; + [asOTvVWy]:a[href],area[href],img[src],iframe[src], [f]:body, [F]:body,code,div,html,p,pre,span, [iI]:img, @@ -652,8 +692,8 @@ 'fenc' 'fileencoding' 'fileencoding' 'fenc' - string - UTF-8 + &option.fileencoding.type; + &option.fileencoding.default;

    Changes the character encoding that &dactyl.appName; uses to read @@ -665,8 +705,8 @@ 'fc' 'findcase' 'findcase' 'fc' - string - smart + &option.findcase.type; + &option.findcase.default;

    Find case matching mode.

    @@ -681,17 +721,20 @@ 'fh' 'followhints' 'followhints' 'fh' - number - 0 + &option.followhints.type; + &option.followhints.default; -

    Changes how soon matching hints are followed in Hints mode.

    +

    + Define the conditions under which hints selected by typing the link + substring are followed. Hints selected by typing their label (as + specified by hintkeys) are always followed immediately. +

    Possible values:

    0
    Follow the first hint as soon as typed text uniquely identifies it.
    1
    Follow the selected hint on .
    -
    2
    Follow the selected hint on only if it's been -selected.
    @@ -700,8 +743,8 @@ 'nofs' 'nofullscreen' 'fs' 'fullscreen' 'fullscreen' 'fs' - boolean - off + &option.fullscreen.type; + &option.fullscreen.default;

    Show the current window full-screen. Also hide certain GUI elements, such as @@ -713,8 +756,8 @@ 'go' 'guioptions' 'guioptions' 'go' - charlist - bCrs + &option.guioptions.type; + &option.guioptions.default;

    Show or hide certain GUI elements.

    @@ -745,8 +788,8 @@ 'hf' 'helpfile' 'helpfile' 'hf' - string - intro + &option.helpfile.type; + &option.helpfile.default;

    Name of the main help file. This is that page shown if the @@ -758,8 +801,8 @@ 'hin' 'hintinputs' 'hintinputs' 'hin' - stringlist - label,value + &option.hintinputs.type; + &option.hintinputs.default;

    When generating hints for input elements that do not have an @@ -779,8 +822,8 @@ 'hk' 'hintkeys' 'hintkeys' 'hk' - string - 0123456789 + &option.hintkeys.type; + &option.hintkeys.default;

    The keys used to label and select hints. With its default value, @@ -795,8 +838,8 @@ 'hm' 'hintmatching' 'hintmatching' 'hm' - stringlist - contains + &option.hintmatching.type; + &option.hintmatching.default;

    Change the hint matching algorithm used in Hints mode.

    @@ -848,7 +891,7 @@ 'ht' 'hinttags' 'hinttags' 'ht' - stringlist + &option.hinttags.type; a,area,button,iframe,input:not([type=hidden]),select,textarea, [onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand], [tabindex],[role=link],[role=button] @@ -867,8 +910,8 @@ 'hto' 'hinttimeout' 'hinttimeout' 'hto' - number - 0 + &option.hinttimeout.type; + &option.hinttimeout.default;

    Timeout in milliseconds before automatically following a non-unique @@ -883,8 +926,8 @@ 'hi' 'history' 'history' 'hi' - number - 500 + &option.history.type; + &option.history.default;

    Maximum number of Ex commands and find patterns to store in the @@ -897,8 +940,8 @@ 'nohlf' 'nohlfind' 'hlf' 'hlfind' 'hlfind' 'hlf' - boolean - off + &option.hlfind.type; + &option.hlfind.default;

    Highlight previous find pattern matches.

    @@ -908,8 +951,8 @@ 'noif' 'noincfind' 'if' 'incfind' 'incfind' 'if' - boolean - on + &option.incfind.type; + &option.incfind.default;

    Show the first match for a find pattern as it is typed.

    @@ -919,8 +962,8 @@ 'noim' 'noinsertmode' 'im' 'insertmode' 'insertmode' 'im' - boolean - on + &option.insertmode.type; + &option.insertmode.default;

    Use Insert mode as the default for text areas. This is useful if you @@ -930,7 +973,7 @@

    - TextEdit mode can be entered with from Insert mode. + Text Edit mode can be entered with from Insert mode.

    @@ -939,8 +982,8 @@ 'nojsd' 'nojsdebugger' 'jsd' 'jsdebugger' 'jsdebugger' 'jsd' - boolean - off + &option.jsdebugger.type; + &option.jsdebugger.default;

    Use the JavaScript debugger service for JavaScript completion. @@ -949,11 +992,20 @@ - 'nolpl' 'noloadplugins' + 'jt' 'jumptags' + 'jumptags' + &option.jumptags.type; + &option.jumptags.default; + +

    XPath or CSS selector strings of jumpable elements for extended hint modes.

    +
    +
    + + 'lpl' 'loadplugins' 'loadplugins' 'lpl' - regexplist - '\.(js|&dactyl.fileExt;)$' + &option.loadplugins.type; + &option.loadplugins.default;

    A regular expression list that defines which plugins are loaded at @@ -990,8 +1042,8 @@ 'ml' 'mapleader' 'mapleader' 'ml' - string - \ + &option.mapleader.type; + &option.mapleader.default;

    Defines the replacement keys for the pseudo-key.

    @@ -1000,8 +1052,8 @@ 'maxitems' 'maxitems' - number - 20 + &option.maxitems.type; + &option.maxitems.default;

    Maximum number of items to display at once in a listing.

    @@ -1010,8 +1062,8 @@ 'msgs' 'messages' 'messages' 'msgs' - number - 100 + &option.messages.type; + &option.messages.default;

    Maximum number of messages to store in the message history.

    @@ -1020,8 +1072,8 @@ 'nomore' 'more' 'more' - boolean - on + &option.more.type; + &option.more.default;

    Pause the message list window when more than one screen of @@ -1033,8 +1085,8 @@ 'newtab' 'newtab' - stringlist - + &option.newtab.type; + &option.newtab.default;

    Defines which Ex commands open pages in new tabs rather than the @@ -1062,8 +1114,8 @@ 'nextpattern' 'nextpattern' - stringlist - '\bnext',^>$,'^(>>|»)$','^(>|»)','(>|»)$','\bmore\b' + &option.nextpattern.type; + &option.nextpattern.default;

    Patterns to use when guessing the next page in a document @@ -1078,8 +1130,8 @@ 'noonline' 'online' 'online' - boolean - on + &option.online.type; + &option.online.default;

    Enables or disables ‘offline’ mode, where network access is @@ -1091,8 +1143,8 @@ 'pa' 'pageinfo' 'pageinfo' 'pa' - charlist - gfm + &option.pageinfo.type; + &option.pageinfo.default;

    Info shown in the :pageinfo output.

    @@ -1100,8 +1152,10 @@
    g
    General info
    +
    e
    Search Engines
    f
    Feeds
    m
    Meta tags
    +
    s
    Security information

    @@ -1114,8 +1168,8 @@ 'pk' 'passkeys' 'passkeys' 'pk' - sitemap - + &option.passkeys.type; + &option.passkeys.default;

    Pass certain keys through directly for the given URLs. @@ -1141,11 +1195,26 @@ + + 'pu' 'passunknown' + 'passunknown' 'pu' + &option.showmode.type; + &option.showmode.default; + +

    + Pass unknown keys through to &dactyl.host; in these + modes. The first element matching a currently + active mode is the one that takes effect. Modes may be + negated by prefixing them with a !. +

    +
    +
    + 'pps' 'popups' 'popups' 'pps' - stringlist - tab + &option.popups.type; + &option.popups.default;

    Defines where to show requested pop-up windows. Applies only to @@ -1179,8 +1248,8 @@ 'previouspattern' 'previouspattern' - stringlist - + &option.previouspattern.type; + &option.previouspattern.default;

    Patterns to use when guessing the previous page in a document @@ -1195,8 +1264,8 @@ 'noprivate' 'private' 'private' - boolean - off + &option.private.type; + &option.private.default;

    Set the private browsing option. In private browsing mode @@ -1213,7 +1282,7 @@ $&dactyl.idName;_RUNTIME 'rtp' 'runtimepath' 'runtimepath' 'rtp' - stringlist + &option.runtimepath.type; $&dactyl.idName;_RUNTIME or Unix, Mac: ~/.&dactyl.name; Windows: ~/&dactyl.name; @@ -1247,8 +1316,8 @@ 'si' 'sanitizeitems' 'sanitizeitems' 'si' - stringlist - all + &option.sanitizeitems.type; + &option.sanitizeitems.default;

    The default list of private items to sanitize. See @@ -1260,8 +1329,8 @@ 'ss' 'sanitizeshutdown' 'sanitizeshutdown' 'ss' - stringlist - + &option.sanitizeshutdown.type; + &option.sanitizeshutdown.default;

    The items to sanitize automatically at shutdown.

    @@ -1271,8 +1340,8 @@ 'sts' 'sanitizetimespan' 'sanitizetimespan' 'sts' - number - all + &option.sanitizetimespan.type; + &option.sanitizetimespan.default;

    The default sanitizer time span. Only items created within this timespan are @@ -1293,8 +1362,8 @@ 'scr' 'scroll' 'scroll' 'scr' - number - 0 + &option.scroll.type; + &option.scroll.default;

    Number of lines to scroll with and @@ -1309,7 +1378,7 @@ 'sh' 'shell' 'shell' 'sh' - string + &option.shell.type; $SHELL or sh, Windows: cmd.exe

    Shell to use for executing :! and :run commands.

    @@ -1320,7 +1389,7 @@ 'shcf' 'shellcmdflag' 'shellcmdflag' 'shcf' - string + &option.shellcmdflag.type; -c, Windows: /c

    Flag passed to shell when executing :! and :run commands.

    @@ -1328,13 +1397,17 @@
    - 'nosmd' 'noshowmode' 'smd' 'showmode' 'showmode' 'smd' - regexplist - !^normal$ + &option.showmode.type; + &option.showmode.default; -

    Show the current mode in the command line if it matches this expression.

    +

    + Show the current mode in the command line if it or any + of its parent modes is included in the list. + Modes may be negated by prefixing them with a + !. +

    @@ -1342,8 +1415,8 @@ 'ssli' 'showstatuslinks' 'showstatuslinks' 'ssli' - string - status + &option.showstatuslinks.type; + &option.showstatuslinks.default;

    When the mouse hovers over a link, or a link is otherwise focused, @@ -1363,8 +1436,8 @@ 'stal' 'showtabline' 'showtabline' 'stal' - string - always + &option.showtabline.type; + &option.showtabline.default;

    Define when the tab bar is visible.

    @@ -1377,23 +1450,30 @@
    - 'nosf' 'nostrictfocus' 'sf' 'strictfocus' 'strictfocus' 'sf' - boolean - on + &option.strictfocus.type; + &option.strictfocus.default;

    Prevent scripts from focusing input elements without user intervention.

    + +

    Possible values:

    + +
    +
    despotic
    Only allow focus changes when explicitly requested by the user
    +
    laissez-faire
    Always allow focus changes
    +
    moderate
    Allow focus changes after user-initiated focus change
    +
    'suggestengines' 'suggestengines' - stringlist - google + &option.suggestengines.type; + &option.suggestengines.default;

    Set the search engines which can be used for completion @@ -1406,8 +1486,8 @@ 'notmo' 'notimeout' 'tmo' 'timeout' 'timeout' 'tmo' - boolean - true + &option.timeout.type; + &option.timeout.default;

    When this option is set and a key sequence interpretable both as a @@ -1420,8 +1500,8 @@ 'tmol' 'timeoutlen' 'timeoutlen' 'tmol' - number - 1000 + &option.timeoutlen.type; + &option.timeoutlen.default;

    Maximum number of milliseconds to wait for a longer key command @@ -1434,8 +1514,8 @@ 'titlestring' 'titlestring' - string - &dactyl.appName; + &option.titlestring.type; + &option.titlestring.default;

    Set the application name shown after the current page title in @@ -1448,8 +1528,8 @@ 'us' 'urlsep' 'urlseparator' 'urlseparator' 'urlsep' 'us' - string - \| + &option.urlseparator.type; + &option.urlseparator.default;

    The regular expression used to split URL lists in commands @@ -1465,8 +1545,8 @@ 'noum' 'nousermode' 'um' 'usermode' 'usermode' 'um' - boolean - off + &option.usermode.type; + &option.usermode.default;

    Show current website with minimal styling.

    @@ -1475,8 +1555,8 @@ 'vbs' 'verbose' 'verbose' 'vbs' - number - 1 + &option.verbose.type; + &option.verbose.default;

    Define which info messages are displayed. As the value increases, @@ -1493,8 +1573,8 @@ 'novb' 'novisualbell' 'vb' 'visualbell' 'visualbell' 'vb' - boolean - off + &option.visualbell.type; + &option.visualbell.default;

    Use visual bell instead of beeping on errors. The visual bell @@ -1508,8 +1588,8 @@ 'wia' 'wildanchor' 'wildanchor' 'wia' - regexplist - !'/ex/(back|buffer|ext|forward|help|undo)' + &option.wildanchor.type; + &option.wildanchor.default;

    Regular expression list defining which completion groups show only @@ -1526,8 +1606,8 @@ 'wic' 'wildcase' 'wildcase' 'wic' - regexpmap - .?:smart + &option.wildcase.type; + &option.wildcase.default;

    Defines how completions are matched with regard to character case. @@ -1547,12 +1627,13 @@ 'wig' 'wildignore' 'wildignore' 'wig' - regexplist - + &option.wildignore.type; + &option.wildignore.default;

    - List of file patterns to ignore when completing files. For example, - the following will ignore object files and Vim swap files: + List of path name patterns to ignore when completing files and + directories. For example, the following will ignore object files + and Vim swap files:

    \.o$,^\..*\.s[a-z]{2}$ @@ -1564,8 +1645,8 @@ 'wim' 'wildmode' 'wildmode' 'wim' - stringlist - list:full + &option.wildmode.type; + &option.wildmode.default;

    Defines how command-line completion works. It is a comma-separated @@ -1602,8 +1683,8 @@ 'wis' 'wildsort' 'wildsort' 'wis' - regexplist - .* + &option.wildsort.type; + &option.wildsort.default;

    A list of regular expressions defining which completion contexts @@ -1620,8 +1701,8 @@ 'wsp' 'wordseparators' 'wordseparators' 'wsp' - string - #*+|=~ _-]]]> + &option.wordseparators.type; + &option.wordseparators.default;

    A regular expression which defines how words are split for diff --git a/common/locale/en-US/pattern.xml b/common/locale/en-US/pattern.xml index e874701..6ecac3a 100644 --- a/common/locale/en-US/pattern.xml +++ b/common/locale/en-US/pattern.xml @@ -45,8 +45,8 @@

    - / - /pattern/ + /]]> + /pattern

    Find pattern starting at the current caret position.

    @@ -75,8 +75,8 @@
    - ? - ?pattern? + ?]]> + ?pattern

    Find a pattern backward of the current caret position in exactly the @@ -86,7 +86,7 @@ - n + n]]> n

    Find next. Repeat the last find.

    @@ -94,7 +94,7 @@
    - N + N]]> N

    Find previous. Repeat the last find in the opposite direction.

    @@ -102,7 +102,7 @@
    - * + *]]> *

    Search forward for the next occurrence of the word under cursor.

    @@ -110,7 +110,7 @@
    - # + #]]> #

    Search backward for the previous occurrence of the word under cursor.

    diff --git a/common/locale/en-US/privacy.xml b/common/locale/en-US/privacy.xml index 84dafa9..7782ff9 100644 --- a/common/locale/en-US/privacy.xml +++ b/common/locale/en-US/privacy.xml @@ -21,7 +21,7 @@ sensitive data.

    -

    Private mode browsing

    +

    Private browsing

    &dactyl.appName; fully supports &dactyl.host;'s private browsing mode. @@ -92,7 +92,7 @@ The following items are always cleared entirely, regardless of - timeframe: cache, host, offlineapps, + timespan: cache, host, offlineapps, passwords, sessions, sitesettings. Conversely, host and options are never cleared unless a host is specified. diff --git a/common/locale/en-US/repeat.xml b/common/locale/en-US/repeat.xml index b14b8a8..c869683 100644 --- a/common/locale/en-US/repeat.xml +++ b/common/locale/en-US/repeat.xml @@ -188,6 +188,17 @@ + + :delgr :delgroup + :delgroup group + :delgroup! + +

    + Delete the specified group. With ! delete all + user groups. +

    +
    +

    Site Filters

    @@ -361,7 +372,7 @@
    - :ru :runtime + :runt :runtime :runtime! file …

    @@ -433,11 +444,12 @@

    Lines may be commented out by prefixing them with a " - character. + character. Comments and commands cannot both occur in a single command + line.

    " This is a comment - foo bar " This is a comment + foo bar " This is a syntax error This is not a comment foo bar This is not a comment diff --git a/common/locale/en-US/starting.xml b/common/locale/en-US/starting.xml index 2a6d33d..3893550 100644 --- a/common/locale/en-US/starting.xml +++ b/common/locale/en-US/starting.xml @@ -104,7 +104,7 @@
  • - If exrc is set and the +u command-line option was not + If exrc is set and the +u command-line option was not specified, then any RC file in the current directory is also sourced.

    @@ -119,7 +119,7 @@

      -
    • noloadplugins is set,
    • +
    • loadplugins is unset,
    • the ++noplugin command-line option was specified, or
    • the +u=NONE command-line option was specified.
    @@ -167,6 +167,28 @@

    Restarting

    + + :reh :rehash + :rehash arg … + +

    + Reload the &dactyl.appName; add-on, including all code, plugins, + and configuration. For users running directly from the development + repository, this is a good way to update to the latest version or + to test your changes. +

    +

    + Any arguments supplied are parsed as command-line arguments as + specified in startup-options. +

    + + Not all plugins are designed to cleanly un-apply during a rehash. + While official plugins are safe, beware of possible instability + if you rehash while running third-party plugins. + +
    +
    + :res :restart :restart diff --git a/common/locale/en-US/styling.xml b/common/locale/en-US/styling.xml index eda39bb..0ff2633 100644 --- a/common/locale/en-US/styling.xml +++ b/common/locale/en-US/styling.xml @@ -27,6 +27,8 @@

    Load a color scheme. name is found by searching the runtimepath for the first file matching colors/name.&dactyl.fileExt;. + The special scheme default can be used to reload the + default highlight settings.

    @@ -57,64 +59,200 @@

    Valid groups include:

    -
    -
    Bell
    &dactyl.appName;'s visual bell
    -
    Boolean
    A JavaScript Boolean object
    -
    CmdLine
    The command line
    -
    CmdOutput
    The output of commands executed by :run
    -
    CompDesc
    The description column of the completion list
    -
    CompGroup
    The top-level container for a group of completion results
    -
    CompIcon
    The favicon of a completion row
    -
    CompItem
    A row of completion list
    -
    CompItem[selected]
    A selected row of completion list
    -
    CompLess::after
    The character of indicator shown when completions may be scrolled up
    -
    CompLess
    The indicator shown when completions may be scrolled up
    -
    CompMore::after
    The character of indicator shown when completions may be scrolled down
    -
    CompMore
    The indicator shown when completions may be scrolled down
    -
    CompMsg
    The message which may appear at the top of a group of completion results
    -
    CompResult
    The result column of the completion list
    -
    CompTitle
    Completion row titles
    -
    CompTitleSep
    The element which separates the completion title from its results
    -
    Disabled
    Text indicating disabled status, such as of an extension or style group
    -
    Enabled
    Text indicating enabled status, such as of an extension or style group
    -
    ErrorMsg
    Error messages
    -
    Filter
    The matching text in a completion list
    -
    FrameIndicator
    The indicator shown when a new frame is selected
    -
    Function
    A JavaScript Function object
    -
    Hint
    A hint indicator. See :help hints
    -
    HintActive
    The hint element of link which will be followed by
    -
    HintElem
    The hintable element
    -
    HintImage
    The indicator which floats above hinted images
    -
    Indicator
    The # and % in the :buffers list
    -
    InfoMsg
    Information messages
    -
    Key
    Generally a keyword used in syntax highlighting.
    -
    Keyword
    A bookmark keyword for a URL
    -
    LineNr
    The line number of an error
    -
    Message
    A message as displayed in :messages
    -
    ModeMsg
    The mode indicator in the command line
    -
    MoreMsg
    The indicator that there is more text to view
    -
    NonText
    The ~ indicators which mark blank lines in the completion list
    -
    Normal
    Normal text in the command line
    -
    Null
    A JavaScript Null object
    -
    Number
    A JavaScript Number object
    -
    Object
    A JavaScript Object
    -
    Preview
    The completion preview displayed in the &tag.command-line;
    -
    Question
    A prompt for a decision
    -
    StatusLine
    The status bar
    -
    StatusLineNormal
    The status bar for an ordinary web page
    -
    StatusLineBroken
    The status bar for a broken web page
    -
    StatusLineExtended
    The status bar for a secure web page with an Extended Validation (EV) certificate
    -
    StatusLineSecure
    The status bar for a secure web page
    -
    String
    A JavaScript String object
    -
    TabClose
    The close button of a browser tab
    -
    TabIcon
    The icon of a browser tab
    -
    TabIconNumber
    The number of a browser tab, over its icon
    -
    TabNumber
    The number of a browser tab, next to its icon
    -
    TabText
    The text of a browser tab
    -
    Tag
    A bookmark tag for a URL
    -
    Title
    The title of a listing, including :pageinfo, :jumps
    -
    URL
    A URL
    -
    WarningMsg
    A warning message
    +
    +
    Addon
    An add-on in the :addons manager
    +
    AddonBar
    +
    AddonButton
    +
    AddonButtons
    +
    AddonCell
    A cell in tell :addons manager
    +
    AddonDescription
    +
    AddonHead
    A heading in the :addons manager
    +
    AddonName
    +
    AddonStatus
    +
    AddonVersion
    +
    Addons
    The :addons manager
    +
    AppmenuButton
    The app-menu button
    +
    Bell
    &dactyl.appName;'s visual bell
    +
    Boolean
    JavaScript booleans
    +
    Button
    A button widget
    +
    Buttons
    A group of buttons
    +
    CmdCmdLine
    +
    CmdErrorMsg
    +
    CmdInfoMsg
    +
    CmdInput
    +
    CmdLine
    The command line
    +
    CmdModeMsg
    +
    CmdMoreMsg
    +
    CmdNormal
    +
    CmdOutput
    The output of commands executed by :run
    +
    CmdPrompt
    +
    CmdQuestion
    +
    CmdWarningMsg
    +
    Comment
    JavaScriptor CSS comments
    +
    CompDesc
    The description column of the completion list
    +
    CompGroup
    Item group in completion output
    +
    CompIcon
    The favicon of a completion row
    +
    CompIcon>img
    +
    CompItem
    A single row of output in the completion list
    +
    CompItem[selected]
    A selected row of completion list
    +
    CompLess::after
    The character of indicator shown when completions may be scrolled up
    +
    CompLess
    The indicator shown when completions may be scrolled up
    +
    CompMore::after
    The character of indicator shown when completions may be scrolled down
    +
    CompMore
    The indicator shown when completions may be scrolled down
    +
    CompMsg
    The message which may appear at the top of a group of completion results
    +
    CompResult
    The result column of the completion list
    +
    CompTitle
    Completion row titles
    +
    CompTitleSep
    The element which separates the completion title from its results
    +
    Dense
    Arbitrary elements which should be packed densely together
    +
    Disabled
    Disabled item indicator text
    +
    Download[active]
    A currently active download
    +
    Download
    A download in the :downloads manager
    +
    DownloadButtons
    A button group in the :downloads manager
    +
    DownloadCell
    A table cell in the :downloads manager
    +
    DownloadHead
    A heading in the :downloads manager
    +
    DownloadPercent
    The percentage column for a download
    +
    DownloadProgress
    The progress column for a download
    +
    DownloadProgressHave
    The completed portion of the progress column
    +
    DownloadProgressTotal
    The remaining portion of the progress column
    +
    DownloadSource
    The download source column for a download
    +
    DownloadState
    The download state column for a download
    +
    DownloadTime
    The time remaining column for a download
    +
    DownloadTitle
    The title column for a download
    +
    Downloads
    The :downloads manager
    +
    EditorBlink1
    Text fields briefly after successfully running the external editor, alternated with EditorBlink2
    +
    EditorBlink2
    Text fields briefly after successfully running the external editor, alternated with EditorBlink1
    +
    EditorEditing
    Text fields for which an external editor is open
    +
    EditorError
    Text fields briefly after an error has occurred running the external editor
    +
    Enabled
    Enabled item indicator text
    +
    ErrorMsg
    Error messages
    +
    Filter
    The matching text in a completion list
    +
    FontCode
    The font used for code listings
    +
    FontFixed
    The font used for fixed-width text
    +
    FontProportional
    The font used for proportionally spaced text
    +
    Find
    Text find highlighting. Only background and foreground colors apply.
    +
    FrameIndicator
    The styling applied to briefly indicate the active frame
    +
    Function
    JavaScript functions
    +
    Help
    A help page
    +
    HelpArg
    A required command argument indicator
    +
    HelpBody
    The body of a help page
    +
    HelpBorder
    The styling of bordered elements
    +
    HelpCode
    Code listings
    +
    HelpDefault
    The default value of a help item
    +
    HelpDescription
    The description of a help item
    +
    HelpDescription[short]
    +
    HelpEm
    Emphasized text
    +
    HelpEx
    An Ex command
    +
    HelpExample
    An example
    +
    HelpHead1
    Any help heading
    +
    HelpHead2
    A first-level help heading
    +
    HelpHead3
    A second-level help heading
    +
    HelpHead4
    A third-level help heading
    +
    HelpHead
    A fourth-level help heading
    +
    HelpInclude
    A help page included in the consolidated help listing
    +
    HelpInfo
    Arbitrary information about a help item
    +
    HelpInfoLabel
    The label for a HelpInfo item
    +
    HelpInfoValue
    The details for a HelpInfo item
    +
    HelpItem
    A help item
    +
    HelpKey
    A keyboard key specification
    +
    HelpKeyword
    A keyword
    +
    HelpLink
    A hyperlink
    +
    HelpLink[rel=external]
    A hyperlink to an external resource
    +
    HelpList
    An unordered list
    +
    HelpListItem
    A list item, ordered or unordered
    +
    HelpNews
    A news item
    +
    HelpNewsNew
    A new news item
    +
    HelpNewsOld
    An old news item
    +
    HelpNewsTag
    The version tag for a news item
    +
    HelpNote
    The indicator for a note
    +
    HelpOpt
    An option name
    +
    HelpOptInfo
    Information about the type and default values for an option entry
    +
    HelpOptionalArg
    An optional command argument indicator
    +
    HelpOrderedList1
    A first-level ordered list
    +
    HelpOrderedList2
    A second-level ordered list
    +
    HelpOrderedList3
    A third-level ordered list
    +
    HelpOrderedList4
    A fourth-level ordered list
    +
    HelpOrderedList
    Any ordered list
    +
    HelpParagraph
    An ordinary paragraph
    +
    HelpSpec
    The specification for a help entry
    +
    HelpString
    A quoted string
    +
    HelpTOC
    The Table of Contents for a help page
    +
    HelpTOC>ol
    +
    HelpTT
    Teletype text
    +
    HelpTab
    +
    HelpTabColumn
    +
    HelpTabDescription
    The description column of description tables
    +
    HelpTabRow
    Entire rows in description tables
    +
    HelpTabTitle
    The title column of description tables
    +
    HelpTag
    A help tag
    +
    HelpTags
    A group of help tags
    +
    HelpTopic
    A link to a help topic
    +
    HelpType
    An option type
    +
    HelpWarning
    The indicator for a warning
    +
    HelpXML
    Highlighted XML
    +
    HelpXMLAttribute
    +
    HelpXMLBlock
    +
    HelpXMLComment
    +
    HelpXMLNamespace
    +
    HelpXMLProcessing
    +
    HelpXMLString
    +
    HelpXMLTagEnd
    +
    HelpXMLTagStart
    +
    HelpXMLText
    +
    Hint
    +
    HintActive
    The hint element of link which will be followed by
    +
    HintElem
    The hintable element
    +
    HintImage
    The indicator which floats above hinted images
    +
    Hint[active]
    +
    Indicator
    The # and % in the :buffers list
    +
    InfoMsg
    Information messages
    +
    InlineHelpLink
    A help link shown in the command line or multi-line output area
    +
    Key
    Keywords
    +
    Keyword
    A bookmark keyword for a URL
    +
    LineNr
    The line number of an error
    +
    Link
    A link with additional information shown on hover
    +
    LinkInfo
    Information shown when hovering over a link
    +
    Message
    +
    Message
    A message as displayed in :messages
    +
    ModeMsg
    The mode indicator
    +
    MoreMsg
    The indicator that there is more text to view
    +
    NonText
    The ~ indicators which mark blank lines in the completion list
    +
    Normal
    Normal text
    +
    Null
    JavaScript null values
    +
    Number
    JavaScript numbers
    +
    Object
    JavaScript objects
    +
    Preview
    The completion preview displayed in the &tag.command-line;
    +
    Question
    A prompt for a decision
    +
    REPL-E
    Evaled input in REPL mode
    +
    REPL-P
    Evaled output in REPL mode
    +
    REPL-R
    Prompts in REPL mode
    +
    REPL
    Read-Eval-Print-Loop output
    +
    StatusInfoMsg
    Information messages in the status line
    +
    StatusLine
    The status bar
    +
    StatusLineBroken
    The status bar for a broken web page
    +
    StatusLineExtended
    The status bar for a secure web page with an Extended Validation (EV) certificate
    +
    StatusLineNormal
    The status bar for an ordinary web page
    +
    StatusLineSecure
    The status bar for a secure web page
    +
    StatusModeMsg
    The mode indicator in the status line
    +
    StatusMoreMsg
    +
    StatusNormal
    Normal text in the status line
    +
    StatusQuestion
    A prompt for a decision in the status line
    +
    StatusWarningMsg
    A warning message in the status line
    +
    String
    String values
    +
    TabClose
    The close button of a browser tab
    +
    TabIcon
    The icon of a browser tab
    +
    TabIconNumber
    The number of a browser tab, over its icon
    +
    TabNumber
    The number of a browser tab, next to its icon
    +
    TabText
    The text of a browser tab
    +
    Tag
    A bookmark tag for a URL
    +
    Title
    The title of a listing, including :pageinfo, :jumps
    +
    URL:hover
    +
    URL
    A URL
    +
    URLExtra
    Extra information about a URL
    +
    Usage
    Output from the :*usage commands
    +
    UsageBody
    The body of listings in output from the :*usage commands
    +
    UsageHead
    Headings in output from the :*usage commands
    +
    UsageItem
    Individual items in output from the :*usage commands
    +
    WarningMsg
    A warning message

    @@ -146,21 +284,26 @@ :sty :style - :style -name=name -append filter css + + :style -name=name -group=name -agent filter css + :style -name=name -append filter css + :style -name=name -group=name filter +

    Add CSS styles to the browser or to web pages. filter is a comma-separated list of site-filters for which the style will - apply. Regular expression filters may not be used and the ! - character may not be used to invert the sense of the match. - css is a full CSS rule set (e.g., body { color: blue; }). + apply. The ! character may not be used to invert the sense + of the match. css is a full CSS rule set (e.g., body { + color: blue; }).

    The following options are available:

    -append
    -
    If provided along with -name, css and - filter are appended to its current value. (short name -a)
    +
    If provided along with -name, css and + filter are appended to its current value. (short name + -a)
    -agent
    If provided, the style is installed as an Agent sheet, which diff --git a/common/locale/en-US/tabs.xml b/common/locale/en-US/tabs.xml index d894020..d275bfe 100644 --- a/common/locale/en-US/tabs.xml +++ b/common/locale/en-US/tabs.xml @@ -82,17 +82,6 @@ - - :tabdu :tabduplicate - :counttabduplicate - -

    - Duplicate the current tab and focus the duplicate. If - count is given, duplicate the tab count times. -

    -
    -
    -

    See opening for other ways to open new tabs. @@ -205,6 +194,7 @@ b :b :buffer :countbuffer! url|index + :countbuffer! match countb

    @@ -216,8 +206,9 @@

    If argument is neither a full URL nor an index but uniquely identifies a - buffer, it is selected. With ! the next buffer matching the argument is - selected, even if it cannot be identified uniquely. Use b as a + buffer, by a partial match with the URL or title, it is selected. + With ! the next buffer matching the argument is selected, + even if it cannot be identified uniquely. Use b as a shortcut to open this prompt.

    @@ -285,15 +276,20 @@ :tabm :tabmove - :tabmove N - :tabmove! +N | -N + :tabmove N + :tabmove match + :tabmove! +N|-N

    - Move the current tab to a position after tab N. When N is 0, the - current tab is made the first one. Without N the current tab is made the - last one. N can also be prefixed with ‘+’ or ‘-’ to indicate a relative - movement. If ! is specified the movement wraps around the start or end of the - tab list. + Move the current tab to the position of tab N. When N + is $, the current tab is made the last one. N can + also be prefixed with + or - to indicate a + relative movement. If ! is specified the movement wraps + around the start or end of the tab list. +

    +

    + The tab index may also be selected by a general match string + like :buffer.

    @@ -381,6 +377,32 @@ +

    Application Tabs

    + + + :pin :pintab + :countpintab! arg + +

    + Pin tab as an application tab. If ! is given, + the tab's pinned state is toggled. Arguments and count + are the same as for :bdelete and :buffer. +

    +
    +
    + + + :unpin :unpintab + :countunpintab arg + +

    + Unpin tab as an application tab. Arguments and count + are the same as for :pintab. +

    +
    +
    + + diff --git a/common/locale/en-US/various.xml b/common/locale/en-US/various.xml index 78ade2e..39d80dc 100644 --- a/common/locale/en-US/various.xml +++ b/common/locale/en-US/various.xml @@ -87,7 +87,7 @@ - CTRL-L :redr :redraw]]> + CTRL-L :redr :redraw]]> :redraw @@ -166,7 +166,7 @@

    Online help

    - :help :h help]]> + :help :h help]]> :help subject <F1> @@ -180,7 +180,7 @@ - :helpall :helpa help-all]]> + :helpall :helpa help-all]]> :helpall subject <A-F1> @@ -262,7 +262,7 @@

    Start Caret mode. This mode resembles the Vim's Normal mode where the text cursor is visible on the web page. The v key - enters visual mode, where text is selected as the cursor moves. + enters Visual mode, where text is selected as the cursor moves.

    diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index 03c4e0f..6f43950 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -22,8 +22,8 @@ var callResult = function callResult(method) { var listener = function listener(action, event) function addonListener(install) { this.dactyl[install.error ? "echoerr" : "echomsg"]( - "Add-on " + action + " " + event + ": " + (install.name || install.sourceURI.spec) + - (install.error ? ": " + addonErrors[install.error] : "")); + _("addon.error", action, event, (install.name || install.sourceURI.spec) + + (install.error ? ": " + addons.errors[install.error] : ""))); } var AddonListener = Class("AddonListener", { @@ -59,17 +59,18 @@ var updateAddons = Class("UpgradeListener", AddonListener, { }, onUpdateAvailable: function (addon, install) { + util.dump("onUpdateAvailable"); this.upgrade.push(addon); install.addListener(this); install.install(); }, onUpdateFinished: function (addon, error) { - this.remaining = this.remaining.filter(function (a) a != addon); + this.remaining = this.remaining.filter(function (a) a.type != addon.type || a.id != addon.id); if (!this.remaining.length) this.dactyl.echomsg( this.upgrade.length - ? "Installing updates for addons: " + this.upgrade.map(function (i) i.name).join(", ") - : "No addon updates found"); + ? _("addon.installingUpdates", this.upgrade.map(function (i) i.name).join(", ")) + : _("addon.noUpdates")); } }); @@ -110,15 +111,15 @@ var actions = { name: "extr[ehash]", description: "Reload an extension", action: function (addon) { - util.assert(util.haveGecko("2b"), _("error.notUseful", config.host)); + util.assert(util.haveGecko("2b"), _("command.notUseful", config.host)); util.timeout(function () { addon.userDisabled = true; addon.userDisabled = false; }); }, get filter() { - let ids = set(keys(JSON.parse(prefs.get("extensions.bootstrappedAddons", "{}")))); - return function ({ item }) !item.userDisabled && set.has(ids, item.id); + let ids = Set(keys(JSON.parse(prefs.get("extensions.bootstrappedAddons", "{}")))); + return function ({ item }) !item.userDisabled && Set.has(ids, item.id); }, perm: "disable" }, @@ -151,11 +152,11 @@ var Addon = Class("Addon", {
, @@ -165,7 +166,7 @@ var Addon = Class("Addon", { }, commandAllowed: function commandAllowed(cmd) { - util.assert(set.has(actions, cmd), "Unknown command"); + util.assert(Set.has(actions, cmd), _("addon.unknownCommand")); let action = actions[cmd]; if ("perm" in action && !(this.permissions & AddonManager["PERM_CAN_" + action.perm.toUpperCase()])) @@ -176,7 +177,7 @@ var Addon = Class("Addon", { }, command: function command(cmd) { - util.assert(this.commandAllowed(cmd), "Command not allowed"); + util.assert(this.commandAllowed(cmd), _("addon.commandNotAllowed")); let action = actions[cmd]; if (action.action) @@ -282,11 +283,11 @@ var AddonList = Class("AddonList", { XML.ignoreWhitespace = true; util.xmlToDom(
- ModeCommandAction{_("title.Mode")}{_("title.Command")}{_("title.Action")}
- On  - Off - Del - Upd - Opt + {_("addon.action.On")} + {_("addon.action.Off")} + {_("addon.action.Delete")} + {_("addon.action.Update")} + {_("addon.action.Options")}
- - - + + + +
NameVersionStatus{_("title.Name")}{_("title.Version")}{_("title.Status")} - Description{_("title.Description")}
, this.document, this.nodes); @@ -347,6 +348,11 @@ var AddonList = Class("AddonList", { }); var Addons = Module("addons", { + errors: Class.memoize(function () + array(["ERROR_NETWORK_FAILURE", "ERROR_INCORRECT_HASH", + "ERROR_CORRUPT_FILE", "ERROR_FILE_ACCESS"]) + .map(function (e) [AddonManager[e], _("AddonManager." + e)]) + .toObject()) }, { }, { commands: function (dactyl, modules, window) { @@ -393,7 +399,7 @@ var Addons = Module("addons", { else if (file.isDirectory()) dactyl.echoerr(_("addon.cantInstallDir", file.path.quote())); else - dactyl.echoerr(_("io.notReadable-1", file.path)); + dactyl.echoerr(_("io.notReadable", file.path)); }, { argCount: "1", completer: function (context) { @@ -413,11 +419,11 @@ var Addons = Module("addons", { function (args) { let name = args[0]; if (args.bang && !command.bang) - dactyl.assert(!name, _("error.trailing")); + dactyl.assert(!name, _("error.trailingCharacters")); else dactyl.assert(name, _("error.argumentRequired")); - AddonManager.getAddonsByTypes(["extension"], dactyl.wrapCallback(function (list) { + AddonManager.getAddonsByTypes(args["-types"], dactyl.wrapCallback(function (list) { if (!args.bang || command.bang) { list = list.filter(function (extension) extension.name == name); if (list.length == 0) @@ -433,13 +439,22 @@ var Addons = Module("addons", { }, { argCount: "?", // FIXME: should be "1" bang: true, - completer: function (context) { - completion.extension(context); + completer: function (context, args) { + completion.extension(context, args["-types"]); context.filters.push(function ({ item }) ok(item)); if (command.filter) context.filters.push(command.filter); }, - literal: 0 + literal: 0, + options: [ + { + names: ["-types", "-type", "-t"], + description: "The add-on types to operate on", + default: ["extension"], + completer: function (context, args) completion.addonType(context), + type: CommandOption.LIST + } + ] }); }); }, @@ -460,8 +475,8 @@ var Addons = Module("addons", { true)); }); } - } - } + }; + }; completion.extension = function extension(context, types) { context.title = ["Extension"]; @@ -494,27 +509,14 @@ else addon = this.wrapAddon(addon); return callback(addon); }, + wrapAddon: function wrapAddon(addon) { addon = Object.create(addon.QueryInterface(Ci.nsIUpdateItem)); - function getRdfProperty(item, property) { - let resource = services.rdf.GetResource("urn:mozilla:item:" + item.id); - let value = ""; - - if (resource) { - let target = services.extensionManager.datasource.GetTarget(resource, - services.rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true); - if (target && target instanceof Ci.nsIRDFLiteral) - value = target.Value; - } - - return value; - } - ["aboutURL", "creator", "description", "developers", "homepageURL", "installDate", "optionsURL", "releaseNotesURI", "updateDate"].forEach(function (item) { - memoize(addon, item, function (item) getRdfProperty(this, item)); + memoize(addon, item, function (item) this.getProperty(item)); }); update(addon, { @@ -523,19 +525,33 @@ else appDisabled: false, + getProperty: function getProperty(property) { + let resource = services.rdf.GetResource("urn:mozilla:item:" + this.id); + + if (resource) { + let target = services.extensionManager.datasource.GetTarget(resource, + services.rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true); + + if (target && target instanceof Ci.nsIRDFLiteral) + return target.Value; + } + + return ""; + }, + installLocation: Class.memoize(function () services.extensionManager.getInstallLocation(this.id)), getResourceURI: function getResourceURI(path) { let file = this.installLocation.getItemFile(this.id, path); return services.io.newFileURI(file); }, - isActive: getRdfProperty(addon, "isDisabled") != "true", + get isActive() this.getProperty("isDisabled") != "true", uninstall: function uninstall() { services.extensionManager.uninstallItem(this.id); }, - get userDisabled() getRdfProperty(addon, "userDisabled") === "true", + get userDisabled() this.getProperty("userDisabled") === "true", set userDisabled(val) { services.extensionManager[val ? "disableItem" : "enableItem"](this.id); } @@ -543,6 +559,7 @@ else return addon; }, + getAddonsByTypes: function (types, callback) { let res = []; for (let [, type] in Iterator(types)) @@ -554,6 +571,7 @@ else util.timeout(function () { callback(res); }); return res; }, + getInstallForFile: function (file, callback, mimetype) { callback({ addListener: function () {}, @@ -562,9 +580,11 @@ else } }); }, + getInstallForURL: function (url, callback, mimetype) { util.assert(false, _("error.unavailable", config.host, services.runtime.version)); }, + observers: [], addAddonListener: function (listener) { observer.listener = listener; @@ -595,12 +615,6 @@ else } }; -var addonErrors = array.toObject([ - [AddonManager.ERROR_NETWORK_FAILURE, "A network error occurred"], - [AddonManager.ERROR_INCORRECT_HASH, "The downloaded file did not match the expected hash"], - [AddonManager.ERROR_CORRUPT_FILE, "The file appears to be corrupt"], - [AddonManager.ERROR_FILE_ACCESS, "There was an error accessing the filesystem"]]); - endModule(); } catch(e){ if (isString(e)) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 13bd9a0..d07ba12 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -37,65 +37,83 @@ if (!Object.create) }; if (!Object.defineProperty) Object.defineProperty = function defineProperty(obj, prop, desc) { - let value = desc.value; - if ("value" in desc) - if (desc.writable && !__lookupGetter__.call(obj, prop) - && !__lookupSetter__.call(obj, prop)) - try { - obj[prop] = value; + try { + let value = desc.value; + if ("value" in desc) + if (desc.writable && !__lookupGetter__.call(obj, prop) + && !__lookupSetter__.call(obj, prop)) + try { + obj[prop] = value; + } + catch (e if e instanceof TypeError) {} + else { + objproto.__defineGetter__.call(obj, prop, function () value); + if (desc.writable) + objproto.__defineSetter__.call(obj, prop, function (val) { value = val; }); } - catch (e if e instanceof TypeError) {} - else { - objproto.__defineGetter__.call(obj, prop, function () value); - if (desc.writable) - objproto.__defineSetter__.call(obj, prop, function (val) { value = val; }); - } - if ("get" in desc) - objproto.__defineGetter__.call(obj, prop, desc.get); - if ("set" in desc) - objproto.__defineSetter__.call(obj, prop, desc.set); + if ("get" in desc) + objproto.__defineGetter__.call(obj, prop, desc.get); + if ("set" in desc) + objproto.__defineSetter__.call(obj, prop, desc.set); + } + catch (e) { + throw e.stack ? e : Error(e); + } }; if (!Object.defineProperties) Object.defineProperties = function defineProperties(obj, props) { for (let [k, v] in Iterator(props)) Object.defineProperty(obj, k, v); - } + }; if (!Object.freeze) Object.freeze = function freeze(obj) {}; +if (!Object.getPropertyDescriptor) + Object.getPropertyDescriptor = function getPropertyDescriptor(obj, prop) { + try { + let desc = { + configurable: true, + enumerable: propertyIsEnumerable.call(obj, prop) + }; + var get = __lookupGetter__.call(obj, prop), + set = __lookupSetter__.call(obj, prop); + if (!get && !set) { + desc.value = obj[prop]; + desc.writable = true; + } + if (get) + desc.get = get; + if (set) + desc.set = set; + return desc; + } + catch (e) { + throw e.stack ? e : Error(e); + } + }; if (!Object.getOwnPropertyDescriptor) Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(obj, prop) { - if (!hasOwnProperty.call(obj, prop)) - return undefined; - let desc = { - configurable: true, - enumerable: propertyIsEnumerable.call(obj, prop) - }; - var get = __lookupGetter__.call(obj, prop), - set = __lookupSetter__.call(obj, prop); - if (!get && !set) { - desc.value = obj[prop]; - desc.writable = true; - } - if (get) - desc.get = get; - if (set) - desc.set = set; - return desc; + if (hasOwnProperty.call(obj, prop)) + return Object.getPropertyDescriptor(obj, prop); }; if (!Object.getOwnPropertyNames) Object.getOwnPropertyNames = function getOwnPropertyNames(obj, _debugger) { - // This is an ugly and unfortunately necessary hack. - if (hasOwnProperty.call(obj, "__iterator__")) { - var oldIter = obj.__iterator__; - delete obj.__iterator__; + try { + // This is an ugly and unfortunately necessary hack. + if (hasOwnProperty.call(obj, "__iterator__")) { + var oldIter = obj.__iterator__; + delete obj.__iterator__; + } + let res = [k for (k in obj) if (hasOwnProperty.call(obj, k))]; + if (oldIter !== undefined) { + obj.__iterator__ = oldIter; + res.push("__iterator__"); + } + return res; } - let res = [k for (k in obj) if (hasOwnProperty.call(obj, k))]; - if (oldIter !== undefined) { - obj.__iterator__ = oldIter; - res.push("__iterator__"); + catch (e) { + throw e.stack ? e : Error(e); } - return res; }; if (!Object.getPrototypeOf) Object.getPrototypeOf = function getPrototypeOf(obj) obj.__proto__; @@ -127,6 +145,7 @@ function defineModule(name, params, module) { use[mod].push(module); } currentModule = module; + module.startTime = Date.now(); } defineModule.loadLog = []; @@ -148,7 +167,6 @@ defineModule.dump = function dump_() { .replace(/^./gm, name + ": $&")); } defineModule.modules = []; -defineModule.times = { all: 0 }; defineModule.time = function time(major, minor, func, self) { let time = Date.now(); if (typeof func !== "function") @@ -161,13 +179,7 @@ defineModule.time = function time(major, minor, func, self) { loaded.util && util.reportError(e); } - let delta = Date.now() - time; - defineModule.times.all += delta; - defineModule.times[major] = (defineModule.times[major] || 0) + delta; - if (minor) { - defineModule.times[":" + minor] = (defineModule.times[":" + minor] || 0) + delta; - defineModule.times[major + ":" + minor] = (defineModule.times[major + ":" + minor] || 0) + delta; - } + JSMLoader.times.add(major, minor, Date.now() - time); return res; } @@ -185,8 +197,10 @@ function require(obj, name, from) { if (arguments.length === 1) [obj, name] = [{}, obj]; + let caller = Components.stack.caller; + if (!loaded[name]) - defineModule.loadLog.push((from || "require") + ": loading " + name + (obj.NAME ? " into " + obj.NAME : "")); + defineModule.loadLog.push((from || "require") + ": loading " + name + " into " + (obj.NAME || caller.filename + ":" + caller.lineNumber)); JSMLoader.load(name + ".jsm", obj); return obj; @@ -196,7 +210,7 @@ function require(obj, name, from) { if (loaded.util) util.reportError(e); else - defineModule.dump(" " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e +"\n"); + defineModule.dump(" " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e + "\n"); } } @@ -204,10 +218,10 @@ defineModule("base", { // sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/ "\2",/p' base.jsm | sort | fmt exports: [ "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Module", "JSMLoader", "Object", "Runnable", - "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper", + "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper", "array", "bind", "call", "callable", "ctypes", "curry", "debuggerProperties", "defineModule", "deprecated", "endModule", "forEach", "isArray", "isGenerator", "isinstance", "isObject", - "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties","keys", "memoize", "octal", + "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties", "keys", "memoize", "octal", "properties", "require", "set", "update", "values", "withCallerGlobal" ], use: ["config", "services", "util"] @@ -259,7 +273,7 @@ function properties(obj, prototypes, debugger_) { try { if ("dactylPropertyNames" in obj && !prototypes) for (let key in values(obj.dactylPropertyNames)) - if (key in obj && !set.add(seen, key)) + if (key in obj && !Set.add(seen, key)) yield key; } catch (e) {} @@ -274,7 +288,7 @@ function properties(obj, prototypes, debugger_) { iter = (prop.name.stringValue for (prop in values(debuggerProperties(obj)))); for (let key in iter) - if (!prototypes || !set.add(seen, key) && obj != orig) + if (!prototypes || !Set.add(seen, key) && obj != orig) yield key; } } @@ -292,7 +306,8 @@ function deprecated(alternative, fn) { let name, func = callable(fn) ? fn : function () this[fn].apply(this, arguments); function deprecatedMethod() { - let obj = this.className ? this.className + "#" : + let obj = !this ? "" : + this.className ? this.className + "#" : this.constructor.className ? this.constructor.className + "#" : ""; @@ -307,17 +322,16 @@ function deprecated(alternative, fn) { } deprecated.warn = function warn(func, name, alternative, frame) { if (!func.seenCaller) - func.seenCaller = set([ + func.seenCaller = Set([ "resource://dactyl" + JSMLoader.suffix + "/javascript.jsm", "resource://dactyl" + JSMLoader.suffix + "/util.jsm" ]); frame = frame || Components.stack.caller.caller; let filename = util.fixURI(frame.filename || "unknown"); - if (!set.add(func.seenCaller, filename)) - util.dactyl(func).warn( - util.urlPath(filename) + ":" + frame.lineNumber + ": " + - name + " is deprecated: Please use " + alternative + " instead"); + if (!Set.add(func.seenCaller, filename)) + util.dactyl(func).warn([util.urlPath(filename), frame.lineNumber, " "].join(":") + + require("messages")._("warn.deprecated", name, alternative)); } /** @@ -359,7 +373,7 @@ var iterAll = deprecated("iter", function iterAll() iter.apply(null, arguments)) * @param {[string]} ary @optional * @returns {object} */ -function set(ary) { +function Set(ary) { let obj = {}; if (ary) for (let val in values(ary)) @@ -374,7 +388,7 @@ function set(ary) { * @param {string} key The key to add. * @returns boolean */ -set.add = curry(function set_add(set, key) { +Set.add = curry(function set_add(set, key) { let res = this.has(set, key); set[key] = true; return res; @@ -386,7 +400,7 @@ set.add = curry(function set_add(set, key) { * @param {string} key The key to check. * @returns {boolean} */ -set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) && +Set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) && propertyIsEnumerable.call(set, key)); /** * Returns a new set containing the members of the first argument which @@ -395,7 +409,7 @@ set.has = curry(function set_has(set, key) hasOwnProperty.call(set, key) && * @param {object} set The set. * @returns {object} */ -set.subtract = function set_subtract(set) { +Set.subtract = function set_subtract(set) { set = update({}, set); for (let i = 1; i < arguments.length; i++) for (let k in keys(arguments[i])) @@ -410,12 +424,23 @@ set.subtract = function set_subtract(set) { * @param {string} key The key to remove. * @returns boolean */ -set.remove = curry(function set_remove(set, key) { +Set.remove = curry(function set_remove(set, key) { let res = set.has(set, key); delete set[key]; return res; }); +function set() { + deprecated.warn(set, "set", "Set"); + return Set.apply(this, arguments); +} +Object.keys(Set).forEach(function (meth) { + set[meth] = function proxy() { + deprecated.warn(proxy, "set." + meth, "Set." + meth); + return Set[meth].apply(Set, arguments); + }; +}); + /** * Curries a function to the given number of arguments. Each * call of the resulting function returns a new function. When @@ -464,9 +489,13 @@ function curry(fn, length, self, acc) { } if (curry.bind) - var bind = function bind(func) func.bind.apply(func, Array.slice(arguments, bind.length)); + var bind = function bind(meth, self) let (func = callable(meth) ? meth : self[meth]) + func.bind.apply(func, Array.slice(arguments, 1)); else var bind = function bind(func, self) { + if (!callable(func)) + func = self[func]; + let args = Array.slice(arguments, bind.length); return function bound() func.apply(self, args.concat(Array.slice(arguments))); }; @@ -582,7 +611,7 @@ function call(fn) { */ function memoize(obj, key, getter) { if (arguments.length == 1) { - obj = update({}, obj); + obj = update({ __proto__: obj.__proto__ }, obj); for (let prop in Object.getOwnPropertyNames(obj)) { let get = __lookupGetter__.call(obj, prop); if (get) @@ -591,17 +620,22 @@ function memoize(obj, key, getter) { return obj; } - Object.defineProperty(obj, key, { - configurable: true, - enumerable: true, + try { + Object.defineProperty(obj, key, { + configurable: true, + enumerable: true, - get: function g_replaceProperty() ( - Class.replaceProperty(this.instance || this, key, null), - Class.replaceProperty(this.instance || this, key, getter.call(this, key))), + get: function g_replaceProperty() ( + Class.replaceProperty(this.instance || this, key, null), + Class.replaceProperty(this.instance || this, key, getter.call(this, key))), - set: function s_replaceProperty(val) - Class.replaceProperty(this.instance || this, key, val) - }); + set: function s_replaceProperty(val) + Class.replaceProperty(this.instance || this, key, val) + }); + } + catch (e) { + obj[key] = getter.call(obj, key); + } } let sandbox = Cu.Sandbox(this); @@ -648,12 +682,14 @@ function update(target) { if (typeof desc.value === "function" && target.__proto__) { let func = desc.value.wrapped || desc.value; - func.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]); - func.superapply = function superapply(self, args) - let (meth = Object.getPrototypeOf(target)[k]) - meth && meth.apply(self, args); - func.supercall = function supercall(self) - func.superapply(self, Array.slice(arguments, 1)); + if (!func.superapply) { + func.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]); + func.superapply = function superapply(self, args) + let (meth = Object.getPrototypeOf(target)[k]) + meth && meth.apply(self, args); + func.supercall = function supercall(self) + func.superapply(self, Array.slice(arguments, 1)); + } } try { Object.defineProperty(target, k, desc); @@ -696,18 +732,28 @@ function Class() { if (callable(args[0])) superclass = args.shift(); - var Constructor = eval(String.replace(, - "constructor", (name || superclass.className).replace(/\W/g, "_")) - .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1] - .replace(/\b(self|res|Constructor)\b/g, "$1_"))); + }; + else + var Constructor = eval(String.replace(, + "constructor", (name || superclass.className).replace(/\W/g, "_")) + .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1] + .replace(/\b(self|res|Constructor)\b/g, "$1_"))); Constructor.className = name || superclass.className || superclass.name; @@ -735,19 +781,19 @@ function Class() { } if (Cu.getGlobalForObject) - Class.objectGlobal = function (caller) { + Class.objectGlobal = function (object) { try { - return Cu.getGlobalForObject(caller); + return Cu.getGlobalForObject(object); } catch (e) { return null; } }; else - Class.objectGlobal = function (caller) { - while (caller.__parent__) - caller = caller.__parent__; - return caller; + Class.objectGlobal = function (object) { + while (object.__parent__) + object = object.__parent__; + return object; }; /** @@ -791,7 +837,7 @@ Class.extend = function extend(subclass, superclass, overrides) { * * @param {function(string)} getter The function which returns the * property's value. - * @return {Class.Property} + * @returns {Class.Property} */ Class.memoize = function memoize(getter, wait) Class.Property({ @@ -883,7 +929,48 @@ Class.prototype = { util.trapErrors(callback, self); } return services.Timer(timeout_notify, timeout || 0, services.Timer.TYPE_ONE_SHOT); - } + }, + + /** + * Updates this instance with the properties of the given objects. + * Like the update function, but with special semantics for + * localized properties. + */ + update: function update() { + let self = this; + // XXX: Duplication. + + for (let i = 0; i < arguments.length; i++) { + let src = arguments[i]; + Object.getOwnPropertyNames(src || {}).forEach(function (k) { + let desc = Object.getOwnPropertyDescriptor(src, k); + if (desc.value instanceof Class.Property) + desc = desc.value.init(k, this) || desc.value; + + if (typeof desc.value === "function") { + let func = desc.value.wrapped || desc.value; + if (!func.superapply) { + func.__defineGetter__("super", function () Object.getPrototypeOf(self)[k]); + func.superapply = function superapply(self, args) + let (meth = Object.getPrototypeOf(self)[k]) + meth && meth.apply(self, args); + func.supercall = function supercall(self) + func.superapply(self, Array.slice(arguments, 1)); + } + } + + try { + if ("value" in desc && (k in this.localizedProperties || k in this.magicalProperties)) + this[k] = desc.value; + else + Object.defineProperty(this, k, desc); + } + catch (e) {} + }, this); + } + }, + + magicalProperties: {} }; Class.makeClosure = function makeClosure() { const self = this; @@ -946,7 +1033,9 @@ var ErrorBase = Class("ErrorBase", Error, { level: 2, init: function EB_init(message, level) { level = level || 0; - update(this, Error(message)) + let error = Error(message); + update(this, error) + this.stack = error.stack; this.message = message; let frame = Components.stack; @@ -956,7 +1045,8 @@ var ErrorBase = Class("ErrorBase", Error, { } this.fileName = frame.filename; this.lineNumber = frame.lineNumber; - } + }, + toString: function () String(this.message) }); /** @@ -1368,7 +1458,7 @@ update(iter, { uniq: function uniq(iter) { let seen = {}; for (let item in iter) - if (!set.add(seen, item)) + if (!Set.add(seen, item)) yield item; }, @@ -1423,7 +1513,7 @@ var array = Class("array", Array, { * as such: * [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" } * - * @param {Array[]} assoc + * @param {[Array]} assoc * @... {string} 0 - Key * @... 1 - Value */ diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm index afaa56b..e85a7c6 100644 --- a/common/modules/bookmarkcache.jsm +++ b/common/modules/bookmarkcache.jsm @@ -30,6 +30,8 @@ update(Bookmark.prototype, { }) Bookmark.setter = function (key, func) this.prototype.__defineSetter__(key, func); Bookmark.setter("url", function (val) { + if (isString(val)) + val = util.newURI(val); let tags = this.tags; this.tags = null; services.bookmarks.changeBookmarkURI(this.id, val); @@ -149,7 +151,6 @@ var BookmarkCache = Module("BookmarkCache", XPCOM(Ci.nsINavBookmarkObserver), { return this.rootFolders.indexOf(root) >= 0; }, - // Should be made thread safe. load: function load() { let bookmarks = {}; diff --git a/common/modules/bootstrap.jsm b/common/modules/bootstrap.jsm index 005ba16..d51f869 100644 --- a/common/modules/bootstrap.jsm +++ b/common/modules/bootstrap.jsm @@ -6,6 +6,8 @@ try { +let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + var EXPORTED_SYMBOLS = ["JSMLoader"]; var BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap"; @@ -17,21 +19,53 @@ if (!JSMLoader && "@mozilla.org/fuel/application;1" in Components.classes) .getService(Components.interfaces.extIApplication) .storage.get("dactyl.JSMLoader", null); -if (JSMLoader && JSMLoader.bump === 4) +if (JSMLoader && JSMLoader.bump === 5) JSMLoader.global = this; else JSMLoader = { - bump: 4, - builtin: Components.utils.Sandbox(this), + bump: 5, + + builtin: Cu.Sandbox(this), + canonical: {}, + factories: [], + global: this, + globals: JSMLoader ? JSMLoader.globals : {}, - io: Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService), - loader: Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader), - manager: Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar), + + io: Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService), + + loader: Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader), + + manager: Components.manager.QueryInterface(Ci.nsIComponentRegistrar), + + modules: JSMLoader && JSMLoader.modules || {}, + stale: JSMLoader ? JSMLoader.stale : {}, + suffix: "", + + times: { + all: 0, + add: function add(major, minor, delta) { + this.all += delta; + + this[major] = (this[major] || 0) + delta; + if (minor) { + minor = ":" + minor; + this[minor] = (this[minor] || 0) + delta; + this[major + minor] = (this[major + minor] || 0) + delta; + } + }, + clear: function clear() { + for (let key in this) + if (typeof this[key] !== "number") + delete this[key]; + } + }, + init: function init(suffix) { this.initialized = true; this.suffix = suffix || ""; @@ -41,14 +75,16 @@ else this.global.JSMLoader = this; base.JSMLoader = this; }, + getTarget: function getTarget(url) { if (url.indexOf(":") === -1) url = "resource://dactyl" + this.suffix + "/" + url; let chan = this.io.newChannel(url, null, null); - chan.cancel(Components.results.NS_BINDING_ABORTED); + chan.cancel(Cr.NS_BINDING_ABORTED); return chan.name; }, + load: function load(name, target) { let url = name; if (url.indexOf(":") === -1) @@ -68,7 +104,13 @@ else } try { - let global = Components.utils.import(url, target); + let now = Date.now(); + this.modules[url] = true; + let global = Cu.import(url, target); + + if (!(name in this.globals)) + this.times.add("require", name, Date.now() - now); + return this.globals[name] = global; } catch (e) { @@ -76,33 +118,52 @@ else throw e; } }, - loadSubScript: function loadSubScript() this.loader.loadSubScript.apply(this.loader, arguments), + + loadSubScript: function loadSubScript(script) { + let now = Date.now(); + this.loader.loadSubScript.apply(this.loader, arguments); + this.times.add("loadSubScript", script, Date.now() - now); + }, + cleanup: function unregister() { for each (let factory in this.factories.splice(0)) this.manager.unregisterFactory(factory.classID, factory); }, + purge: function purge() { dump("dactyl: JSMLoader: purge\n"); - for (let [url, global] in Iterator(this.globals)) { - if (url === "bootstrap.jsm" || url === "resource://dactyl/bootstrap.jsm") - continue; - - let target = this.getTarget(url); - this.stale[url] = target; - this.stale[target] = target; - - for each (let prop in Object.getOwnPropertyNames(global)) + if (Cu.unload) { + Object.keys(this.modules).reverse().forEach(function (url) { try { - if (!(prop in this.builtin) && - ["JSMLoader", "set", "EXPORTED_SYMBOLS"].indexOf(prop) < 0 && - !global.__lookupGetter__(prop)) - global[prop] = undefined; + Cu.unload(url); } catch (e) { - dump("Deleting property " + prop + " on " + url + ":\n " + e + "\n"); - Components.utils.reportError(e); + Cu.reportError(e); } + }); + } + else { + for (let [url, global] in Iterator(this.globals)) { + if (url === "bootstrap.jsm" || url === "resource://dactyl/bootstrap.jsm") + continue; + + let target = this.getTarget(url); + this.stale[url] = target; + this.stale[target] = target; + + for each (let prop in Object.getOwnPropertyNames(global)) + try { + if (!(prop in this.builtin) && + ["JSMLoader", "Set", "set", "EXPORTED_SYMBOLS"].indexOf(prop) < 0 && + !global.__lookupGetter__(prop)) + global[prop] = undefined; + } + catch (e) { + dump("Deleting property " + prop + " on " + url + ":\n " + e + "\n"); + Cu.reportError(e); + } + } } }, diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index 1036f20..7d7093e 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -94,7 +94,7 @@ update(CommandOption, { * A class representing Ex commands. Instances are created by * the {@link Commands} class. * - * @param {string[]} specs The names by which this command can be invoked. + * @param {[string]} specs The names by which this command can be invoked. * These are specified in the form "com[mand]" where "com" is a unique * command name prefix. * @param {string} description A short one line description of the command. @@ -129,7 +129,7 @@ var Command = Class("Command", { this.action = action; if (extraInfo) - update(this, extraInfo); + this.update(extraInfo); if (this.options) this.options = this.options.map(CommandOption.fromArray, CommandOption); for each (let option in this.options) @@ -142,7 +142,7 @@ var Command = Class("Command", { get helpTag() ":" + this.name, - get lastCommand() this._lastCommand || commandline.command, + get lastCommand() this._lastCommand || this.modules.commandline.command, set lastCommand(val) { this._lastCommand = val; }, /** @@ -155,16 +155,13 @@ var Command = Class("Command", { const { dactyl } = this.modules; let context = args.context; - if (this.deprecated && !set.add(this.complained, context ? context.file : "[Command Line]")) { - let loc = contexts.context ? context.file + ":" + context.line + ": " : ""; - dactyl.echoerr(loc + ":" + this.name + " is deprecated" + - (isString(this.deprecated) ? ": " + this.deprecated : "")); - } + if (this.deprecated) + this.warn(context, "deprecated", _("warn.deprecated", ":" + this.name, this.deprecated)); modifiers = modifiers || {}; if (args.count != null && !this.count) - throw FailedAssertion(_("command.noRange")); + throw FailedAssertion(_("command.noCount")); if (args.bang && !this.bang) throw FailedAssertion(_("command.noBang")); @@ -212,23 +209,27 @@ var Command = Class("Command", { complained: Class.memoize(function () ({})), /** - * @property {string[]} All of this command's name specs. e.g., "com[mand]" + * @property {[string]} All of this command's name specs. e.g., "com[mand]" */ specs: null, - /** @property {string[]} All of this command's short names, e.g., "com" */ + /** @property {[string]} All of this command's short names, e.g., "com" */ shortNames: null, /** - * @property {string[]} All of this command's long names, e.g., "command" + * @property {[string]} All of this command's long names, e.g., "command" */ longNames: null, /** @property {string} The command's canonical name. */ name: null, - /** @property {string[]} All of this command's long and short names. */ + /** @property {[string]} All of this command's long and short names. */ names: null, /** @property {string} This command's description, as shown in :listcommands */ description: Messages.Localized(""), + + /** @property {string|null} If set, the deprecation message for this command. */ + deprecated: Messages.Localized(null), + /** * @property {function (Args)} The function called to execute this command. */ @@ -295,7 +296,7 @@ var Command = Class("Command", { explicitOpts: Class.memoize(function () ({})), - has: function AP_has(opt) set.has(this.explicitOpts, opt) || typeof opt === "number" && set.has(this, opt), + has: function AP_has(opt) Set.has(this.explicitOpts, opt) || typeof opt === "number" && Set.has(this, opt), get literalArg() this.command.literal != null && this[this.command.literal] || "", @@ -309,7 +310,7 @@ var Command = Class("Command", { util.assert((this.length == 0 || this.command.argCount !== "0") && (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)), - _("error.trailing")); + _("error.trailingCharacters")); } } }); @@ -356,7 +357,21 @@ var Command = Class("Command", { * @property {string} For commands defined via :command, contains the Ex * command line to be executed upon invocation. */ - replacementText: null + replacementText: null, + + /** + * Warns of a misuse of this command once per warning type per file. + * + * @param {object} context The calling context. + * @param {string} type The type of warning. + * @param {string} warning The warning message. + */ + warn: function warn(context, type, message) { + let loc = !context ? "" : [context.file, context.line, " "].join(":"); + + if (!Set.add(this.complained, type + ":" + (context ? context.file : "[Command Line]"))) + this.modules.dactyl.warn(loc + message); + } }, { // TODO: do we really need more than longNames as a convenience anyway? /** @@ -395,10 +410,12 @@ var Ex = Module("Ex", { else { let opt = cmd.optionMap["-" + k]; let val = opt.type && opt.type.parse(v); + util.assert(val != null && (typeof val !== "number" || !isNaN(val)), _("option.noSuch", k)); - Class.replaceProperty(args, opt.names[0], val); - args.explicitOpts[opt.names[0]] = val; + + Class.replaceProperty(res, opt.names[0], val); + res.explicitOpts[opt.names[0]] = val; } for (let [i, val] in array.iterItems(args)) res[i] = String(val); @@ -447,7 +464,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { * Adds a new command to the builtin hive. Accessible only to core * dactyl code. Plugins should use group.commands.add instead. * - * @param {string[]} names The names by which this command can be + * @param {[string]} specs The names by which this command can be * invoked. The first name specified is the command's canonical * name. * @param {string} description A description of the command. @@ -455,7 +472,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { * @param {Object} extra An optional extra configuration hash. * @optional */ - add: function add(names, description, action, extra, replace) { + add: function add(specs, description, action, extra, replace) { const { commands, contexts } = this.modules; extra = extra || {}; @@ -463,7 +480,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { extra.definedAt = contexts.getCaller(Components.stack.caller); extra.hive = this; - extra.parsedSpecs = Command.parseSpecs(names); + extra.parsedSpecs = Command.parseSpecs(specs); let names = array.flatten(extra.parsedSpecs); let name = names[0]; @@ -483,7 +500,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { let self = this; let closure = function () self._map[name]; - memoize(this._map, name, function () commands.Command(names, description, action, extra)); + memoize(this._map, name, function () commands.Command(specs, description, action, extra)); memoize(this._list, this._list.length, closure); for (let alias in values(names.slice(1))) memoize(this._map, alias, closure); @@ -624,48 +641,58 @@ var Commands = Module("commands", { }, /** - * Displays a list of user-defined commands. + * Lists all user-defined commands matching *filter* and optionally + * *hives*. + * + * @param {string} filter Limits the list to those commands with a name + * matching this anchored substring. + * @param {[Hive]} hives List of hives. + * @optional */ - list: function list() { + list: function list(filter, hives) { const { commandline, completion } = this.modules; function completerToString(completer) { if (completer) return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom"; return ""; } + // TODO: allow matching of aliases? + function cmds(hive) hive._list.filter(function (cmd) cmd.name.indexOf(filter || "") == 0) + + let hives = (hives || this.userHives).map(function (h) [h, cmds(h)]).filter(function ([h, c]) c.length); + + let list = + + + + + + + + + + { + template.map(hives, function ([hive, cmds]) let (i = 0) + + + template.map(cmds, function (cmd) + + + + + + + + + ) + + ) + } +
+ {_("title.Name")}{_("title.Args")}{_("title.Range")}{_("title.Complete")}{_("title.Definition")}
{!i++ ? hive.name : ""}{cmd.bang ? "!" : " "}{cmd.name}{cmd.argCount}{cmd.count ? "0c" : ""}{completerToString(cmd.completer)}{cmd.replacementText || "function () { ... }"}
; - if (!this.userHives.some(function (h) h._list.length)) + if (list.*.length() === list.text().length() + 2) dactyl.echomsg(_("command.none")); else - commandline.commandOutput( - - - - - - - - - - - { - template.map(this.userHives, function (hive) let (i = 0) - + - template.map(hive, function (cmd) - template.map(cmd.names, function (name) - - - - - - - - - )) + - ) - } -
- NameArgsRangeCompleteDefinition
{!i++ ? hive.name : ""}{cmd.bang ? "!" : " "}{cmd.name}{cmd.argCount}{cmd.count ? "0c" : ""}{completerToString(cmd.completer)}{cmd.replacementText || "function () { ... }"}
); + commandline.commandOutput(list); } }), @@ -848,9 +875,9 @@ var Commands = Module("commands", { let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes); if (quote == "\\" && !complete) - return [, , , "Trailing \\"]; + return [, , , _("error.trailingCharacters", "\\")]; if (quote && !complete) - return [, , , "E114: Missing quote: " + quote]; + return [, , , _("error.missingQuote", quote)]; return [count, arg, quote]; } @@ -881,7 +908,7 @@ var Commands = Module("commands", { let matchOpts = function matchOpts(arg) { // Push possible option matches into completions if (complete && !onlyArgumentsRemaining) - completeOpts = options.filter(function (opt) opt.multiple || !set.has(args, opt.names[0])); + completeOpts = options.filter(function (opt) opt.multiple || !Set.has(args, opt.names[0])); }; let resetCompletions = function resetCompletions() { completeOpts = null; @@ -946,7 +973,7 @@ var Commands = Module("commands", { util.assert(!error, error); // if we add the argument to an option after a space, it MUST not be empty - if (sep != "=" && !quote && arg.length == 0) + if (sep != "=" && !quote && arg.length == 0 && !complete) arg = null; count++; // to compensate the "=" character @@ -1336,7 +1363,7 @@ var Commands = Module("commands", { // dynamically get completions as specified with the command's completer function context.highlight(); if (!command) { - context.message = "No such command: " + match.cmd; + context.message = _("command.noSuch", match.cmd); context.highlight(0, match.cmd.length, "SPELLCHECK"); return; } @@ -1354,6 +1381,7 @@ var Commands = Module("commands", { } catch (e) { util.reportError(e); + cmdContext.message = _("error.error", e); } }; @@ -1367,8 +1395,6 @@ var Commands = Module("commands", { commands: function initCommands(dactyl, modules, window) { const { commands, contexts } = modules; - // TODO: Vim allows commands to be defined without {rep} if there are {attr}s - // specified - useful? commands.add(["com[mand]"], "List or define commands", function (args) { @@ -1377,33 +1403,26 @@ var Commands = Module("commands", { util.assert(!cmd || cmd.split(",").every(commands.validName.closure.test), _("command.invalidName", cmd)); - if (!args.literalArg) - commands.list(); + if (args.length <= 1) + commands.list(cmd, args.explicitOpts["-group"] ? [args["-group"]] : null); else { util.assert(args["-group"].modifiable, _("group.cantChangeBuiltin", _("command.commands"))); - let completer = args["-complete"]; + let completer = args["-complete"]; let completerFunc = null; // default to no completion for user commands if (completer) { if (/^custom,/.test(completer)) { completer = completer.substr(7); - let context = update({}, contexts.context || {}); + if (contexts.context) + var ctxt = update({}, contexts.context || {}); completerFunc = function (context) { - try { - var result = contextswithSavedValues(["context"], function () { - contexts.context = context; - return dactyl.userEval(completer); - }); - } - catch (e) { - dactyl.echo(":" + this.name + " ..."); - dactyl.echoerr(_("command.unknownCompleter", completer)); - dactyl.log(e); - return undefined; - } + var result = contexts.withSavedValues(["context"], function () { + contexts.context = ctxt; + return dactyl.userEval(completer); + }); if (callable(result)) return result.apply(this, Array.slice(arguments)); else @@ -1473,7 +1492,7 @@ var Commands = Module("commands", { }, { names: ["-literal", "-l"], - description: "Process the nth ignoring any quoting or meta characters", + description: "Process the specified argument ignoring any quoting or meta characters", type: CommandOption.INT }, { @@ -1558,7 +1577,7 @@ var Commands = Module("commands", { ] })), iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS) - this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || set.has(cmd.helpTag)), + this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || Set.has(tags, cmd.helpTag)), format: { headings: ["Command", "Group", "Description"], description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")), @@ -1576,9 +1595,10 @@ var Commands = Module("commands", { dactyl.clipboardWrite(res); let lines = res.split("\n").length; - dactyl.echomsg("Yanked " + lines + " line" + (lines == 1 ? "" : "s")); + dactyl.echomsg(_("command.yank.yankedLine" + (lines == 1 ? "" : "s"), lines)); }, { + argCount: "1", completer: function (context) modules.completion[/^:/.test(context.filter) ? "ex" : "javascript"](context), literal: 0 }); diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index b7a34e5..9671e95 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -11,7 +11,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("completion", { exports: ["CompletionContext", "Completion", "completion"], - use: ["config", "template", "util"] + use: ["config", "messages", "template", "util"] }, this); /** @@ -331,16 +331,16 @@ var CompletionContext = Class("CompletionContext", { * The message displayed at the head of the completions for the * current context. */ - get message() this._message || (this.waitingForTab && this.hasItems !== false ? "Waiting for " : null), + get message() this._message || (this.waitingForTab && this.hasItems !== false ? _("completion.waitingFor", "") : null), set message(val) this._message = val, /** * The prototype object for items returned by {@link items}. */ get itemPrototype() { - let res = {}; + let res = { highlight: "" }; function result(quote) { - yield ["result", quote ? function () quote[0] + quote[1](this.text) + quote[2] + yield ["result", quote ? function () quote[0] + util.trapErrors(1, quote, this.text) + quote[2] : function () this.text]; }; for (let i in iter(this.keys, result(this.quote))) { @@ -396,7 +396,7 @@ var CompletionContext = Class("CompletionContext", { } catch (e) { util.reportError(e); - this.message = "Error: " + e; + this.message = _("error.error", e); } } // XXX @@ -495,7 +495,7 @@ var CompletionContext = Class("CompletionContext", { return this.cache.filtered = filtered; } catch (e) { - this.message = "Error: " + e; + this.message = _("error.error", e); util.reportError(e); return []; } @@ -903,6 +903,13 @@ var Completion = Module("completion", { // depending on the 'complete' option // if the 'complete' argument is passed like "h", it temporarily overrides the complete option url: function url(context, complete) { + if (/^jar:[^!]*$/.test(context.filter)) { + context.advance(4); + + context.quote = context.quote || ["", util.identity, ""]; + let quote = context.quote[1]; + context.quote[1] = function (str) quote(str.replace(/!/g, escape)); + } if (this.options["urlseparator"]) var skip = util.regexp("^.*" + this.options["urlseparator"] + "\\s*") @@ -953,7 +960,7 @@ var Completion = Module("completion", { context.title = ["URL", "Title"]; context.fork("additional", 0, this, function (context) { - context.title[0] += " (additional)"; + context.title[0] += " " + _("completion.additional"); context.filter = context.parent.filter; // FIXME context.completions = context.parent.completions; // For items whose URL doesn't exactly match the filter, @@ -1037,7 +1044,7 @@ var Completion = Module("completion", { }; options.add(["altwildmode", "awim"], - "Define the behavior of the key in command-line completion", + "Define the behavior of the c_ key in command-line completion", "stringlist", "list:full", wildmode); @@ -1066,7 +1073,7 @@ var Completion = Module("completion", { }); options.add(["wildmode", "wim"], - "Define the behavior of the key in command-line completion", + "Define the behavior of the c_ key in command-line completion", "stringlist", "list:full", wildmode); diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 9fb101a..4da5a88 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("config", { exports: ["ConfigBase", "Config", "config"], require: ["services", "storage", "util", "template"], - use: ["io", "prefs"] + use: ["io", "messages", "prefs", "styles"] }, this); var ConfigBase = Class("ConfigBase", { @@ -22,25 +22,24 @@ var ConfigBase = Class("ConfigBase", { * initialization code. Must call superclass's init function. */ init: function init() { - this.features.push = deprecated("set.add", function push(feature) set.add(this, feature)); + this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature)); if (util.haveGecko("2b")) - set.add(this.features, "Gecko2"); + Set.add(this.features, "Gecko2"); this.timeout(function () { - services["dactyl:"].pages.dtd = function () [null, - iter(config.dtdExtra, - (["dactyl." + k, v] for ([k, v] in iter(config.dtd))), - (["dactyl." + s, config[s]] for each (s in config.dtdStrings))) - .map(function ([k, v]) [""].join("")) - .join("\n")] + services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)]; }); }, - loadStyles: function loadStyles() { + loadStyles: function loadStyles(force) { const { highlight } = require("highlight"); + const { _ } = require("messages"); + highlight.styleableChrome = this.styleableChrome; - highlight.loadCSS(this.CSS); - highlight.loadCSS(this.helpCSS); + + highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1))); + highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1))); + if (!util.haveGecko("2b")) highlight.loadCSS(); + + let hl = highlight.set("Find", ""); + hl.onChange = function () { + function hex(val) ("#" + util.regexp.iterate(/\d+/g, val) + .map(function (num) ("0" + Number(num).toString(16)).slice(-2)) + .join("") + ).slice(0, 7); + + let elem = services.appShell.hiddenDOMWindow.document.createElement("div"); + elem.style.cssText = this.cssText; + let style = util.computedStyle(elem); + + let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray(); + let bg = keys.some(function (k) /^background/.test(k)); + let fg = keys.indexOf("color") >= 0; + + prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor)); + prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color)); + }; }, get addonID() this.name + "@dactyl.googlecode.com", @@ -124,50 +142,59 @@ var ConfigBase = Class("ConfigBase", { * @returns {string} */ bestLocale: function (list) { - let langs = set(list); + let langs = Set(list); return values([this.appLocale, this.appLocale.replace(/-.*/, ""), "en", "en-US", iter(langs).next()]) - .nth(function (l) set.has(langs, l), 0); + .nth(function (l) Set.has(langs, l), 0); }, - haveHg: Class.memoize(function () { + /** + * @property {string} The pathname of the VCS repository clone's root + * directory if the application is running from one via an extension + * proxy file. + */ + VCSPath: Class.memoize(function () { if (/pre$/.test(this.addon.version)) { - let uri = this.addon.getResourceURI("../.hg"); + let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg"); if (uri instanceof Ci.nsIFileURL && - uri.QueryInterface(Ci.nsIFileURL).file.exists() && + uri.file.exists() && io.pathSearch("hg")) - return ["hg", "-R", uri.file.parent.path]; + return uri.file.parent.path; } return null; }), + /** + * @property {string} The name of the VCS branch that the application is + * running from if using an extension proxy file or was built from if + * installed as an XPI. + */ branch: Class.memoize(function () { - if (this.haveHg) - return io.system(this.haveHg.concat(["branch"])).output; + if (this.VCSPath) + return io.system(["hg", "-R", this.VCSPath, "branch"]).output; return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1]; }), /** @property {string} The Dactyl version string. */ version: Class.memoize(function () { - if (/pre$/.test(this.addon.version)) { - let uri = this.addon.getResourceURI("../.hg"); - if (uri instanceof Ci.nsIFileURL && - uri.QueryInterface(Ci.nsIFileURL).file.exists() && - io.pathSearch("hg")) { - return io.system(["hg", "-R", uri.file.parent.path, - "log", "-r.", - "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output; - } - } + if (this.VCSPath) + return io.system(["hg", "-R", this.VCSPath, "log", "-r.", + "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output; let version = this.addon.version; if ("@DATE@" !== "@" + "DATE@") - version += " (created: @DATE@)"; + version += " " + _("dactyl.created", "@DATE@"); return version; }), - get fileExt() this.name.slice(0, -5), + get fileExt() this.name.slice(0, -6), - dtd: memoize({ + dtd: Class.memoize(function () + iter(this.dtdExtra, + (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))), + (["dactyl." + s, config[s]] for each (s in config.dtdStrings))) + .toObject()), + + dtdDactyl: memoize({ get name() config.name, get home() "http://dactyl.sourceforge.net/", get apphome() this.home + this.name, @@ -188,8 +215,9 @@ var ConfigBase = Class("ConfigBase", { "xmlns.html": "http://www.w3.org/1999/xhtml", "xmlns.xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", - "tag.command-line": 'command line', - "tag.status-line": 'status line', + "tag.command-line": command line, + "tag.status-line": status line, + "mode.command-line": Command Line, }, dtdStrings: [ @@ -202,7 +230,7 @@ var ConfigBase = Class("ConfigBase", { "version" ], - helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/, + helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/, styleHelp: function styleHelp() { if (!this.helpStyled) { const { highlight } = require("highlight"); @@ -222,12 +250,12 @@ var ConfigBase = Class("ConfigBase", { ; for each (let [id, [name, key, uri]] in Iterator(this.sidebars)) { append.XUL::menupopup[0].* += - + ; append.XUL::broadcasterset[0].* += + oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>; } util.overlayWindow(window, { append: append.elements() }); @@ -307,10 +335,12 @@ var ConfigBase = Class("ConfigBase", { /** * @property {Object} A map of dialogs available via the * :dialog command. Property names map dialog names to an array - * as follows: + * with the following elements: * [0] description - A description of the dialog, used in * command completion results for :dialog. * [1] action - The function executed by :dialog. + * [2] test - Function which returns true if the dialog is available in + * the current window. @optional */ dialogs: {}, @@ -373,21 +403,25 @@ var ConfigBase = Class("ConfigBase", { */ CSS: UTF8(String.replace(<> - Boolean color: red; - Function color: navy; - Null color: blue; - Number color: blue; - Object color: maroon; - String color: green; white-space: pre; - - Key font-weight: bold; - - Enabled color: blue; - Disabled color: red; - - FontFixed font-family: monospace !important; - FontCode font-size: 9pt; font-family: -mox-fixed, monospace !important; - FontProportional font-size: 10pt; font-family: "Droid Sans", "Helvetica LT Std", Helvetica, "DejaVu Sans", Verdana, sans-serif !important; + Boolean /* JavaScript booleans */ color: red; + Function /* JavaScript functions */ color: navy; + Null /* JavaScript null values */ color: blue; + Number /* JavaScript numbers */ color: blue; + Object /* JavaScript objects */ color: maroon; + String /* String values */ color: green; white-space: pre; + Comment /* JavaScriptor CSS comments */ color: gray; + + Key /* Keywords */ font-weight: bold; + + Enabled /* Enabled item indicator text */ color: blue; + Disabled /* Disabled item indicator text */ color: red; + + FontFixed /* The font used for fixed-width text */ \ + font-family: monospace !important; + FontCode /* The font used for code listings */ \ + font-size: 9pt; font-family: monospace !important; + FontProportional /* The font used for proportionally spaced text */ \ + font-size: 10pt; font-family: "Droid Sans", "Helvetica LT Std", Helvetica, "DejaVu Sans", Verdana, sans-serif !important; // Hack to give these groups slightly higher precedence // than their unadorned variants. @@ -400,84 +434,130 @@ var ConfigBase = Class("ConfigBase", { CmdQuestion;[dactyl|highlight] StatusQuestion;[dactyl|highlight] CmdWarningMsg;[dactyl|highlight] StatusWarningMsg;[dactyl|highlight] - Normal color: black !important; background: white !important; font-weight: normal !important; - StatusNormal color: inherit !important; background: transparent !important; - ErrorMsg color: white !important; background: red !important; font-weight: bold !important; - InfoMsg color: black !important; background: white !important; - StatusInfoMsg color: inherit !important; background: transparent !important; - LineNr color: orange !important; background: white !important; - ModeMsg color: black !important; background: white !important; - StatusModeMsg color: inherit !important; background: transparent !important; padding-right: 1em; - MoreMsg color: green !important; background: white !important; + Normal /* Normal text */ \ + color: black !important; background: white !important; font-weight: normal !important; + StatusNormal /* Normal text in the status line */ \ + color: inherit !important; background: transparent !important; + ErrorMsg /* Error messages */ \ + color: white !important; background: red !important; font-weight: bold !important; + InfoMsg /* Information messages */ \ + color: black !important; background: white !important; + StatusInfoMsg /* Information messages in the status line */ \ + color: inherit !important; background: transparent !important; + LineNr /* The line number of an error */ \ + color: orange !important; background: white !important; + ModeMsg /* The mode indicator */ \ + color: black !important; background: white !important; + StatusModeMsg /* The mode indicator in the status line */ \ + color: inherit !important; background: transparent !important; padding-right: 1em; + MoreMsg /* The indicator that there is more text to view */ \ + color: green !important; background: white !important; StatusMoreMsg background: transparent !important; - Message white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block; - Message String white-space: pre-wrap; - NonText color: blue; background: transparent !important; - *Preview color: gray; - Question color: green !important; background: white !important; font-weight: bold !important; - StatusQuestion color: green !important; background: transparent !important; - WarningMsg color: red !important; background: white !important; - StatusWarningMsg color: red !important; background: transparent !important; - - CmdLine;>*;;FontFixed padding: 1px !important; - CmdPrompt;.dactyl-commandline-prompt + Message /* A message as displayed in :messages */ \ + white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block; + Message String /* A message as displayed in :messages */ \ + white-space: pre-wrap; + NonText /* The ~ indicators which mark blank lines in the completion list */ \ + color: blue; background: transparent !important; + *Preview /* The completion preview displayed in the &tag.command-line; */ \ + color: gray; + Question /* A prompt for a decision */ \ + color: green !important; background: white !important; font-weight: bold !important; + StatusQuestion /* A prompt for a decision in the status line */ \ + color: green !important; background: transparent !important; + WarningMsg /* A warning message */ \ + color: red !important; background: white !important; + StatusWarningMsg /* A warning message in the status line */ \ + color: red !important; background: transparent !important; + Disabled /* Disabled items */ \ + color: gray !important; + + CmdLine;>*;;FontFixed /* The command line */ \ + padding: 1px !important; + CmdPrompt;.dactyl-commandline-prompt /* The default styling form the command prompt */ CmdInput;.dactyl-commandline-command - CmdOutput white-space: pre; - + CmdOutput /* The output of commands executed by :run */ \ + white-space: pre; - CompGroup + CompGroup /* Item group in completion output */ CompGroup:not(:first-of-type) margin-top: .5em; CompGroup:last-of-type padding-bottom: 1.5ex; - CompTitle color: magenta; background: white; font-weight: bold; + CompTitle /* Completion row titles */ \ + color: magenta; background: white; font-weight: bold; CompTitle>* padding: 0 .5ex; - CompTitleSep height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white); + CompTitleSep /* The element which separates the completion title from its results */ \ + height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white); - CompMsg font-style: italic; margin-left: 16px; + CompMsg /* The message which may appear at the top of a group of completion results */ \ + font-style: italic; margin-left: 16px; - CompItem + CompItem /* A single row of output in the completion list */ CompItem:nth-child(2n+1) background: rgba(0, 0, 0, .04); - CompItem[selected] background: yellow; + CompItem[selected] /* A selected row of completion list */ \ + background: yellow; CompItem>* padding: 0 .5ex; - CompIcon width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex; + CompIcon /* The favicon of a completion row */ \ + width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex; CompIcon>img max-width: 16px; max-height: 16px; vertical-align: middle; - CompResult width: 36%; padding-right: 1%; overflow: hidden; - CompDesc color: gray; width: 62%; padding-left: 1em; - - CompLess text-align: center; height: 0; line-height: .5ex; padding-top: 1ex; - CompLess::after content: "⌃"; - - CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex; - CompMore::after content: "⌄"; - - - EditorEditing;;* background: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important; - EditorError;;* background: red !important; - EditorBlink1;;* background: yellow !important; - EditorBlink2;;* - - REPL overflow: auto; max-height: 40em; - REPL-R;;;Question - REPL-E white-space: pre-wrap; - REPL-P white-space: pre-wrap; margin-bottom: 1em; - - Usage width: 100%; - UsageBody - UsageHead - UsageItem + CompResult /* The result column of the completion list */ \ + width: 36%; padding-right: 1%; overflow: hidden; + CompDesc /* The description column of the completion list */ \ + color: gray; width: 62%; padding-left: 1em; + + CompLess /* The indicator shown when completions may be scrolled up */ \ + text-align: center; height: 0; line-height: .5ex; padding-top: 1ex; + CompLess::after /* The character of indicator shown when completions may be scrolled up */ \ + content: "⌃"; + + CompMore /* The indicator shown when completions may be scrolled down */ \ + text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex; + CompMore::after /* The character of indicator shown when completions may be scrolled down */ \ + content: "⌄"; + + Dense /* Arbitrary elements which should be packed densely together */\ + margin-top: 0; margin-bottom: 0; + + EditorEditing;;* /* Text fields for which an external editor is open */ \ + background-color: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important; + EditorError;;* /* Text fields briefly after an error has occurred running the external editor */ \ + background: red !important; + EditorBlink1;;* /* Text fields briefly after successfully running the external editor, alternated with EditorBlink2 */ \ + background: yellow !important; + EditorBlink2;;* /* Text fields briefly after successfully running the external editor, alternated with EditorBlink1 */ + + REPL /* Read-Eval-Print-Loop output */ \ + overflow: auto; max-height: 40em; + REPL-R;;;Question /* Prompts in REPL mode */ + REPL-E /* Evaled input in REPL mode */ \ + white-space: pre-wrap; + REPL-P /* Evaled output in REPL mode */ \ + white-space: pre-wrap; margin-bottom: 1em; + + Usage /* Output from the :*usage commands */ \ + width: 100%; + UsageHead /* Headings in output from the :*usage commands */ + UsageBody /* The body of listings in output from the :*usage commands */ + UsageItem /* Individual items in output from the :*usage commands */ UsageItem:nth-of-type(2n) background: rgba(0, 0, 0, .04); - Indicator color: blue; width: 1.5em; text-align: center; - Filter font-weight: bold; + Indicator /* The # and % in the :buffers list */ \ + color: blue; width: 1.5em; text-align: center; + Filter /* The matching text in a completion list */ \ + font-weight: bold; - Keyword color: red; - Tag color: blue; + Keyword /* A bookmark keyword for a URL */ \ + color: red; + Tag /* A bookmark tag for a URL */ \ + color: blue; - Link position: relative; padding-right: 2em; + Link /* A link with additional information shown on hover */ \ + position: relative; padding-right: 2em; Link:not(:hover)>LinkInfo opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden; LinkInfo { + /* Information shown when hovering over a link */ color: black; position: absolute; left: 100%; @@ -488,6 +568,7 @@ var ConfigBase = Class("ConfigBase", { } StatusLine;;;FontFixed { + /* The status bar */ -moz-appearance: none !important; font-weight: bold; background: transparent !important; @@ -496,16 +577,24 @@ var ConfigBase = Class("ConfigBase", { min-height: 18px !important; text-shadow: none !important; } - StatusLineNormal;[dactyl|highlight] color: white !important; background: black !important; - StatusLineBroken;[dactyl|highlight] color: black !important; background: #FFa0a0 !important; /* light-red */ - StatusLineSecure;[dactyl|highlight] color: black !important; background: #a0a0FF !important; /* light-blue */ - StatusLineExtended;[dactyl|highlight] color: black !important; background: #a0FFa0 !important; /* light-green */ - - TabClose;.tab-close-button - TabIcon;.tab-icon min-width: 16px; - TabText;.tab-text - TabNumber font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default; - TabIconNumber { + StatusLineNormal;[dactyl|highlight] /* The status bar for an ordinary web page */ \ + color: white !important; background: black !important; + StatusLineBroken;[dactyl|highlight] /* The status bar for a broken web page */ \ + color: black !important; background: #FFa0a0 !important; /* light-red */ + StatusLineSecure;[dactyl|highlight] /* The status bar for a secure web page */ \ + color: black !important; background: #a0a0FF !important; /* light-blue */ + StatusLineExtended;[dactyl|highlight] /* The status bar for a secure web page with an Extended Validation (EV) certificate */ \ + color: black !important; background: #a0FFa0 !important; /* light-green */ + + !TabClose;.tab-close-button /* The close button of a browser tab */ \ + /* The close button of a browser tab */ + !TabIcon;.tab-icon,.tab-icon-image /* The icon of a browser tab */ \ + /* The icon of a browser tab */ + !TabText;.tab-text /* The text of a browser tab */ + TabNumber /* The number of a browser tab, next to its icon */ \ + font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default; + TabIconNumber { + /* The number of a browser tab, over its icon */ cursor: default; width: 16px; margin: 0 2px 0 -18px !important; @@ -515,12 +604,16 @@ var ConfigBase = Class("ConfigBase", { text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px; } - Title color: magenta; font-weight: bold; - URL text-decoration: none; color: green; background: inherit; + Title /* The title of a listing, including :pageinfo, :jumps */ \ + color: magenta; font-weight: bold; + URL /* A URL */ \ + text-decoration: none; color: green; background: inherit; URL:hover text-decoration: underline; cursor: pointer; - URLExtra color: gray; + URLExtra /* Extra information about a URL */ \ + color: gray; FrameIndicator;;* { + /* The styling applied to briefly indicate the active frame */ background-color: red; opacity: 0.5; z-index: 999999; @@ -531,9 +624,11 @@ var ConfigBase = Class("ConfigBase", { right: 0; } - Bell background-color: black !important; + Bell /* &dactyl.appName;’s visual bell */ \ + background-color: black !important; Hint;;* { + /* A hint indicator. See :help hints */ font: bold 10px "Droid Sans Mono", monospace !important; margin: -.2ex; padding: 0 0 0 1px; @@ -543,47 +638,61 @@ var ConfigBase = Class("ConfigBase", { } Hint[active];;* background: rgba(255, 253, 208, .8); Hint::after;;* content: attr(text) !important; - HintElem;;* background-color: yellow !important; color: black !important; - HintActive;;* background-color: #88FF00 !important; color: black !important; - HintImage;;* opacity: .5 !important; - - Button display: inline-block; font-weight: bold; cursor: pointer; color: black; text-decoration: none; + HintElem;;* /* The hintable element */ \ + background-color: yellow !important; color: black !important; + HintActive;;* /* The hint element of link which will be followed by */ \ + background-color: #88FF00 !important; color: black !important; + HintImage;;* /* The indicator which floats above hinted images */ \ + opacity: .5 !important; + + Button /* A button widget */ \ + display: inline-block; font-weight: bold; cursor: pointer; color: black; text-decoration: none; Button:hover text-decoration: underline; Button[collapsed] visibility: collapse; width: 0; Button::before content: "["; color: gray; text-decoration: none !important; Button::after content: "]"; color: gray; text-decoration: none !important; Button:not([collapsed]) ~ Button:not([collapsed])::before content: "/["; - Buttons + Buttons /* A group of buttons */ - DownloadCell display: table-cell; padding: 0 1ex; + DownloadCell /* A table cell in the :downloads manager */ \ + display: table-cell; padding: 0 1ex; - Downloads display: table; margin: 0; padding: 0; - DownloadHead;;;CompTitle display: table-row; + Downloads /* The :downloads manager */ \ + display: table; margin: 0; padding: 0; + DownloadHead;;;CompTitle /* A heading in the :downloads manager */ \ + display: table-row; DownloadHead>*;;;DownloadCell - Download display: table-row; + Download /* A download in the :downloads manager */ \ + display: table-row; Download:not([active]) color: gray; + Download:nth-child(2n+1) background: rgba(0, 0, 0, .04); Download>*;;;DownloadCell - DownloadButtons - DownloadPercent - DownloadProgress - DownloadProgressHave - DownloadProgressTotal - DownloadSource - DownloadState - DownloadTime - DownloadTitle + DownloadButtons /* A button group in the :downloads manager */ + DownloadPercent /* The percentage column for a download */ + DownloadProgress /* The progress column for a download */ + DownloadProgressHave /* The completed portion of the progress column */ + DownloadProgressTotal /* The remaining portion of the progress column */ + DownloadSource /* The download source column for a download */ + DownloadState /* The download state column for a download */ + DownloadTime /* The time remaining column for a download */ + DownloadTitle /* The title column for a download */ DownloadTitle>Link>a max-width: 48ex; overflow: hidden; display: inline-block; - AddonCell display: table-cell; padding: 0 1ex; + AddonCell /* A cell in tell :addons manager */ \ + display: table-cell; padding: 0 1ex; - Addons display: table; margin: 0; padding: 0; - AddonHead;;;CompTitle display: table-row; + Addons /* The :addons manager */ \ + display: table; margin: 0; padding: 0; + AddonHead;;;CompTitle /* A heading in the :addons manager */ \ + display: table-row; AddonHead>*;;;AddonCell - Addon display: table-row; + Addon /* An add-on in the :addons manager */ \ + display: table-row; + Addon:nth-child(2n+1) background: rgba(0, 0, 0, .04); Addon>*;;;AddonCell AddonButtons @@ -597,111 +706,154 @@ var ConfigBase = Class("ConfigBase", { helpCSS: UTF8(<> - InlineHelpLink font-size: inherit !important; font-family: inherit !important; + InlineHelpLink /* A help link shown in the command line or multi-line output area */ \ + font-size: inherit !important; font-family: inherit !important; - Help;;;FontProportional line-height: 1.4em; + Help;;;FontProportional /* A help page */ \ + line-height: 1.4em; - HelpInclude margin: 2em 0; + HelpInclude /* A help page included in the consolidated help listing */ \ + margin: 2em 0; - HelpArg;;;FontCode color: #6A97D4; - HelpOptionalArg;;;FontCode color: #6A97D4; + HelpArg;;;FontCode /* A required command argument indicator */ \ + color: #6A97D4; + HelpOptionalArg;;;FontCode /* An optional command argument indicator */ \ + color: #6A97D4; - HelpBody display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px; - HelpBorder;*;dactyl://help/* border-color: silver; border-width: 0px; border-style: solid; - HelpCode;;;FontCode display: block; white-space: pre; margin-left: 2em; - HelpTT;html|tt;dactyl://help/*;FontCode + HelpBody /* The body of a help page */ \ + display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px; + HelpBorder;*;dactyl://help/* /* The styling of bordered elements */ \ + border-color: silver; border-width: 0px; border-style: solid; + HelpCode;;;FontCode /* Code listings */ \ + display: block; white-space: pre; margin-left: 2em; + HelpTT;html|tt;dactyl://help/*;FontCode /* Teletype text */ - HelpDefault;;;FontCode display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top; + HelpDefault;;;FontCode /* The default value of a help item */ \ + display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top; - HelpDescription display: block; clear: right; + HelpDescription /* The description of a help item */ \ + display: block; clear: right; HelpDescription[short] clear: none; - HelpEm;html|em;dactyl://help/* font-weight: bold; font-style: normal; + HelpEm;html|em;dactyl://help/* /* Emphasized text */ \ + font-weight: bold; font-style: normal; - HelpEx;;;FontCode display: inline-block; color: #527BBD; + HelpEx;;;FontCode /* An Ex command */ \ + display: inline-block; color: #527BBD; - HelpExample display: block; margin: 1em 0; - HelpExample::before content: "Example: "; font-weight: bold; + HelpExample /* An example */ \ + display: block; margin: 1em 0; + HelpExample::before content: "__MSG_help.Example__: "; font-weight: bold; - HelpInfo display: block; width: 20em; margin-left: auto; - HelpInfoLabel display: inline-block; width: 6em; color: magenta; font-weight: bold; vertical-align: text-top; - HelpInfoValue display: inline-block; width: 14em; text-decoration: none; vertical-align: text-top; + HelpInfo /* Arbitrary information about a help item */ \ + display: block; width: 20em; margin-left: auto; + HelpInfoLabel /* The label for a HelpInfo item */ \ + display: inline-block; width: 6em; color: magenta; font-weight: bold; vertical-align: text-top; + HelpInfoValue /* The details for a HelpInfo item */ \ + display: inline-block; width: 14em; text-decoration: none; vertical-align: text-top; - HelpItem display: block; margin: 1em 1em 1em 10em; clear: both; + HelpItem /* A help item */ \ + display: block; margin: 1em 1em 1em 10em; clear: both; - HelpKey;;;FontCode color: #102663; - HelpKeyword font-weight: bold; color: navy; + HelpKey;;;FontCode /* A keyboard key specification */ \ + color: #102663; + HelpKeyword /* A keyword */ \ + font-weight: bold; color: navy; - HelpLink;html|a;dactyl://help/* text-decoration: none !important; + HelpLink;html|a;dactyl://help/* /* A hyperlink */ \ + text-decoration: none !important; HelpLink[href]:hover text-decoration: underline !important; HelpLink[href^="mailto:"]::after content: "✉"; padding-left: .2em; HelpLink[rel=external] { + /* A hyperlink to an external resource */ /* Thanks, Wikipedia */ background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAFVBMVEVmmcwzmcyZzP8AZswAZv////////9E6giVAAAAB3RSTlP///////8AGksDRgAAADhJREFUGFcly0ESAEAEA0Ei6/9P3sEcVB8kmrwFyni0bOeyyDpy9JTLEaOhQq7Ongf5FeMhHS/4AVnsAZubxDVmAAAAAElFTkSuQmCC) no-repeat scroll right center; padding-right: 13px; } - - HelpTOC - HelpTOC>ol ol margin-left: -1em; - - HelpOrderedList;ol;dactyl://help/* margin: 1em 0; - HelpOrderedList1;ol[level="1"],ol;dactyl://help/* list-style: outside decimal; display: block; - HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/* list-style: outside upper-alpha; - HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/* list-style: outside lower-roman; - HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/* list-style: outside decimal; - - HelpList;html|ul;dactyl://help/* display: block; list-style-position: outside; margin: 1em 0; - HelpListItem;html|li;dactyl://help/* display: list-item; - - - HelpNote color: red; font-weight: bold; - - HelpOpt;;;FontCode color: #106326; - HelpOptInfo;;;FontCode display: block; margin-bottom: 1ex; padding-left: 4em; - - HelpParagraph;html|p;dactyl://help/* display: block; margin: 1em 0em; + ErrorMsg HelpEx color: inherit; background: inherit; text-decoration: underline; + ErrorMsg HelpKey color: inherit; background: inherit; text-decoration: underline; + ErrorMsg HelpOption color: inherit; background: inherit; text-decoration: underline; + ErrorMsg HelpTopic color: inherit; background: inherit; text-decoration: underline; + + HelpTOC /* The Table of Contents for a help page */ + HelpTOC>ol ol margin-left: -1em; + + HelpOrderedList;ol;dactyl://help/* /* Any ordered list */ \ + margin: 1em 0; + HelpOrderedList1;ol[level="1"],ol;dactyl://help/* /* A first-level ordered list */ \ + list-style: outside decimal; display: block; + HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/* /* A second-level ordered list */ \ + list-style: outside upper-alpha; + HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/* /* A third-level ordered list */ \ + list-style: outside lower-roman; + HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/* /* A fourth-level ordered list */ \ + list-style: outside decimal; + + HelpList;html|ul;dactyl://help/* /* An unordered list */ \ + display: block; list-style-position: outside; margin: 1em 0; + HelpListItem;html|li;dactyl://help/* /* A list item, ordered or unordered */ \ + display: list-item; + + HelpNote /* The indicator for a note */ \ + color: red; font-weight: bold; + + HelpOpt;;;FontCode /* An option name */ \ + color: #106326; + HelpOptInfo;;;FontCode /* Information about the type and default values for an option entry */ \ + display: block; margin-bottom: 1ex; padding-left: 4em; + + HelpParagraph;html|p;dactyl://help/* /* An ordinary paragraph */ \ + display: block; margin: 1em 0em; HelpParagraph:first-child margin-top: 0; HelpParagraph:last-child margin-bottom: 0; - HelpSpec;;;FontCode display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em; + HelpSpec;;;FontCode /* The specification for a help entry */ \ + display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em; - HelpString;;;FontCode color: green; font-weight: normal; + HelpString;;;FontCode /* A quoted string */ \ + color: green; font-weight: normal; HelpString::before content: '"'; HelpString::after content: '"'; HelpString[delim]::before content: attr(delim); HelpString[delim]::after content: attr(delim); - HelpNews position: relative; - HelpNewsOld opacity: .7; - HelpNewsNew font-style: italic; - HelpNewsTag font-style: normal; position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre; + HelpNews /* A news item */ position: relative; + HelpNewsOld /* An old news item */ opacity: .7; + HelpNewsNew /* A new news item */ font-style: italic; + HelpNewsTag /* The version tag for a news item */ \ + font-style: normal; position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre; HelpHead;html|h1,html|h2,html|h3,html|h4;dactyl://help/* { + /* Any help heading */ font-weight: bold; color: #527BBD; clear: both; } HelpHead1;html|h1;dactyl://help/* { + /* A first-level help heading */ margin: 2em 0 1em; padding-bottom: .2ex; border-bottom-width: 1px; font-size: 2em; } HelpHead2;html|h2;dactyl://help/* { + /* A second-level help heading */ margin: 2em 0 1em; padding-bottom: .2ex; border-bottom-width: 1px; font-size: 1.2em; } HelpHead3;html|h3;dactyl://help/* { + /* A third-level help heading */ margin: 1em 0; padding-bottom: .2ex; font-size: 1.1em; } HelpHead4;html|h4;dactyl://help/* { + /* A fourth-level help heading */ } - HelpTab;html|dl;dactyl://help/* { + /* A description table */ display: table; width: 100%; margin: 1em 0; @@ -712,19 +864,28 @@ var ConfigBase = Class("ConfigBase", { } HelpTabColumn;html|column;dactyl://help/* display: table-column; HelpTabColumn:first-child width: 25%; - HelpTabTitle;html|dt;dactyl://help/*;FontCode display: table-cell; padding: .1ex 1ex; font-weight: bold; - HelpTabDescription;html|dd;dactyl://help/* display: table-cell; padding: .3ex 1em; text-indent: -1em; border-width: 0px; + HelpTabTitle;html|dt;dactyl://help/*;FontCode /* The title column of description tables */ \ + display: table-cell; padding: .1ex 1ex; font-weight: bold; + HelpTabDescription;html|dd;dactyl://help/* /* The description column of description tables */ \ + display: table-cell; padding: .3ex 1em; text-indent: -1em; border-width: 0px; HelpTabDescription>*;;dactyl://help/* text-indent: 0; - HelpTabRow;html|dl>html|tr;dactyl://help/* display: table-row; - - HelpTag;;;FontCode display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal; - HelpTags display: block; float: right; clear: right; - HelpTopic;;;FontCode color: #102663; - HelpType;;;FontCode margin-right: 2ex; - - HelpWarning color: red; font-weight: bold; - - HelpXML;;;FontCode color: #C5F779; background-color: #444444; font-family: Terminus, Fixed, monospace; + HelpTabRow;html|dl>html|tr;dactyl://help/* /* Entire rows in description tables */ \ + display: table-row; + + HelpTag;;;FontCode /* A help tag */ \ + display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal; + HelpTags /* A group of help tags */ \ + display: block; float: right; clear: right; + HelpTopic;;;FontCode /* A link to a help topic */ \ + color: #102663; + HelpType;;;FontCode /* An option type */ \ + color: #102663 !important; margin-right: 2ex; + + HelpWarning /* The indicator for a warning */ \ + color: red; font-weight: bold; + + HelpXML;;;FontCode /* Highlighted XML */ \ + color: #C5F779; background-color: #444444; font-family: Terminus, Fixed, monospace; HelpXMLBlock { white-space: pre; color: #C5F779; background-color: #444444; border: 1px dashed #aaaaaa; display: block; diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index 15549e3..f5d976c 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -109,6 +109,9 @@ var Contexts = Module("contexts", { completer: function (context) modules.completion.group(context) }); + + memoize(modules, "userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules, true])); + memoize(modules, "_userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules.userContext])); }, cleanup: function () { @@ -121,7 +124,7 @@ var Contexts = Module("contexts", { util.trapErrors("destroy", hive); for (let [name, plugin] in iter(this.modules.plugins.contexts)) - if (plugin && "onUnload" in plugin) + if (plugin && "onUnload" in plugin && callable(plugin.onUnload)) util.trapErrors("onUnload", plugin); }, @@ -159,7 +162,7 @@ var Contexts = Module("contexts", { { _hive: { value: name } }))); memoize(contexts.groupsProto, name, - function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); + function () [group[name] for (group in values(this.groups)) if (Set.has(group, name))]); }, get toStringParams() [this.name, this.Hive] @@ -177,17 +180,18 @@ var Contexts = Module("contexts", { 0); let contextPath = file.path; - let self = set.has(plugins, contextPath) && plugins.contexts[contextPath]; + let self = Set.has(plugins, contextPath) && plugins.contexts[contextPath]; if (self) { - if (set.has(self, "onUnload")) + if (Set.has(self, "onUnload")) self.onUnload(); } else { let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-") : file.leafName; - self = update(newContext.apply(null, args || [userContext]), { + self = args && !isArray(args) ? args : newContext.apply(null, args || [userContext]); + update(self, { NAME: Const(name.replace(/\.[^.]*$/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())), PATH: Const(file.path), @@ -212,10 +216,12 @@ var Contexts = Module("contexts", { contexts.removeGroup(this.GROUP); }) }); - Class.replaceProperty(plugins, file.path, self); + + if (group !== this.user) + Class.replaceProperty(plugins, file.path, self); // This belongs elsewhere - if (isPlugin && args) + if (isPlugin) Object.defineProperty(plugins, self.NAME, { configurable: true, enumerable: true, @@ -233,7 +239,7 @@ var Contexts = Module("contexts", { group = this.addGroup(commands.nameRegexp .iterate(name.replace(/\.[^.]*$/, "")) .join("-").replace(/--+/g, "-"), - "Script group for " + file.path, + _("context.scriptGroup", file.path), null, false); Class.replaceProperty(self, "GROUP", group); @@ -272,7 +278,7 @@ var Contexts = Module("contexts", { })), matchingGroups: function (uri) Object.create(this.groupsProto, { - groups: { value: this.activeGroups(uri) }, + groups: { value: this.activeGroups(uri) } }), activeGroups: function (uri, doc) { @@ -288,7 +294,7 @@ var Contexts = Module("contexts", { initializedGroups: function (hive) let (need = hive ? [hive] : Object.keys(this.hives)) - this.groupList.filter(function (group) need.some(set.has(group))), + this.groupList.filter(function (group) need.some(Set.has(group))), addGroup: function addGroup(name, description, filter, persist, replace) { let group = this.getGroup(name); @@ -307,7 +313,7 @@ var Contexts = Module("contexts", { if (description) group.description = description; if (filter) - group.filter = filter + group.filter = filter; group.persist = persist; } @@ -344,7 +350,7 @@ var Contexts = Module("contexts", { getGroup: function getGroup(name, hive) { if (name === "default") var group = this.context && this.context.context && this.context.context.GROUP; - else if (set.has(this.groupMap, name)) + else if (Set.has(this.groupMap, name)) group = this.groupMap[name]; if (group && hive) @@ -352,15 +358,35 @@ var Contexts = Module("contexts", { return group; }, + getDocs: function getDocs(context) { + try { + if (isinstance(context, ["Sandbox"])) { + let info = "INFO" in context && Cu.evalInSandbox("this.INFO instanceof XML && INFO.toXMLString()", context); + return info && XML(info); + } + if (typeof context.INFO == "xml") + return context.INFO; + } + catch (e) {} + return null; + }, + bindMacro: function (args, default_, params) { const { dactyl, events, modules } = this.modules; + function Proxy(obj, key) Class.Property({ + configurable: true, + enumerable: true, + get: function Proxy_get() process(obj[key]), + set: function Proxy_set(val) obj[key] = val + }) + let process = util.identity; if (callable(params)) var makeParams = function makeParams(self, args) - iter.toObject([k, process(v)] - for ([k, v] in iter(params.apply(self, args)))); + let (obj = params.apply(self, args)) + iter.toObject([k, Proxy(obj, k)] for (k in properties(obj))); else if (params) makeParams = function makeParams(self, args) iter.toObject([name, process(args[i])] @@ -368,6 +394,7 @@ var Contexts = Module("contexts", { let rhs = args.literalArg; let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_); + switch (type) { case "-builtin": let noremap = true; @@ -378,9 +405,10 @@ var Contexts = Module("contexts", { var action = function action() { events.feedkeys(action.macro(makeParams(this, arguments)), noremap, silent); - } + }; action.macro = util.compileMacro(rhs, true); break; + case "-ex": action = function action() modules.commands .execute(action.macro, makeParams(this, arguments), @@ -388,6 +416,7 @@ var Contexts = Module("contexts", { action.macro = util.compileMacro(rhs, true); action.context = this.context && update({}, this.context); break; + case "-javascript": if (callable(params)) action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })"); @@ -398,6 +427,7 @@ var Contexts = Module("contexts", { action.makeParams = makeParams; break; } + action.toString = function toString() (type === default_ ? "" : type + " ") + rhs; args = null; return action; @@ -466,9 +496,9 @@ var Contexts = Module("contexts", { if (args.has("-locations")) group.filter = filter; if (args.has("-description")) - group.description = args["-description"] + group.description = args["-description"]; if (args.has("-nopersist")) - group.persist = !args["-nopersist"] + group.persist = !args["-nopersist"]; } if (!group.builtin && args.has("-args")) { @@ -482,7 +512,7 @@ var Contexts = Module("contexts", { util.assert(!group.builtin || !["-description", "-locations", "-nopersist"] - .some(set.has(args.explicitOpts)), + .some(Set.has(args.explicitOpts)), _("group.cantModifyBuiltin")); }, { @@ -540,14 +570,23 @@ var Contexts = Module("contexts", { commands.add(["delg[roup]"], "Delete a group", function (args) { - util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0])); - contexts.removeGroup(args[0]); + util.assert(args.bang ^ !!args[0], _("error.argumentOrBang")); + + if (args.bang) + contexts.groupList = contexts.groupList.filter(function (g) g.builtin); + else { + util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0])); + contexts.removeGroup(args[0]); + } }, { - argCount: "1", + argCount: "?", + bang: true, completer: function (context, args) { - modules.completion.group(context); + if (args.bang) + return; context.filters.push(function ({ item }) !item.builtin); + modules.completion.group(context); } }); diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index 403c1e4..faee6e2 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -7,7 +7,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("downloads", { exports: ["Download", "Downloads", "downloads"], - use: ["io", "prefs", "services", "util"] + use: ["io", "messages", "prefs", "services", "util"] }, this); Cu.import("resource://gre/modules/DownloadUtils.jsm", this); @@ -32,30 +32,38 @@ var Download = Class("Download", { - {self.displayName} {self.targetFile.path} - Pause - Remove - Resume - Retry - Cancel - Delete + {_("download.action.Pause")} + {_("download.action.Remove")} + {_("download.action.Resume")} + {_("download.action.Retry")} + {_("download.action.Cancel")} + {_("download.action.Delete")} / + {self.source.spec} , this.list.document, this.nodes); + this.nodes.launch.addEventListener("click", function (event) { + if (event.button == 0) { + event.preventDefault(); + self.command("launch"); + } + }, false); + self.updateStatus(); return self; }, @@ -78,10 +86,13 @@ var Download = Class("Download", { })), command: function command(name) { - util.assert(set.has(this.allowedCommands, name), "Unknown command"); - util.assert(this.allowedCommands[name], "Command not allowed"); + util.assert(Set.has(this.allowedCommands, name), _("download.unknownCommand")); + util.assert(this.allowedCommands[name], _("download.commandNotAllowed")); - services.downloadManager[name + "Download"](this.id); + if (Set.has(this.commands, name)) + this.commands[name].call(this); + else + services.downloadManager[name + "Download"](this.id); }, commands: { @@ -95,7 +106,7 @@ var Download = Class("Download", { function action() { try { if (this.MIMEInfo && this.MIMEInfo.preferredAction == this.MIMEInfo.useHelperApp) - this.MIMEInfo.launchWithFile(file) + this.MIMEInfo.launchWithFile(file); else file.launch(); } @@ -106,7 +117,7 @@ var Download = Class("Download", { let file = io.File(this.targetFile); if (file.isExecutable() && prefs.get("browser.download.manager.alertOnEXEOpen", true)) - this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]/always) ", + this.list.modules.commandline.input(_("download.prompt.launchExecutable") + " ", function (resp) { if (/^a(lways)$/i.test(resp)) { prefs.set("browser.download.manager.alertOnEXEOpen", false); @@ -120,26 +131,48 @@ var Download = Class("Download", { } }, - compare: function compare(other) String.localeCompare(this.displayName, other.displayName), + _compare: { + active: function (a, b) a.alive - b.alive, + complete: function (a, b) a.percentComplete - b.percentComplete, + date: function (a, b) a.startTime - b.startTime, + filename: function (a, b) String.localeCompare(a.targetFile.leafName, b.targetFile.leafName), + size: function (a, b) a.size - b.size, + speed: function (a, b) a.speed - b.speed, + time: function (a, b) a.timeRemaining - b.timeRemaining, + url: function (a, b) String.localeCompare(a.source.spec, b.source.spec) + }, + + compare: function compare(other) values(this.list.sortOrder).map(function (order) { + let val = this._compare[order.substr(1)](this, other); + + return (order[0] == "-") ? -val : val; + }, this).nth(util.identity, 0) || 0, timeRemaining: Infinity, updateProgress: function updateProgress() { let self = this.__proto__; - if (this.amountTransferred === this.size) + if (this.amountTransferred === this.size) { + this.nodes.speed.textContent = ""; this.nodes.time.textContent = ""; - else if (this.speed == 0 || this.size == 0) - this.nodes.time.textContent = "Unknown"; + } else { - let seconds = (this.size - this.amountTransferred) / this.speed; - [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining); - if (this.timeRemaining) - this.nodes.time.textContent = util.formatSeconds(this.timeRemaining); - else - this.nodes.time.textContent = "~1 second"; + this.nodes.speed.textContent = util.formatBytes(this.speed, 1, true) + "/s"; + + if (this.speed == 0 || this.size == 0) + this.nodes.time.textContent = _("download.unknown"); + else { + let seconds = (this.size - this.amountTransferred) / this.speed; + [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining); + if (this.timeRemaining) + this.nodes.time.textContent = util.formatSeconds(this.timeRemaining); + else + this.nodes.time.textContent = _("download.almostDone"); + } } - let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : "Unknown"; + + let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : _("download.unknown"); let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$"); this.nodes.progressHave.textContent = util.formatBytes(this.amountTransferred, 1, true).replace(suffix, ""); @@ -165,7 +198,8 @@ var DownloadList = Class("DownloadList", XPCOM([Ci.nsIDownloadProgressListener, Ci.nsIObserver, Ci.nsISupportsWeakReference]), { - init: function init(modules, filter) { + init: function init(modules, filter, sort) { + this.sortOrder = sort; this.modules = modules; this.filter = filter && filter.toLowerCase(); this.nodes = { @@ -173,6 +207,7 @@ var DownloadList = Class("DownloadList", }; this.downloads = {}; }, + cleanup: function cleanup() { this.observe.unregister(); services.downloadManager.removeListener(this); @@ -182,26 +217,28 @@ var DownloadList = Class("DownloadList", util.xmlToDom( - Title - Status + {_("title.Title")} + {_("title.Status")} - Progress + {_("title.Progress")} - Time remaining - Source + {_("title.Speed")} + {_("title.Time remaining")} + {_("title.Source")}
- + @@ -253,6 +290,17 @@ var DownloadList = Class("DownloadList", } }, + sort: function sort() { + let list = values(this.downloads).sort(function (a, b) a.compare(b)); + + for (let [i, download] in iter(list)) + if (this.nodes.list.childNodes[i + 1] != download.nodes.row) + this.nodes.list.insertBefore(download.nodes.row, + this.nodes.list.childNodes[i + 1]); + }, + + shouldSort: function shouldSort() Array.some(arguments, function (val) this.sortOrder.some(function (v) v.substr(1) == val), this), + update: function update() { for (let node in values(this.nodes)) if (node.update && node.update != update) @@ -277,9 +325,12 @@ var DownloadList = Class("DownloadList", let active = downloads.filter(function (dl) dl.alive).length; if (active) - this.nodes.total.textContent = active + " active"; - else for (let key in values(["total", "percent", "time"])) + this.nodes.total.textContent = _("download.nActive", active); + else for (let key in values(["total", "percent", "speed", "time"])) this.nodes[key].textContent = ""; + + if (this.shouldSort("complete", "size", "speed", "time")) + this.sort(); }, observers: { @@ -305,11 +356,15 @@ var DownloadList = Class("DownloadList", this.nodes.list.scrollIntoView(false); } this.update(); + + if (this.shouldSort("active")) + this.sort(); } catch (e) { util.reportError(e); } }, + onProgressChange: function (webProgress, request, curProgress, maxProgress, curTotalProgress, maxTotalProgress, @@ -328,17 +383,85 @@ var DownloadList = Class("DownloadList", var Downloads = Module("downloads", { }, { }, { - commands: function (dactyl, modules, window) { - const { commands } = modules; + commands: function initCommands(dactyl, modules, window) { + const { commands, CommandOption } = modules; commands.add(["downl[oads]", "dl"], "Display the downloads list", function (args) { - let downloads = DownloadList(modules, args[0]); + let downloads = DownloadList(modules, args[0], args["-sort"]); modules.commandline.echo(downloads); }, { - argCount: "?" + argCount: "?", + options: [ + { + names: ["-sort", "-s"], + description: "Sort order (see 'downloadsort')", + type: CommandOption.LIST, + get default() modules.options["downloadsort"], + completer: function (context, args) modules.options.get("downloadsort").completer(context, { values: args["-sort"] }), + validator: function (value) modules.options.get("downloadsort").validator(value) + } + ] + }); + + commands.add(["dlc[lear]"], + "Clear completed downloads", + function (args) { services.downloadManager.cleanUp(); }); + }, + options: function initOptions(dactyl, modules, window) { + const { options } = modules; + + if (false) + options.add(["downloadcolumns", "dlc"], + "The columns to show in the download manager", + "stringlist", "filename,state,buttons,progress,percent,time,url", + { + values: { + buttons: "Control buttons", + filename: "Target filename", + percent: "Percent complete", + size: "File size", + speed: "Download speed", + state: "The download's state", + time: "Time remaining", + url: "Source URL" + } + }); + + options.add(["downloadsort", "dlsort", "dls"], + ":downloads sort order", + "stringlist", "-active,+filename", + { + values: { + active: "Whether download is active", + complete: "Percent complete", + date: "Date and time the download began", + filename: "Target filename", + size: "File size", + speed: "Download speed", + time: "Time remaining", + url: "Source URL" + }, + + completer: function (context, extra) { + let seen = Set.has(Set(extra.values.map(function (val) val.substr(1)))); + + context.completions = iter(this.values).filter(function ([k, v]) !seen(k)) + .map(function ([k, v]) [["+" + k, [v, " (", _("sort.ascending"), ")"].join("")], + ["-" + k, [v, " (", _("sort.descending"), ")"].join("")]]) + .flatten().array; + }, + + has: function () Array.some(arguments, function (val) this.value.some(function (v) v.substr(1) == val)), + + validator: function (value) { + let seen = {}; + return value.every(function (val) /^[+-]/.test(val) && Set.has(this.values, val.substr(1)) + && !Set.add(seen, val.substr(1)), + this) && value.length; + } }); } }); diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm index 5edd590..e2d24bb 100644 --- a/common/modules/finder.jsm +++ b/common/modules/finder.jsm @@ -7,11 +7,14 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("finder", { exports: ["RangeFind", "RangeFinder", "rangefinder"], + require: ["prefs"], use: ["messages", "services", "util"] }, this); function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b); +try { + /** @instance rangefinder */ var RangeFinder = Module("rangefinder", { Local: function (dactyl, modules, window) ({ @@ -31,6 +34,12 @@ var RangeFinder = Module("rangefinder", { set rangeFind(val) modules.buffer.localStore.rangeFind = val }), + init: function init() { + prefs.safeSet("accessibility.typeaheadfind.autostart", false); + // The above should be sufficient, but: http://dactyl.sf.net/bmo/348187 + prefs.safeSet("accessibility.typeaheadfind", false); + }, + get commandline() this.modules.commandline, get modes() this.modules.modes, get options() this.modules.options, @@ -41,10 +50,13 @@ var RangeFinder = Module("rangefinder", { if (this.rangeFind && equals(this.rangeFind.window.get(), this.window)) this.rangeFind.reset(); - this.find("", mode === this.modes.FIND_BACKWARD); + this.find("", mode == this.modes.FIND_BACKWARD); }, bootstrap: function (str, backward) { + if (arguments.length < 2 && this.rangeFind) + backward = this.rangeFind.reverse; + let highlighted = this.rangeFind && this.rangeFind.highlighted; let selections = this.rangeFind && this.rangeFind.selections; let linksOnly = false; @@ -132,6 +144,10 @@ var RangeFinder = Module("rangefinder", { } }, + onHistory: function () { + this.rangeFind.found = false; + }, + onSubmit: function (command) { if (!this.options["incfind"] || !this.rangeFind || !this.rangeFind.found) { this.clear(); @@ -168,7 +184,7 @@ var RangeFinder = Module("rangefinder", { modes.addMode("FIND", { description: "Find mode, active when typing search input", - bases: [modes.COMMAND_LINE], + bases: [modes.COMMAND_LINE] }); modes.addMode("FIND_FORWARD", { description: "Forward Find mode, active when typing search input", @@ -200,40 +216,41 @@ var RangeFinder = Module("rangefinder", { get onCancel() modules.rangefinder.closure.onCancel, get onChange() modules.rangefinder.closure.onChange, + get onHistory() modules.rangefinder.closure.onHistory, get onSubmit() modules.rangefinder.closure.onSubmit }); }, mappings: function (dactyl, modules, window) { - const { buffer, config, mappings, modes, rangefinder } = modules; + const { Buffer, buffer, config, mappings, modes, rangefinder } = modules; var myModes = config.browserModes.concat([modes.CARET]); mappings.add(myModes, - ["/"], "Find a pattern starting at the current caret position", + ["/", ""], "Find a pattern starting at the current caret position", function () { rangefinder.openPrompt(modes.FIND_FORWARD); }); mappings.add(myModes, - ["?"], "Find a pattern backward of the current caret position", + ["?", ""], "Find a pattern backward of the current caret position", function () { rangefinder.openPrompt(modes.FIND_BACKWARD); }); mappings.add(myModes, - ["n"], "Find next", + ["n", ""], "Find next", function () { rangefinder.findAgain(false); }); mappings.add(myModes, - ["N"], "Find previous", + ["N", ""], "Find previous", function () { rangefinder.findAgain(true); }); - mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["*"], + mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["*", ""], "Find word under cursor", function () { - rangefinder.find(buffer.getCurrentWord(), false); + rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), false); rangefinder.findAgain(); }); - mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["#"], + mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["#", ""], "Find word under cursor backwards", function () { - rangefinder.find(buffer.getCurrentWord(), true); + rangefinder.find(Buffer.currentWord(buffer.focusedFrame, true), true); rangefinder.findAgain(); }); @@ -242,10 +259,6 @@ var RangeFinder = Module("rangefinder", { const { options, rangefinder } = modules; const { prefs } = require("prefs"); - // prefs.safeSet("accessibility.typeaheadfind.autostart", false); - // The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187 - prefs.safeSet("accessibility.typeaheadfind", false); - options.add(["hlfind", "hlf"], "Highlight all /find pattern matches on the current page after submission", "boolean", false, { @@ -267,7 +280,7 @@ var RangeFinder = Module("rangefinder", { }); options.add(["incfind", "if"], - "Find a pattern incrementally as it is typed rather than awaiting ", + "Find a pattern incrementally as it is typed rather than awaiting c_", "boolean", true); } }); @@ -369,7 +382,7 @@ var RangeFind = Class("RangeFind", { let doc = range.startContainer.ownerDocument; let win = doc.defaultView; let ranges = this.ranges.filter(function (r) - r.window === win && RangeFind.contains(r.range, range)); + r.window === win && RangeFind.sameDocument(r.range, range) && RangeFind.contains(r.range, range)); if (this.backward) return ranges[ranges.length - 1]; @@ -390,7 +403,7 @@ var RangeFind = Class("RangeFind", { var node = util.evaluateXPath(RangeFind.selectNodePath, this.lastRange.commonAncestorContainer).snapshotItem(0); if (node) { - node.focus() + node.focus(); // Re-highlight collapsed selection this.selectedRange = this.lastRange; } @@ -480,7 +493,9 @@ var RangeFind = Class("RangeFind", { frames.push(r); } - let range = start.startContainer.ownerDocument.createRange(); + let doc = start.startContainer.ownerDocument; + + let range = doc.createRange(); range.setStart(start.startContainer, start.startOffset); range.setEnd(end.startContainer, end.startOffset); @@ -492,7 +507,7 @@ var RangeFind = Class("RangeFind", { } function rec(win) { let doc = win.document; - let pageRange = RangeFind.nodeRange(doc.body || doc.documentElement.lastChild); + let pageRange = RangeFind[doc.body ? "nodeRange" : "nodeContents"](doc.body || doc.documentElement); backup = backup || pageRange; let pageStart = RangeFind.endpoint(pageRange, true); let pageEnd = RangeFind.endpoint(pageRange, false); @@ -507,6 +522,14 @@ var RangeFind = Class("RangeFind", { } } pushRange(pageStart, pageEnd); + + let anonNodes = doc.getAnonymousNodes(doc.documentElement); + if (anonNodes) { + for (let [, elem] in iter(anonNodes)) { + let range = RangeFind.nodeContents(elem); + pushRange(RangeFind.endpoint(range, true), RangeFind.endpoint(range, false)); + } + } } rec(win); if (frames.length == 0) @@ -724,6 +747,14 @@ var RangeFind = Class("RangeFind", { return false; } }, + nodeContents: function (node) { + let range = node.ownerDocument.createRange(); + try { + range.selectNodeContents(node); + } + catch (e) {} + return range; + }, nodeRange: function (node) { let range = node.ownerDocument.createRange(); try { @@ -732,10 +763,23 @@ var RangeFind = Class("RangeFind", { catch (e) {} return range; }, - sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument, + sameDocument: function (r1, r2) { + if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument)) + return false; + try { + r1.compareBoundaryPoints(r1.START_TO_START, r2); + } + catch (e if e.result == 0x80530004 /* NS_ERROR_DOM_WRONG_DOCUMENT_ERR */) { + return false; + } + return true; + }, selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | ") }); +} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } + endModule(); + // vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm index 3528894..73b6b06 100644 --- a/common/modules/highlight.jsm +++ b/common/modules/highlight.jsm @@ -33,6 +33,8 @@ Highlight.liveProperty = function (name, prop) { h.style.css = h.css; this.style[prop || name] = this[prop || name]; + if (this.onChange) + this.onChange(); }); } Highlight.liveProperty("agent"); @@ -100,7 +102,7 @@ var Highlights = Module("Highlight", { __iterator__: function () values(this.highlight).sort(function (a, b) String.localeCompare(a.class, b.class)) .iterValues(), - _create: function (agent, args) { + _create: function _create(agent, args) { let obj = Highlight.apply(Highlight, args); if (!isArray(obj.sites)) @@ -143,9 +145,9 @@ var Highlights = Module("Highlight", { return obj; }, - get: function (k) this.highlight[k], + get: function get(k) this.highlight[k], - set: function (key, newStyle, force, append, extend) { + set: function set(key, newStyle, force, append, extend) { let [, class_, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/); let highlight = this.highlight[key] || this._create(false, [key]); @@ -181,7 +183,7 @@ var Highlights = Module("Highlight", { * Clears all highlighting rules. Rules with default values are * reset. */ - clear: function () { + clear: function clear() { for (let [k, v] in Iterator(this.highlight)) this.set(k, null, true); }, @@ -193,7 +195,7 @@ var Highlights = Module("Highlight", { * @param {Node} node * @param {string} group */ - highlightNode: function (node, group, applyBindings) { + highlightNode: function highlightNode(node, group, applyBindings) { node.setAttributeNS(NS.uri, "highlight", group); let groups = group.split(" "); @@ -214,7 +216,7 @@ var Highlights = Module("Highlight", { * * @param {string} class */ - selector: function (class_) + selector: function selector(class_) let (self = this) class_.replace(/(^|[>\s])([A-Z][\w-]+)\b/g, function (m, n1, hl) n1 + @@ -275,8 +277,9 @@ var Highlights = Module("Highlight", { * @param {string} css The rules to load. See {@link Highlights#css}. * @param {boolean} eager When true, load all provided rules immediately. */ - loadCSS: function (css, eager) { - String.replace(css, this.groupRegexp, function (m, m1, m2) m1 + " " + m2.replace(/\n\s*/g, " ")) + loadCSS: function loadCSS(css, eager) { + String.replace(css, /\\\n/g, "") + .replace(this.groupRegexp, function (m, m1, m2) m1 + " " + m2.replace(/\n\s*/g, " ")) .split("\n").filter(function (s) /\S/.test(s) && !/^\s*\/\//.test(s)) .forEach(function (highlight) { @@ -291,7 +294,7 @@ var Highlights = Module("Highlight", { } }, { }, { - commands: function (dactyl, modules) { + commands: function initCommands(dactyl, modules) { const { autocommands, commands, completion, CommandOption, config, io } = modules; let lastScheme; @@ -337,7 +340,7 @@ var Highlights = Module("Highlight", { if (!modify && /&$/.test(key)) [clear, modify, key] = [true, true, key.replace(/&$/, "")]; - dactyl.assert(!(clear && css), _("error.trailing")); + dactyl.assert(!(clear && css), _("error.trailingCharacters")); if (!modify) modules.commandline.commandOutput( @@ -347,9 +350,11 @@ var Highlights = Module("Highlight", { ([h.class, XXX, template.map(h.extends, template.highlight), - template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)] - for (h in highlight) - if (!key || h.class.indexOf(key) > -1)))); + template.highlightRegexp(h.value, /\b[-\w]+(?=:)|\/\*.*?\*\//g, + function (match) {match}) + ] + for (h in highlight) + if (!key || h.class.indexOf(key) > -1)))); else if (!key && clear) highlight.clear(); else if (key) @@ -369,7 +374,10 @@ var Highlights = Module("Highlight", { else if (args.completeArg == 1) { let hl = highlight.get(args[0]); if (hl) - context.completions = [[hl.value, "Current Value"], [hl.defaultValue || "", "Default Value"]]; + context.completions = [ + [hl.value, _("option.currentValue")], + [hl.defaultValue || "", _("option.defaultValue")] + ]; context.fork("css", 0, completion, "css"); } }, @@ -385,8 +393,8 @@ var Highlights = Module("Highlight", { let group = args[0] && highlight.get(args[0]); if (group) context.fork("extra", 0, this, function (context) [ - [String(group.extends), "Current Value"], - [String(group.defaultExtends) || "", "Default Value"] + [String(group.extends), _("option.currentValue")], + [String(group.defaultExtends) || "", _("option.defaultValue")] ]); context.fork("groups", 0, completion, "highlightGroup"); } @@ -403,17 +411,22 @@ var Highlights = Module("Highlight", { ] }); }, - completion: function (dactyl, modules) { + completion: function initCompletion(dactyl, modules) { const { completion, config, io } = modules; + completion.colorScheme = function colorScheme(context) { let extRe = RegExp("\\." + config.fileExtension + "$"); context.title = ["Color Scheme", "Runtime Path"]; context.keys = { text: function (f) f.leafName.replace(extRe, ""), description: ".parent.path" }; - context.completions = array.flatten( - io.getRuntimeDirectories("colors").map( - function (dir) dir.readDirectory().filter( - function (file) extRe.test(file.leafName)))); + context.completions = + array.flatten( + io.getRuntimeDirectories("colors").map( + function (dir) dir.readDirectory().filter( + function (file) extRe.test(file.leafName)))) + .concat([ + { leafName: "default", parent: { path: /*L*/"Revert to builtin colorscheme" } } + ]); }; @@ -422,7 +435,7 @@ var Highlights = Module("Highlight", { context.completions = [[v.class, v.value] for (v in highlight)]; }; }, - javascript: function (dactyl, modules, window) { + javascript: function initJavascript(dactyl, modules, window) { modules.JavaScript.setCompleter(["get", "set"].map(function (m) highlight[m]), [ function (context, obj, args) Iterator(highlight.highlight) ]); modules.JavaScript.setCompleter(["highlightNode"].map(function (m) highlight[m]), diff --git a/common/modules/io.jsm b/common/modules/io.jsm index a6487a9..63ac606 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -105,7 +105,7 @@ var IO = Module("io", { * found it is sourced. Only the first file found (per specified path) is * sourced unless *all* is specified, then all found files are sourced. * - * @param {string[]} paths An array of relative paths to source. + * @param {[string]} paths An array of relative paths to source. * @param {boolean} all Whether all found files should be sourced. */ sourceFromRuntimePath: function sourceFromRuntimePath(paths, all) { @@ -116,7 +116,7 @@ var IO = Module("io", { outer: for (let dir in values(dirs)) { - for (let [,path] in Iterator(paths)) { + for (let [, path] in Iterator(paths)) { let file = dir.child(path); dactyl.echomsg(_("io.searchingFor", file.path.quote()), 3); @@ -202,13 +202,13 @@ var IO = Module("io", { this._scriptNames.push(file.path); dactyl.echomsg(_("io.sourcingEnd", filename.quote()), 2); + dactyl.log(_("dactyl.sourced", filename), 3); - dactyl.log("Sourced: " + filename, 3); return context; } catch (e) { dactyl.reportError(e); - let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); + let message = _("io.sourcingError", e.echoerr || (file ? file.path : filename) + ": " + e); if (!params.silent) dactyl.echoerr(message); } @@ -216,7 +216,7 @@ var IO = Module("io", { defineModule.loadLog.push("done sourcing " + filename + ": " + (Date.now() - time) + "ms"); } }, this); - }, + } }), // TODO: there seems to be no way, short of a new component, to change @@ -354,7 +354,7 @@ var IO = Module("io", { */ listJar: function listJar(file, path) { file = util.getFile(file); - if (file) { + if (file && file.exists() && file.isFile() && file.isReadable()) { // let jar = services.zipReader.getZip(file); Crashes. let jar = services.ZipReader(file); try { @@ -366,7 +366,8 @@ var IO = Module("io", { yield entry; } finally { - jar.close(); + if (jar) + jar.close(); } } }, @@ -422,7 +423,7 @@ var IO = Module("io", { * Runs an external program. * * @param {File|string} program The program to run. - * @param {string[]} args An array of arguments to pass to *program*. + * @param {[string]} args An array of arguments to pass to *program*. */ run: function (program, args, blocking) { args = args || []; @@ -569,7 +570,6 @@ var IO = Module("io", { // go directly to an absolute path or look for a relative path // match in 'cdpath' - // TODO: handle ../ and ./ paths if (File.isAbsolutePath(arg)) { io.cwd = arg; dactyl.echomsg(io.cwd.path); @@ -610,7 +610,7 @@ var IO = Module("io", { dactyl.assert(!file.exists() || args.bang, _("io.exists", file.path.quote())); // TODO: Use a set/specifiable list here: - let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)]; + let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)]; lines = array.flatten(lines); lines.unshift('"' + config.version + "\n"); @@ -620,8 +620,8 @@ var IO = Module("io", { file.write(lines.join("\n")); } catch (e) { - dactyl.echoerr(_("io.notWriteable"), file.path.quote()); - dactyl.log("Could not write to " + file.path + ": " + e.message); // XXX + dactyl.echoerr(_("io.notWriteable", file.path.quote())); + dactyl.log(_("error.notWriteable", file.path, e.message)); // XXX } }, { argCount: "*", // FIXME: should be "?" but kludged for proper error message @@ -639,7 +639,7 @@ var IO = Module("io", { if (file.exists() && file.isDirectory() || args[0] && /\/$/.test(args[0])) file.append(config.name + ".vim"); - dactyl.assert(!file.exists() || args.bang, "File exists"); + dactyl.assert(!file.exists() || args.bang, _("io.exists")); let template = util.compileMacro(Css matchgroup=CssDelimiter' syn match Notation "<[0-9A-Za-z-]\+>" -syn match Comment +".*$+ contains=Todo,@Spell syn keyword Todo FIXME NOTE TODO XXX contained syn region String start="\z(["']\)" end="\z1" skip="\\\\\|\\\z1" oneline -syn match LineComment +^\s*".*$+ contains=Todo,@Spell +syn match Comment +^\s*".*$+ contains=Todo,@Spell " NOTE: match vim.vim highlighting group names hi def link AutoCmd Command hi def link AutoEvent Type hi def link Command Statement -hi def link Comment Comment hi def link JavaScriptDelimiter Delimiter hi def link CssDelimiter Delimiter hi def link Notation Special -hi def link LineComment Comment +hi def link Comment Comment hi def link Option PreProc hi def link SetMod Option hi def link String String @@ -782,14 +780,18 @@ unlet s:cpo_save commands.add(["scrip[tnames]"], "List all sourced script names", function () { - modules.commandline.commandOutput( - template.tabular(["", "Filename"], ["text-align: right; padding-right: 1em;"], - ([i + 1, file] for ([i, file] in Iterator(io._scriptNames))))); // TODO: add colon and remove column titles for pedantic Vim compatibility? + if (!io._scriptNames.length) + dactyl.echomsg(_("command.scriptnames.none")); + else + modules.commandline.commandOutput( + template.tabular(["", "Filename"], ["text-align: right; padding-right: 1em;"], + ([i + 1, file] for ([i, file] in Iterator(io._scriptNames))))); + }, { argCount: "0" }); commands.add(["so[urce]"], - "Read Ex commands from a file", + "Read Ex commands, JavaScript or CSS from a file", function (args) { if (args.length > 1) dactyl.echoerr(_("io.oneFileAllowed")); @@ -819,7 +821,7 @@ unlet s:cpo_save if (modules.options["banghist"]) { // replaceable bang and no previous command? dactyl.assert(!/((^|[^\\])(\\\\)*)!/.test(arg) || io._lastRunCommand, - "E34: No previous command"); + _("command.run.noPrevious")); arg = arg.replace(/(\\)*!/g, function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", io._lastRunCommand) @@ -830,14 +832,14 @@ unlet s:cpo_save let result = io.system(arg); if (result.returnValue != 0) - result.output += "\nshell returned " + result.returnValue; + result.output += "\n" + _("io.shellReturn", result.returnValue); - modules.commandline.command = "!" + arg; + modules.commandline.command = args.commandName.replace("run", "$& ") + arg; modules.commandline.commandOutput({result.output}); modules.autocommands.trigger("ShellCmdPost", {}); }, { - argCount: "?", // TODO: "1" - probably not worth supporting weird Vim edge cases. The dream is dead. --djk + argCount: "?", bang: true, // This is abominably slow. // completer: function (context) completion.shellCommand(context), @@ -878,6 +880,9 @@ unlet s:cpo_save }; completion.file = function file(context, full, dir) { + if (/^jar:[^!]*$/.test(context.filter)) + context.advance(4); + // dir == "" is expanded inside readDirectory to the current dir function getDir(str) str.match(/^(?:.*[\/\\])?/)[0]; dir = getDir(dir || context.filter); @@ -900,10 +905,8 @@ unlet s:cpo_save }; context.compare = function (a, b) b.isdir - a.isdir || String.localeCompare(a.text, b.text); - if (modules.options["wildignore"]) { - let wig = modules.options.get("wildignore"); - context.filters.push(function (item) item.isdir || !wig.getKey(this.name)); - } + if (modules.options["wildignore"]) + context.filters.push(function (item) !modules.options.get("wildignore").getKey(item.path)); // context.background = true; context.key = dir; @@ -977,14 +980,17 @@ unlet s:cpo_save context.key = match.prefix; context.advance(match.prefix.length + 1); context.generate = function () iter({ - content: "Chrome content", - locale: "Locale-specific content", - skin: "Theme-specific content" + content: /*L*/"Chrome content", + locale: /*L*/"Locale-specific content", + skin: /*L*/"Theme-specific content" }); } } if (!match || match.scheme === "resource" && match.path) - if (/^(\.{0,2}|~)\/|^file:/.test(context.filter) || util.getFile(context.filter) || io.isJarURL(context.filter)) + if (/^(\.{0,2}|~)\/|^file:/.test(context.filter) + || util.OS.isWindows && /^[a-z]:/i.test(context.filter) + || util.getFile(context.filter) + || io.isJarURL(context.filter)) completion.file(context, full); }); }, @@ -1067,7 +1073,7 @@ unlet s:cpo_save options["shellcmdflag"]; options.add(["wildignore", "wig"], - "List of file patterns to ignore when completing file names", + "List of path name patterns to ignore when completing files and directories", "regexplist", ""); } }); diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm index 0f78615..e85b8ca 100644 --- a/common/modules/javascript.jsm +++ b/common/modules/javascript.jsm @@ -11,7 +11,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("javascript", { exports: ["JavaScript", "javascript"], - use: ["services", "template", "util"] + use: ["messages", "services", "template", "util"] }, this); let isPrototypeOf = Object.prototype.isPrototypeOf; @@ -41,11 +41,11 @@ var JavaScript = Module("javascript", { this.window = window; init.supercall(this); - }, + } }), globals: Class.memoize(function () [ - [this.modules.userContext, "Global Variables"], + [this.modules.userContext, /*L*/"Global Variables"], [this.modules, "modules"], [this.window, "window"] ]), @@ -54,7 +54,7 @@ var JavaScript = Module("javascript", { lazyInit: true, - newContext: function () this.modules.newContext(this.modules.userContext), + newContext: function () this.modules.newContext(this.modules.userContext, true), get completers() JavaScript.completers, // For backward compatibility @@ -71,22 +71,22 @@ var JavaScript = Module("javascript", { if (obj == null) return; - let seen = isinstance(obj, ["Sandbox"]) ? set(JavaScript.magicalNames) : {}; + let seen = isinstance(obj, ["Sandbox"]) ? Set(JavaScript.magicalNames) : {}; let globals = values(toplevel && this.window === obj ? this.globalNames : []); if (toplevel && isObject(obj) && "wrappedJSObject" in obj) - if (!set.add(seen, "wrappedJSObject")) + if (!Set.add(seen, "wrappedJSObject")) yield "wrappedJSObject"; for (let key in iter(globals, properties(obj, !toplevel, true))) - if (!set.add(seen, key)) + if (!Set.add(seen, key)) yield key; // Properties aren't visible in an XPCNativeWrapper until // they're accessed. for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel, true)) try { - if (key in obj && !set.has(seen, key)) + if (key in obj && !Set.has(seen, key)) yield key; } catch (e) {} @@ -117,18 +117,14 @@ var JavaScript = Module("javascript", { return cache[key]; context[JavaScript.EVAL_TMP] = tmp; - context[JavaScript.EVAL_EXPORT] = function export_(obj) cache[key] = obj; try { - if (tmp != null) // Temporary hack until bug 609949 is fixed. - this.modules.dactyl.userEval(JavaScript.EVAL_EXPORT + "(" + arg + ")", context, "[Command Line Completion]", 1); - else - cache[key] = this.modules.dactyl.userEval(arg, context, "[Command Line Completion]", 1); + cache[key] = this.modules.dactyl.userEval(arg, context, /*L*/"[Command Line Completion]", 1); return cache[key]; } catch (e) { util.reportError(e); - this.context.message = "Error: " + e; + this.context.message = _("error.error", e); return null; } finally { @@ -170,7 +166,7 @@ var JavaScript = Module("javascript", { if (this._top.char != arg) { this.context.highlight(this._top.offset, this._i - this._top.offset, "SPELLCHECK"); - throw Error("Invalid JS"); + throw Error(/*L*/"Invalid JS"); } // The closing character of this stack frame will have pushed a new @@ -308,7 +304,7 @@ var JavaScript = Module("javascript", { if (this._checkFunction(prev, dot, cacheKey)) return []; if (prev != statement && obj == null) { - this.context.message = "Error: " + cacheKey.quote() + " is " + String(obj); + this.context.message = /*L*/"Error: " + cacheKey.quote() + " is " + String(obj); return []; } @@ -324,7 +320,7 @@ var JavaScript = Module("javascript", { let end = (frame == -1 ? this._lastIdx : this._get(frame + 1).offset); this._cacheKey = null; - let obj = [[this.cache.evalContext, "Local Variables"]].concat(this.globals); + let obj = [[this.cache.evalContext, /*L*/"Local Variables"]].concat(this.globals); // Is this an object dereference? if (dot < statement) // No. dot = statement - 1; @@ -339,7 +335,7 @@ var JavaScript = Module("javascript", { const self = this; if (!getOwnPropertyNames && !services.debugger.isOn && !this.context.message) - this.context.message = "For better completion data, please enable the JavaScript debugger (:set jsdebugger)"; + this.context.message = /*L*/"For better completion data, please enable the JavaScript debugger (:set jsdebugger)"; let base = this.context.fork("js", this._top.offset); base.forceAnchored = true; @@ -419,14 +415,14 @@ var JavaScript = Module("javascript", { objects.forEach(function (obj) { obj.ctxt_p.split(obj[1] + "/anchored", this, function (context) { context.anchored = true; - context.title[0] += " (prototypes)"; + context.title[0] += /*L*/" (prototypes)"; }); }); objects.forEach(function (obj) { obj.ctxt_t.split(obj[1] + "/unanchored", this, function (context) { context.anchored = false; - context.title[0] += " (substrings)"; + context.title[0] += /*L*/" (substrings)"; context.filters.push(unanchored); }); }); @@ -434,7 +430,7 @@ var JavaScript = Module("javascript", { objects.forEach(function (obj) { obj.ctxt_p.split(obj[1] + "/unanchored", this, function (context) { context.anchored = false; - context.title[0] += " (prototype substrings)"; + context.title[0] += /*L*/" (prototype substrings)"; context.filters.push(unanchored); }); }); @@ -598,7 +594,7 @@ var JavaScript = Module("javascript", { // Wait for a keypress before completing when there's no key if (!this.context.tabPressed && key == "" && obj.length > 1) { this.context.waitingForTab = true; - this.context.message = "Waiting for key press"; + this.context.message = _("completion.waitingForKeyPress"); return null; } @@ -633,12 +629,11 @@ var JavaScript = Module("javascript", { "Math", "NaN", "Namespace", "Number", "Object", "Proxy", "QName", "ROCSSPrimitiveValue", "RangeError", "ReferenceError", "RegExp", "StopIteration", "String", "SyntaxError", "TypeError", "URIError", - "Uint16Array", "Uint32Array", "Uint8Array", "XML", - "XMLHttpProgressEvent", "XMLList", "XMLSerializer", "XPCNativeWrapper", - "XPCSafeJSWrapper", "XULControllers", "constructor", "decodeURI", - "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", - "eval", "isFinite", "isNaN", "isXMLName", "parseFloat", "parseInt", - "undefined", "unescape", "uneval" + "Uint16Array", "Uint32Array", "Uint8Array", "XML", "XMLHttpProgressEvent", + "XMLList", "XMLSerializer", "XPCNativeWrapper", "XPCSafeJSWrapper", + "XULControllers", "constructor", "decodeURI", "decodeURIComponent", + "encodeURI", "encodeURIComponent", "escape", "eval", "isFinite", "isNaN", + "isXMLName", "parseFloat", "parseInt", "undefined", "unescape", "uneval" ].concat([k.substr(6) for (k in keys(Ci)) if (/^nsIDOM/.test(k))]) .concat([k.substr(3) for (k in keys(Ci)) if (/^nsI/.test(k))]) .concat(this.magicalNames) @@ -646,7 +641,6 @@ var JavaScript = Module("javascript", { }, { EVAL_TMP: "__dactyl_eval_tmp", - EVAL_EXPORT: "__dactyl_eval_export", /** * A map of argument completion functions for named methods. The @@ -670,9 +664,9 @@ var JavaScript = Module("javascript", { * time they are accessed, so they should be accessed * judiciously. * - * @param {function|function[]} funcs The functions for which to + * @param {function|[function]} funcs The functions for which to * install the completers. - * @param {function[]} completers An array of completer + * @param {[function]} completers An array of completer * functions. */ setCompleter: function (funcs, completers) { @@ -705,7 +699,8 @@ var JavaScript = Module("javascript", { modes.addMode("REPL", { description: "JavaScript Read Eval Print Loop", - bases: [modes.COMMAND_LINE] + bases: [modes.COMMAND_LINE], + displayName: Class.memoize(function () this.name) }); }, commandline: function initCommandLine(dactyl, modules, window) { @@ -775,8 +770,8 @@ var JavaScript = Module("javascript", { this.js.newContext = function newContext() modules.newContext(self.context, !sandbox); this.js.globals = [ - [this.context, "REPL Variables"], - [context, "REPL Global"] + [this.context, /*L*/"REPL Variables"], + [context, /*L*/"REPL Global"] ].concat(this.js.globals.filter(function ([global]) isPrototypeOf.call(global, context))); if (!isPrototypeOf.call(modules.jsmodules, context)) @@ -790,13 +785,14 @@ var JavaScript = Module("javascript", { this.repl = REPL(this.context); }, + open: function open(context) { - this.updatePrompt(); modules.mow.echo(this.repl); this.widgets.message = null; open.superapply(this, arguments); + this.updatePrompt(); }, complete: function complete(context) { @@ -807,8 +803,10 @@ var JavaScript = Module("javascript", { mode: modes.REPL, + get completionList() this.widgets.statusbar.commandline.id, + accept: function accept() { - dactyl.trapErrors(function () { this.repl.addOutput(this.command) }, this); + dactyl.trapErrors(function () { this.repl.addOutput(this.command); }, this); this.completions.cleanup(); this.history.save(); @@ -836,8 +834,6 @@ var JavaScript = Module("javascript", { commands.add(["javas[cript]", "js"], "Evaluate a JavaScript string", function (args) { - modules.commandline; - if (args[0] && !args.bang) dactyl.userEval(args[0]); else { @@ -846,6 +842,7 @@ var JavaScript = Module("javascript", { .open(); } }, { + argCount: "?", bang: true, completer: function (context) modules.completion.javascript(context), hereDoc: true, diff --git a/common/modules/messages.jsm b/common/modules/messages.jsm index 4b6e478..f1f4de1 100644 --- a/common/modules/messages.jsm +++ b/common/modules/messages.jsm @@ -45,7 +45,7 @@ var Messages = Module("messages", { let seen = {}; for (let { key } in this.iterate()) { - if (!set.add(seen, key)) + if (!Set.add(seen, key)) this._[key] = this[key] = { __noSuchMethod__: function __(prop, args) self._.apply(self, [prop].concat(args)) }; @@ -67,7 +67,7 @@ var Messages = Module("messages", { catch (e) {} // Report error so tests fail, but don't throw - if (arguments.length < 2) + if (arguments.length < 2) // Do *not* localize these strings util.reportError(Error("Invalid locale string: " + value)); return arguments.length > 1 ? default_ : value; }, @@ -80,7 +80,7 @@ var Messages = Module("messages", { catch (e) {} // Report error so tests fail, but don't throw - if (arguments.length < 3) + if (arguments.length < 3) // Do *not* localize these strings util.reportError(Error("Invalid locale string: " + value)); return arguments.length > 2 ? default_ : value; } @@ -88,13 +88,17 @@ var Messages = Module("messages", { }, { Localized: Class("Localized", Class.Property, { init: function init(prop, obj) { - let _prop = "localized_" + prop; + let _prop = "unlocalized_" + prop; if (this.initialized) { /* if (config.locale === "en-US") - return { configurable: true, enumerable: true, value: null, writable: true }; + return { configurable: true, enumerable: true, value: this.default, writable: true }; */ + if (!Set.has(obj, "localizedProperties")) + obj.localizedProperties = { __proto__: obj.localizedProperties }; + obj.localizedProperties[prop] = true; + obj[_prop] = this.default; return { get: function get() { @@ -103,26 +107,29 @@ var Messages = Module("messages", { function getter(key, default_) function getter() messages.get([name, key].join("."), default_); - let name = [this.constructor.className.toLowerCase(), this.identifier || this.name, prop].join("."); - if (!isObject(value)) - value = messages.get(name, value) - else if (isArray(value)) - // Deprecated - iter(value).forEach(function ([k, v]) { - if (isArray(v)) - memoize(v, 1, getter(v[0], v[1])); - else - memoize(value, k, getter(k, v)); - }); - else - iter(value).forEach(function ([k, v]) { - memoize(value, k, function () messages.get([name, k].join("."), v)); - }); + if (value != null) { + var name = [this.constructor.className.toLowerCase(), this.identifier || this.name, prop].join("."); + if (!isObject(value)) + value = messages.get(name, value); + else if (isArray(value)) + // Deprecated + iter(value).forEach(function ([k, v]) { + if (isArray(v)) + memoize(v, 1, getter(v[0], v[1])); + else + memoize(value, k, getter(k, v)); + }); + else + iter(value).forEach(function ([k, v]) { + memoize(value, k, function () messages.get([name, k].join("."), v)); + }); + } return Class.replaceProperty(this, prop, value); }, + set: function set(val) this[_prop] = val - } + }; } this.default = prop; this.initialized = true; diff --git a/common/modules/options.jsm b/common/modules/options.jsm index 1c61a40..a09fef2 100644 --- a/common/modules/options.jsm +++ b/common/modules/options.jsm @@ -12,7 +12,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("options", { exports: ["Option", "Options", "ValueError", "options"], require: ["messages", "storage"], - use: ["commands", "completion", "prefs", "services", "styles", "template", "util"] + use: ["commands", "completion", "config", "prefs", "services", "styles", "template", "util"] }, this); /** @scope modules */ @@ -25,7 +25,7 @@ let ValueError = Class("ValueError", ErrorBase); * A class representing configuration options. Instances are created by the * {@link Options} class. * - * @param {string[]} names The names by which this option is identified. + * @param {[string]} names The names by which this option is identified. * @param {string} description A short one line description of the option. * @param {string} type The option's value data type (see {@link Option#type}). * @param {string} defaultValue The default value for this option. @@ -44,44 +44,21 @@ let ValueError = Class("ValueError", ErrorBase); * @private */ var Option = Class("Option", { - init: function init(names, description, type, defaultValue, extraInfo) { + init: function init(modules, names, description, defaultValue, extraInfo) { + this.modules = modules; this.name = names[0]; this.names = names; this.realNames = names; - this.type = type; this.description = description; - if (this.type in Option.getKey) - this.getKey = Option.getKey[this.type]; - - if (this.type in Option.parse) - this.parse = Option.parse[this.type]; - - if (this.type in Option.stringify) - this.stringify = Option.stringify[this.type]; - - if (this.type in Option.domains) - this.domains = Option.domains[this.type]; - - if (this.type in Option.testValues) - this.testValues = Option.testValues[this.type]; - - this._op = Option.ops[this.type]; - - // Need to trigger setter - if (extraInfo && "values" in extraInfo && !extraInfo.__lookupGetter__("values")) { - this.values = extraInfo.values; - delete extraInfo.values; - } - if (extraInfo) - update(this, extraInfo); + this.update(extraInfo); - if (set.has(this.modules.config.defaults, this.name)) + if (Set.has(this.modules.config.defaults, this.name)) defaultValue = this.modules.config.defaults[this.name]; if (defaultValue !== undefined) { - if (this.type == "string") + if (this.type === "string") defaultValue = Commands.quote(defaultValue); if (isObject(defaultValue)) @@ -101,6 +78,8 @@ var Option = Class("Option", { this.globalValue = this.defaultValue; }, + magicalProperties: Set(["cleanupValue"]), + /** * @property {string} This option's description, as shown in :listoptions. */ @@ -114,6 +93,13 @@ var Option = Class("Option", { get isDefault() this.stringValue === this.stringDefaultValue, + /** @property {value} The value to reset this option to at cleanup time. */ + get cleanupValue() options.cleanupPrefs.get(this.name), + set cleanupValue(value) { + if (options.cleanupPrefs.get(this.name) == null) + options.cleanupPrefs.set(this.name, value); + }, + /** @property {value} The option's global value. @see #scope */ get globalValue() { try { return options.store.get(this.name, {}).value; } catch (e) { util.reportError(e); throw e; } }, set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); }, @@ -123,14 +109,14 @@ var Option = Class("Option", { * "charlist" or "stringlist" or else unchanged. * * @param {value} value The option value. - * @returns {value|string[]} + * @returns {value|[string]} */ parse: function parse(value) Option.dequote(value), /** * Returns *values* packed in the appropriate format for the option type. * - * @param {value|string[]} values The option value. + * @param {value|[string]} values The option value. * @returns {value} */ stringify: function stringify(vals) Commands.quote(vals), @@ -141,7 +127,7 @@ var Option = Class("Option", { * * @param {number} scope The scope to return these values from (see * {@link Option#scope}). - * @returns {value|string[]} + * @returns {value|[string]} */ get: function get(scope) { if (scope) { @@ -213,6 +199,7 @@ var Option = Class("Option", { set stringValue(value) this.value = this.parse(value), get stringDefaultValue() this.stringify(this.defaultValue), + set stringDefaultValue(val) this.defaultValue = this.parse(val), getKey: function getKey(key) undefined, @@ -252,7 +239,7 @@ var Option = Class("Option", { * Sets the option's value using the specified set *operator*. * * @param {string} operator The set operator. - * @param {value|string[]} values The value (or values) to apply. + * @param {value|[string]} values The value (or values) to apply. * @param {number} scope The scope to apply this value to (see * {@link #scope}). * @param {boolean} invert Whether this is an invert boolean operation. @@ -262,7 +249,7 @@ var Option = Class("Option", { try { var newValues = this._op(operator, values, scope, invert); if (newValues == null) - return "Operator " + operator + " not supported for option type " + this.type; + return _("option.operatorNotSupported", operator, this.type); if (!this.isValidValue(newValues)) return this.invalidArgument(str || this.stringify(values), operator); @@ -281,7 +268,7 @@ var Option = Class("Option", { /** @property {string} The option's canonical name. */ name: null, - /** @property {string[]} All names by which this option is identified. */ + /** @property {[string]} All names by which this option is identified. */ names: null, /** @@ -305,13 +292,14 @@ var Option = Class("Option", { */ scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep - cleanupValue: null, - /** * @property {function(CompletionContext, Args)} This option's completer. * @see CompletionContext */ - completer: function completer(context) { + completer: function completer(context, extra) { + if (/map$/.test(this.type) && extra.value == null) + return; + if (this.values) context.completions = this.values; }, @@ -451,12 +439,14 @@ var Option = Class("Option", { let [, bang, filter] = /^(!?)(.*)/.exec(pattern); filter = Option.dequote(filter); + let quote = this.keepQuotes ? util.identity : Option.quote; + return update(Styles.matchFilter(filter), { bang: bang, filter: filter, result: result !== undefined ? result : !bang, - toString: function toString() this.bang + Option.quote(this.filter) + - (typeof this.result === "boolean" ? "" : ":" + Option.quote(this.result)), + toString: function toString() this.bang + Option.quote(this.filter, /:/) + + (typeof this.result === "boolean" ? "" : ":" + quote(this.result)), }); }, @@ -466,7 +456,7 @@ var Option = Class("Option", { regexplist: function regexplist(k, default_) { for (let re in values(this.value)) - if (re(k)) + if ((re.test || re).call(re, k)) return re.result; return arguments.length > 1 ? default_ : null; }, @@ -495,7 +485,7 @@ var Option = Class("Option", { parse: { number: function (value) let (val = Option.dequote(value)) - Option.validIf(Number(val) % 1 == 0, "Integer value required") && parseInt(val), + Option.validIf(Number(val) % 1 == 0, _("option.intRequired")) && parseInt(val), boolean: function boolean(value) Option.dequote(value) == "true" || value == true ? true : false, @@ -512,7 +502,7 @@ var Option = Class("Option", { return []; if (!isArray(value)) value = Option.splitList(value, true); - return value.map(Option.parseSite); + return value.map(Option.parseSite, this); }, stringmap: function stringmap(value) array.toObject( @@ -536,7 +526,7 @@ var Option = Class("Option", { if (v.length > count) return prev = parse.call(this, filter, val); else { - util.assert(prev, "Syntax error", false); + util.assert(prev, _("error.syntaxError"), false); prev.result += "," + v; } }, this)) @@ -592,7 +582,7 @@ var Option = Class("Option", { let value = parseInt(values); util.assert(Number(values) % 1 == 0, - "E521: Number required after =: " + this.name + "=" + values); + _("command.set.numberRequired", this.name, values)); switch (operator) { case "+": @@ -637,18 +627,23 @@ var Option = Class("Option", { stringlist: function stringlist(operator, values, scope, invert) { values = Array.concat(values); + function uniq(ary) { + let seen = {}; + return ary.filter(function (elem) !Set.add(seen, elem)); + } + switch (operator) { case "+": - return array.uniq(Array.concat(this.value, values), true); + return uniq(Array.concat(this.value, values), true); case "^": // NOTE: Vim doesn't prepend if there's a match in the current value - return array.uniq(Array.concat(values, this.value), true); + return uniq(Array.concat(values, this.value), true); case "-": - return this.value.filter(function (item) values.indexOf(item) == -1); + return this.value.filter(function (item) !Set.has(this, item), Set(values)); case "=": if (invert) { - let keepValues = this.value.filter(function (item) values.indexOf(item) == -1); - let addValues = values.filter(function (item) this.value.indexOf(item) == -1, this); + let keepValues = this.value.filter(function (item) !Set.has(this, item), Set(values)); + let addValues = values.filter(function (item) !Set.has(this, item), Set(this.value)); return addValues.concat(keepValues); } return values; @@ -688,7 +683,7 @@ var Option = Class("Option", { * Validates the specified *values* against values generated by the * option's completer function. * - * @param {value|string[]} values The value or array of values to validate. + * @param {value|[string]} values The value or array of values to validate. * @returns {boolean} */ validateCompleter: function validateCompleter(values) { @@ -702,15 +697,54 @@ var Option = Class("Option", { } if (isArray(acceptable)) - acceptable = set(acceptable.map(function ([k]) k)); + acceptable = Set(acceptable.map(function ([k]) k)); if (this.type === "regexpmap" || this.type === "sitemap") - return Array.concat(values).every(function (re) set.has(acceptable, re.result)); + return Array.concat(values).every(function (re) Set.has(acceptable, re.result)); - return Array.concat(values).every(set.has(acceptable)); - } + return Array.concat(values).every(Set.has(acceptable)); + }, + + types: {} }); +["Boolean", + "Charlist", + "Number", + "RegexpList", + "RegexpMap", + "SiteList", + "SiteMap", + "String", + "StringList", + "StringMap"].forEach(function (name) { + let type = name.toLowerCase(); + let class_ = Class(name + "Option", Option, { + type: type, + + _op: Option.ops[type] + }); + + if (type in Option.getKey) + class_.prototype.getKey = Option.getKey[type]; + + if (type in Option.parse) + class_.prototype.parse = Option.parse[type]; + + if (type in Option.stringify) + class_.prototype.stringify = Option.stringify[type]; + + if (type in Option.domains) + class_.prototype.domains = Option.domains[type]; + + if (type in Option.testValues) + class_.prototype.testValues = Option.testValues[type]; + + Option.types[type] = class_; + this[class_.className] = class_; + EXPORTED_SYMBOLS.push(class_.className); +}, this); + /** * @instance options */ @@ -721,7 +755,6 @@ var Options = Module("options", { this.needInit = []; this._options = []; this._optionMap = {}; - this.Option = Class("Option", Option, { modules: modules }); storage.newMap("options", { store: false }); storage.addObserver("options", function optionObserver(key, event, option) { @@ -730,6 +763,18 @@ var Options = Module("options", { if (event == "change" && opt) opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true); }, window); + + services["dactyl:"].pages["options.dtd"] = function () [null, + util.makeDTD( + iter(([["option", o.name, "default"].join("."), + o.type === "string" ? o.defaultValue.replace(/'/g, "''") : + o.value === true ? "on" : + o.value === false ? "off" : o.stringDefaultValue] + for (o in self)), + + ([["option", o.name, "type"].join("."), o.type] for (o in self)), + + config.dtd))]; }, dactyl: dactyl, @@ -742,7 +787,7 @@ var Options = Module("options", { * @param {number} scope Only list options in this scope (see * {@link Option#scope}). */ - list: function (filter, scope) { + list: function list(filter, scope) { if (!scope) scope = Option.SCOPE_BOTH; @@ -780,13 +825,13 @@ var Options = Module("options", { cleanup: function cleanup() { for (let opt in this) if (opt.cleanupValue != null) - opt.value = opt.parse(opt.cleanupValue); + opt.stringValue = opt.cleanupValue; }, /** * Adds a new option. * - * @param {string[]} names All names for the option. + * @param {[string]} names All names for the option. * @param {string} description A description of the option. * @param {string} type The option type (see {@link Option#type}). * @param {value} defaultValue The option's default value. @@ -794,7 +839,7 @@ var Options = Module("options", { * {@link Map#extraInfo}). * @optional */ - add: function (names, description, type, defaultValue, extraInfo) { + add: function add(names, description, type, defaultValue, extraInfo) { const self = this; if (!extraInfo) @@ -810,7 +855,7 @@ var Options = Module("options", { let closure = function () self._optionMap[name]; - memoize(this._optionMap, name, function () self.Option(names, description, type, defaultValue, extraInfo)); + memoize(this._optionMap, name, function () Option.types[type](modules, names, description, defaultValue, extraInfo)); for (let alias in values(names.slice(1))) memoize(this._optionMap, alias, closure); @@ -836,7 +881,7 @@ var Options = Module("options", { allPrefs: deprecated("prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)), getPref: deprecated("prefs.get", function getPref() prefs.get.apply(prefs, arguments)), invertPref: deprecated("prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)), - listPrefs: deprecated("prefs.list", function listPrefs() { commandline.commandOutput(prefs.list.apply(prefs, arguments)); }), + listPrefs: deprecated("prefs.list", function listPrefs() { this.modules.commandline.commandOutput(prefs.list.apply(prefs, arguments)); }), observePref: deprecated("prefs.observe", function observePref() prefs.observe.apply(prefs, arguments)), popContext: deprecated("prefs.popContext", function popContext() prefs.popContext.apply(prefs, arguments)), pushContext: deprecated("prefs.pushContext", function pushContext() prefs.pushContext.apply(prefs, arguments)), @@ -846,6 +891,13 @@ var Options = Module("options", { setPref: deprecated("prefs.set", function setPref() prefs.set.apply(prefs, arguments)), withContext: deprecated("prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)), + cleanupPrefs: Class.memoize(function () localPrefs.Branch("cleanup.option.")), + + cleanup: function cleanup(reason) { + if (~["disable", "uninstall"].indexOf(reason)) + this.cleanupPrefs.resetBranch(); + }, + /** * Returns the option with *name* in the specified *scope*. * @@ -887,8 +939,11 @@ var Options = Module("options", { } if (matches) { - res.option = this.get(res.name, res.scope); - if (!res.option && (res.option = this.get(prefix + res.name, res.scope))) { + if (res.option = this.get(res.name, res.scope)) { + if (prefix === "no" && res.option.type !== "boolean") + res.option = null; + } + else if (res.option = this.get(prefix + res.name, res.scope)) { res.name = prefix + res.name; prefix = ""; } @@ -953,7 +1008,9 @@ var Options = Module("options", { format: { description: function (map) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <> {options.get("passkeys").has(map.name) - ? (passed by {template.helpLink("'passkeys'")}) + ? ({ + tempate.linkifyHelp(_("option.passkeys.passedBy")) + }) : <>} {template.linkifyHelp(map.description)} ) @@ -968,7 +1025,7 @@ var Options = Module("options", { format: { description: function (opt) (XML.ignoreWhitespace = false, XML.prettyPrinting = false, <> {opt.scope == Option.SCOPE_LOCAL - ? (buffer local) : ""} + ? ({_("option.bufferLocal")}) : ""} {template.linkifyHelp(opt.description)} ), help: function (opt) "'" + opt.name + "'" @@ -982,12 +1039,12 @@ var Options = Module("options", { let list = []; function flushList() { - let names = set(list.map(function (opt) opt.option ? opt.option.name : "")); + let names = Set(list.map(function (opt) opt.option ? opt.option.name : "")); if (list.length) if (list.some(function (opt) opt.all)) - options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault) , list[0].scope); + options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault), list[0].scope); else - options.list(function (opt) set.has(names, opt.name), list[0].scope); + options.list(function (opt) Set.has(names, opt.name), list[0].scope); list = []; } @@ -1009,7 +1066,7 @@ var Options = Module("options", { } if (name == "all" && reset) - modules.commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ", + modules.commandline.input(_("pref.prompt.resetAll", config.host) + " ", function (resp) { if (resp == "yes") for (let pref in values(prefs.getNames())) @@ -1017,7 +1074,7 @@ var Options = Module("options", { }, { promptHighlight: "WarningMsg" }); else if (name == "all") - commandline.commandOutput(prefs.list(onlyNonDefault, "")); + modules.commandline.commandOutput(prefs.list(onlyNonDefault, "")); else if (reset) prefs.reset(name); else if (invertBoolean) @@ -1044,10 +1101,11 @@ var Options = Module("options", { } let opt = modules.options.parseOpt(arg, modifiers); - util.assert(opt, "Error parsing :set command: " + arg); + util.assert(opt, _("command.set.errorParsing", arg)); + util.assert(!opt.error, _("command.set.errorParsing", opt.error)); let option = opt.option; - util.assert(option != null || opt.all, "E518: Unknown option: " + opt.name); + util.assert(option != null || opt.all, _("command.set.unknownOption", opt.name)); // reset a variable to its default value if (opt.reset) { @@ -1099,8 +1157,8 @@ var Options = Module("options", { context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100))); context.completions = [ - [prefs.get(filter), "Current Value"], - [prefs.defaults.get(filter), "Default Value"] + [prefs.get(filter), _("option.currentValue")], + [prefs.defaults.get(filter), _("option.defaultValue")] ].filter(function (k) k[0] != null); return null; } @@ -1115,7 +1173,7 @@ var Options = Module("options", { if (context.filter.indexOf("=") == -1) { if (false && prefix) context.filters.push(function ({ item }) item.type == "boolean" || prefix == "inv" && isArray(item.values)); - return completion.option(context, opt.scope, prefix); + return completion.option(context, opt.scope, opt.name == "inv" ? opt.name : prefix); } function error(length, message) { @@ -1125,11 +1183,11 @@ var Options = Module("options", { let option = opt.option; if (!option) - return error(opt.name.length, "No such option: " + opt.name); + return error(opt.name.length, _("option.noSuch", opt.name)); context.advance(context.filter.indexOf("=")); if (option.type == "boolean") - return error(context.filter.length, "Trailing characters"); + return error(context.filter.length, _("error.trailingCharacters")); context.advance(1); if (opt.error) @@ -1143,8 +1201,8 @@ var Options = Module("options", { context.title = ["Extra Completions"]; context.pushProcessor(0, function (item, text, next) next(item, text.substr(0, 100))); context.completions = [ - [option.stringValue, "Current value"], - [option.stringDefaultValue, "Default value"] + [option.stringValue, _("option.currentValue")], + [option.stringDefaultValue, _("option.defaultValue")] ].filter(function (f) f[0] !== ""); context.quote = ["", util.identity, ""]; }); @@ -1155,12 +1213,12 @@ var Options = Module("options", { // Fill in the current values if we're removing if (opt.operator == "-" && isArray(opt.values)) { - let have = set([i.text for (i in values(context.allItems.items))]); + let have = Set([i.text for (i in values(context.allItems.items))]); context = context.fork("current-values", 0); context.anchored = optcontext.anchored; context.maxItems = optcontext.maxItems; - context.filters.push(function (i) !set.has(have, i.text)); + context.filters.push(function (i) !Set.has(have, i.text)); modules.completion.optionValue(context, opt.name, opt.operator, null, function (context) { context.generate = function () option.value.map(function (o) [o, ""]); @@ -1195,7 +1253,7 @@ var Options = Module("options", { if (str.text().length() == str.*.length()) dactyl.echomsg(_("variable.none")); else - dactyl.echo(str, commandline.FORCE_MULTILINE); + dactyl.echo(str, modules.commandline.FORCE_MULTILINE); return; } @@ -1206,7 +1264,7 @@ var Options = Module("options", { util.assert(scope == "g:" || scope == null, _("command.let.illegalVar", scope + name)); - util.assert(set.has(globalVariables, name) || (expr && !op), + util.assert(Set.has(globalVariables, name) || (expr && !op), _("command.let.undefinedVar", fullName)); if (!expr) @@ -1304,7 +1362,7 @@ var Options = Module("options", { function (args) { for (let [, name] in args) { name = name.replace(/^g:/, ""); // throw away the scope prefix - if (!set.has(dactyl._globalVariables, name)) { + if (!Set.has(dactyl._globalVariables, name)) { if (!args.bang) dactyl.echoerr(_("command.let.noSuch", name)); return; @@ -1346,7 +1404,7 @@ var Options = Module("options", { var newValues = opt.parse(context.filter); } catch (e) { - context.message = "Error: " + e; + context.message = _("error.error", e); context.completions = []; return; } @@ -1394,6 +1452,14 @@ var Options = Module("options", { context.filters.push(function (i) curValues.indexOf(i.text) == -1); if (op == "-") context.filters.push(function (i) curValues.indexOf(i.text) > -1); + + memoize(extra, "values", function () { + if (op == "+") + return curValues.concat(newValues); + if (op == "-") + return curValues.filter(function (v) newValues.indexOf(val) == -1); + return newValues; + }); } let res = completer.call(opt, context, extra); diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index ea626b4..08c2829 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -9,7 +9,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("overlay", { exports: ["ModuleBase"], - require: ["config", "services", "util"] + require: ["config", "io", "services", "util"] }, this); /** @@ -206,8 +206,8 @@ var Overlay = Module("Overlay", { const start = Date.now(); const deferredInit = { load: {} }; - const seen = set(); - const loaded = set(); + const seen = Set(); + const loaded = Set(); modules.loaded = loaded; function load(module, prereq, frame) { @@ -222,7 +222,7 @@ var Overlay = Module("Overlay", { return; if (module.className in seen) throw Error("Module dependency loop."); - set.add(seen, module.className); + Set.add(seen, module.className); for (let dep in values(module.requires)) load(Module.constructors[dep], module.className); @@ -275,9 +275,9 @@ var Overlay = Module("Overlay", { }); } defineModule.modules.forEach(function defModule(mod) { - let names = set(Object.keys(mod.INIT)); + let names = Set(Object.keys(mod.INIT)); if ("init" in mod.INIT) - set.add(names, "init"); + Set.add(names, "init"); keys(names).forEach(function (name) { deferInit(name, mod.INIT, mod); }); }); diff --git a/common/modules/prefs.jsm b/common/modules/prefs.jsm index 981a6be..1350068 100644 --- a/common/modules/prefs.jsm +++ b/common/modules/prefs.jsm @@ -16,36 +16,71 @@ defineModule("prefs", { }, this); var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), { - SAVED: "extensions.dactyl.saved.", + ORIGINAL: "extensions.dactyl.original.", RESTORE: "extensions.dactyl.restore.", + SAVED: "extensions.dactyl.saved.", INIT: {}, - init: function (branch, defaults) { + init: function init(branch, defaults) { this._prefContexts = []; this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || ""); - if (this.branch instanceof Ci.nsIPrefBranch2) - this.branch.QueryInterface(Ci.nsIPrefBranch2); + this.branch instanceof Ci.nsIPrefBranch2; this.defaults = defaults ? this : this.constructor(branch, true); + this.branches = memoize({ + __proto__: this, + get original() this.constructor(this.ORIGINAL + this.root), + get restore() this.constructor(this.RESTORE + this.root), + get saved() this.constructor(this.SAVED + this.root), + }); + if (!defaults) this.restore(); this._observers = {}; }, - cleanup: function cleanup() { + cleanup: function cleanup(reason) { if (this.defaults != this) this.defaults.cleanup(); + this._observers = {}; if (this.observe) { this.branch.removeObserver("", this); this.observe.unregister(); delete this.observe; } + + if (this == prefs) { + if (~["uninstall", "disable"].indexOf(reason)) { + for (let name in values(this.branches.saved.getNames())) + this.safeReset(name, null, true); + + this.branches.original.resetBranch(); + this.branches.saved.resetBranch(); + } + + if (reason == "uninstall" && this == prefs) + localPrefs.resetBranch(); + } }, + /** + * Returns the full name of this object's preference branch. + */ + get root() this.branch.root, + + /** + * Returns a new Prefs instance for the sub-branch *branch* of this + * branch. + * + * @param {string} branch The branch to branch to. + * @returns {Prefs} + */ + Branch: function Branch(branch) Prefs(this.root + branch), + observe: null, observers: { "nsPref:changed": function (subject, data) { @@ -69,7 +104,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {function(object)} callback The callback, called with the * new value of the preference whenever it changes. */ - watch: function (pref, callback, strong) { + watch: function watch(pref, callback, strong) { if (!this.observe) { util.addObserver(this); this.branch.addObserver("", this, false); @@ -116,7 +151,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) } }; - return template.options(config.host + " Preferences", prefs.call(this)); + return template.options(_("pref.hostPreferences", config.host), prefs.call(this)); }, /** @@ -161,17 +196,22 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} branch The branch in which to search preferences. * @default "" */ - getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }), + getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }), - _checkSafe: function (name, message, value) { + _checkSafe: function _checkSafe(name, message, value) { let curval = this.get(name, null); + + if (this.branches.original.get(name) == null) + this.branches.original.set(name, curval, true); + if (arguments.length > 2 && curval === value) return; + let defval = this.defaults.get(name, null); - let saved = this.get(this.SAVED + name); + let saved = this.branches.saved.get(name); if (saved == null && curval != defval || saved != null && curval != saved) { - let msg = "Warning: setting preference " + name + ", but it's changed from its default value."; + let msg = _("pref.safeSet.warnChanged", name); if (message) msg = template.linkifyHelp(msg + " " + message); util.dactyl.warn(msg); @@ -184,11 +224,13 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference name. * @param {value} value The new preference value. + * @param {boolean} silent Ignore errors. */ - safeReset: function (name, message) { + safeReset: function safeReset(name, message, silent) { this._checkSafe(name, message); - this.reset(name); - this.reset(this.SAVED + name); + this.set(name, this.branches.original.get(name), silent); + this.branches.original.reset(name); + this.branches.saved.reset(name); }, /** @@ -198,10 +240,10 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} name The preference name. * @param {value} value The new preference value. */ - safeSet: function (name, value, message, skipSave) { + safeSet: function safeSet(name, value, message, skipSave) { this._checkSafe(name, message, value); this.set(name, value); - this[skipSave ? "reset" : "set"](this.SAVED + name, value); + this.branches.saved[skipSave ? "reset" : "set"](name, value); }, /** @@ -209,40 +251,45 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference name. * @param {value} value The new preference value. + * @param {boolean} silent Ignore errors. */ - set: function (name, value) { - if (this._prefContexts.length) { - let val = this.get(name, null); - if (val != null) - this._prefContexts[this._prefContexts.length - 1][name] = val; - } + set: function set(name, value, silent) { + if (this._prefContexts.length) + this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null); function assertType(needType) util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType, type === Ci.nsIPrefBranch.PREF_INT - ? "E521: Number required after =: " + name + "=" + value - : "E474: Invalid argument: " + name + "=" + value); + ? /*L*/"E521: Number required after =: " + name + "=" + value + : /*L*/"E474: Invalid argument: " + name + "=" + value); let type = this.branch.getPrefType(name); - switch (typeof value) { - case "string": - assertType(Ci.nsIPrefBranch.PREF_STRING); - - this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value)); - break; - case "number": - assertType(Ci.nsIPrefBranch.PREF_INT); - - this.branch.setIntPref(name, value); - break; - case "boolean": - assertType(Ci.nsIPrefBranch.PREF_BOOL); - - this.branch.setBoolPref(name, value); - break; - default: - throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); + try { + switch (typeof value) { + case "string": + assertType(Ci.nsIPrefBranch.PREF_STRING); + + this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value)); + break; + case "number": + assertType(Ci.nsIPrefBranch.PREF_INT); + + this.branch.setIntPref(name, value); + break; + case "boolean": + assertType(Ci.nsIPrefBranch.PREF_BOOL); + + this.branch.setBoolPref(name, value); + break; + default: + if (value == null && this != this.defaults) + this.reset(name); + else + throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); + } } + catch (e if silent) {} + return value; }, /** @@ -251,7 +298,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference to save. */ - save: function (name) { + save: function save(name) { let val = this.get(name); this.set(this.RESTORE + name, val); this.safeSet(name, val); @@ -263,7 +310,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} branch The branch from which to restore * preferences. @optional */ - restore: function (branch) { + restore: function restore(branch) { this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) { this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true); this.reset(pref); @@ -275,21 +322,30 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference name. */ - reset: function (name) { + reset: function reset(name) { try { this.branch.clearUserPref(name); } catch (e) {} // ignore - thrown if not a user set value }, + /** + * Resets the preference branch *branch* to its default value. + * + * @param {string} branch The preference name. @optional + */ + resetBranch: function resetBranch(branch) { + this.getNames(branch).forEach(this.closure.reset); + }, + /** * Toggles the value of the boolean preference *name*. * * @param {string} name The preference name. */ - toggle: function (name) { + toggle: function toggle(name) { util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL, - _("error.trailing", name + "!")); + _("error.trailingCharacters", name + "!")); this.set(name, !this.get(name)); }, @@ -298,7 +354,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @see #withContext */ - pushContext: function () { + pushContext: function pushContext() { this._prefContexts.push({}); }, @@ -307,7 +363,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @see #withContext */ - popContext: function () { + popContext: function popContext() { for (let [k, v] in Iterator(this._prefContexts.pop())) this.set(k, v); }, @@ -322,7 +378,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @see #pushContext * @see #popContext */ - withContext: function (func, self) { + withContext: function withContext(func, self) { try { this.pushContext(); return func.call(self); @@ -333,7 +389,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) } }, { }, { - completion: function (dactyl, modules) { + completion: function init_completion(dactyl, modules) { modules.completion.preference = function preference(context) { context.anchored = false; context.title = [config.host + " Preference", "Value"]; @@ -341,7 +397,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) context.completions = prefs.getNames(); }; }, - javascript: function (dactyl, modules) { + javascript: function init_javascript(dactyl, modules) { modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle], [function (context) (context.anchored=false, this.getNames().map(function (pref) [pref, ""]))]); } diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index c7f3ac9..e240424 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -18,7 +18,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("sanitizer", { exports: ["Range", "Sanitizer", "sanitizer"], use: ["config"], - require: ["messages", "prefs", "services", "storage", "template", "util"], + require: ["messages", "prefs", "services", "storage", "template", "util"] }, this); let tmp = {}; @@ -33,7 +33,7 @@ update(Range.prototype, { get isEternity() this.max == null && this.min == null, get isSession() this.max == null && this.min == sanitizer.sessionStart, - get native() this.isEternity ? null : [range.min || 0, range.max == null ? Number.MAX_VALUE : range.max] + get native() this.isEternity ? null : [this.min || 0, this.max == null ? Number.MAX_VALUE : this.max] }); var Item = Class("SanitizeItem", { @@ -69,10 +69,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef util.addObserver(this); - services.add("contentprefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService); + services.add("contentPrefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService); services.add("cookies", "@mozilla.org/cookiemanager;1", [Ci.nsICookieManager, Ci.nsICookieManager2, Ci.nsICookieService]); - services.add("loginmanager", "@mozilla.org/login-manager;1", Ci.nsILoginManager); + services.add("loginManager", "@mozilla.org/login-manager;1", Ci.nsILoginManager); services.add("permissions", "@mozilla.org/permissionmanager;1", Ci.nsIPermissionManager); this.itemMap = {}; @@ -107,11 +107,13 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef action: function (range, host) { if (host) services.history.removePagesFromHost(host, true); - else - services.history.removeVisitsByTimeframe(this.range.min, this.range.max); - - if (!host) + else { + if (range.isEternity) + services.history.removeAllPages(); + else + services.history.removeVisitsByTimeframe(range.native[0], Math.min(Date.now() * 1000, range.native[1])); // XXX services.observer.notifyObservers(null, "browser:purge-session-history", ""); + } if (!host || util.isDomainURL(prefs.get("general.open_location.last_url"), host)) prefs.reset("general.open_location.last_url"); @@ -138,20 +140,20 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef services.permissions.remove(util.createURI(p.host), p.type); services.permissions.add(util.createURI(p.host), p.type, 0); } - for (let p in iter(services.contentprefs.getPrefs(util.createURI(host)))) - services.contentprefs.removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name); + for (let p in iter(services.contentPrefs.getPrefs(util.createURI(host)))) + services.contentPrefs.removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name); } else { // "Allow this site to open popups" ... services.permissions.removeAll(); // Zoom level, ... - services.contentprefs.removeGroupedPrefs(); + services.contentPrefs.removeGroupedPrefs(); } // "Never remember passwords" ... - for each (let domain in services.loginmanager.getAllDisabledHosts()) + for each (let domain in services.loginManager.getAllDisabledHosts()) if (!host || util.isSubdomain(domain, host)) - services.loginmanager.setLoginSavingEnabled(host, true); + services.loginManager.setLoginSavingEnabled(host, true); }, override: true }); @@ -183,7 +185,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef append: { SanitizeDialogPane: - - - + + + @@ -365,9 +370,13 @@ var Styles = Module("Styles", { for (let prop in Styles.propertyIter(str)) props[prop.name] = prop.value; - return Object.keys(props)[sort ? "sort" : "slice"]() - .map(function (prop) prop + ": " + props[prop] + ";") - .join(" "); + let val = Object.keys(props)[sort ? "sort" : "slice"]() + .map(function (prop) prop + ": " + props[prop] + ";") + .join(" "); + + if (/^\s*(\/\*.*?\*\/)/.exec(src)) + val = RegExp.$1 + " " + val; + return val; }, completeSite: function (context, content, group) { @@ -377,8 +386,8 @@ var Styles = Module("Styles", { context.fork("current", 0, this, function (context) { context.title = ["Current Site"]; context.completions = [ - [content.location.host, "Current Host"], - [content.location.href, "Current URL"] + [content.location.host, /*L*/"Current Host"], + [content.location.href, /*L*/"Current URL"] ]; }); } @@ -389,7 +398,7 @@ var Styles = Module("Styles", { context.generate = function () values(group.sites); context.keys.text = util.identity; - context.keys.description = function (site) this.sheets.length + " sheet" + (this.sheets.length == 1 ? "" : "s") + ": " + + context.keys.description = function (site) this.sheets.length + /*L*/" sheet" + (this.sheets.length == 1 ? "" : "s") + ": " + array.compact(this.sheets.map(function (s) s.name)).join(", "); context.keys.sheets = function (site) group.sheets.filter(function (s) s.sites.indexOf(site) >= 0); context.keys.active = function (site) uris.some(Styles.matchFilter(site)); @@ -434,7 +443,7 @@ var Styles = Module("Styles", { for (let item in Iterator({ Active: true, Inactive: false })) { let [name, active] = item; context.split(name, null, function (context) { - context.title[0] = name + " " + (title || "Sheets"); + context.title[0] = /*L*/name + " " + (title || "Sheets"); context.filters.push(function (item) !!item.active == active); }); } @@ -553,17 +562,16 @@ var Styles = Module("Styles", { let [filter, css] = args; if (!css) - styles.list(window.content, filter, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null); + styles.list(window.content, filter ? filter.split(",") : null, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null); else { util.assert(args["-group"].modifiable && args["-group"].hive.modifiable, - "Cannot modify styles in the builtin group"); + _("group.cantChangeBuiltin", _("style.styles"))); if (args["-append"]) { let sheet = args["-group"].get(args["-name"]); if (sheet) { - filter = sheet.sites.concat(filter).join(","); + filter = array(sheet.sites).concat(filter).uniq().join(","); css = sheet.css + " " + css; - } } let style = args["-group"].add(args["-name"], filter, css, args["-agent"]); @@ -573,7 +581,6 @@ var Styles = Module("Styles", { } }, { - bang: true, completer: function (context, args) { let compl = []; let sheet = args["-group"].get(args["-name"]); @@ -584,7 +591,9 @@ var Styles = Module("Styles", { } else if (args.completeArg == 1) { if (sheet) - context.completions = [[sheet.css, "Current Value"]]; + context.completions = [ + [sheet.css, _("option.currentValue")] + ]; context.fork("css", 0, modules.completion, "css"); } }, @@ -607,9 +616,8 @@ var Styles = Module("Styles", { command: "style", arguments: [style.sites.join(",")], literalArg: style.css, - options: update({ - "-group": hive.name, - }, + options: update( + hive.name == "user" ? {} : { "-group": hive.name }, style.name ? { "-name": style.name } : {}) }))) .flatten().array @@ -642,7 +650,7 @@ var Styles = Module("Styles", { commands.add(cmd.name, cmd.desc, function (args) { dactyl.assert(args.bang ^ !!(args[0] || args[1] || args["-name"] || args["-index"]), - "Argument or ! required"); + _("error.argumentOrBang")); args["-group"].find(args["-name"], args[0], args.literalArg, args["-index"]) .forEach(cmd.action); diff --git a/common/modules/template.jsm b/common/modules/template.jsm index 8a398c2..6e18dc5 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -119,7 +119,7 @@ var Template = Module("Template", { "click": function onClick(event) { event.preventDefault(); if (this.commandAllowed) { - if (set.has(this.target.commands || {}, this.command)) + if (Set.has(this.target.commands || {}, this.command)) this.target.commands[this.command].call(this.target); else this.target.command(this.command); @@ -128,7 +128,7 @@ var Template = Module("Template", { }, get commandAllowed() { - if (set.has(this.target.allowedCommands || {}, this.command)) + if (Set.has(this.target.allowedCommands || {}, this.command)) return this.target.allowedCommands[this.command]; if ("commandAllowed" in this.target) return this.target.commandAllowed(this.command); @@ -197,7 +197,7 @@ var Template = Module("Template", { - from pushing the baseline down and enlarging - the row. --> -
  • {text} 
  • +
  • {text} 
  • {desc} 
  • ; // @@ -213,7 +213,7 @@ var Template = Module("Template", { else if (/^n_/.test(topic)) topic = topic.slice(2); - if (services["dactyl:"].initialized && !set.has(services["dactyl:"].HELP_TAGS, topic)) + if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) return {text || token}; XML.ignoreWhitespace = false; XML.prettyPrinting = false; @@ -233,7 +233,7 @@ var Template = Module("Template", { else if (/^n_/.test(topic)) topic = topic.slice(2); - if (services["dactyl:"].initialized && !set.has(services["dactyl:"].HELP_TAGS, topic)) + if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) return <>{token}; XML.ignoreWhitespace = false; XML.prettyPrinting = false; @@ -247,7 +247,7 @@ var Template = Module("Template", { linkifyHelp: function linkifyHelp(str, help) { let re = util.regexp( [/\s]|^) - (?P '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | [a-zA-Z]_\w+ | \[[\w-]+\] | E\d{3} ) + (?P '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:\w+|.) | \[[\w-]+\] | E\d{3} ) (?= [[\)!,:;./\s]|$) ]]>, "gx"); return this.highlightSubstrings(str, (function () { @@ -349,7 +349,7 @@ var Template = Module("Template", { highlightURL: function highlightURL(str, force) { if (force || /^[a-zA-Z]+:\/\//.test(str)) - return {str}; + return {util.losslessDecodeURI(str)}; else return str; }, @@ -363,7 +363,9 @@ var Template = Module("Template", { // return
    Totals: {_("title.Totals")}:  - Clear + {_("download.action.Clear")} / +
    + { @@ -204,7 +206,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef function (win) prefOverlay(branch, false, { append: { itemList: <> - + { template.map(ourItems(), function ([item, desc]) = 0) - items = Object.keys(sanitizer.itemMap).filter(function (k) items.indexOf(k) === -1); - - sanitizer.range = range.native; - sanitizer.ignoreTimespan = range.min == null; - sanitizer.sanitizing = true; - if (args["-host"]) { - args["-host"].forEach(function (host) { - sanitizer.sanitizing = true; - sanitizer.sanitizeItems(items, range, host) - }); - } + modules.commandline.input(_("sanitize.prompt.deleteAll") + " ", + function (resp) { + if (resp.match(/^y(es)?$/i)) { + items = Object.keys(sanitizer.itemMap).filter(function (k) items.indexOf(k) === -1); + sanitize(items); + dactyl.echo(_("command.sanitize.allDeleted")); + } + else + dactyl.echo(_("command.sanitize.noneDeleted")); + }); else - sanitizer.sanitize(items, range); + sanitize(items); + }, { argCount: "*", // FIXME: should be + and 0 @@ -453,7 +473,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef !args["-host"].some(function (host) util.isSubdomain(item.text, host))); modules.completion.domain(context); }, - type: modules.CommandOption.LIST, + type: modules.CommandOption.LIST }, { names: ["-older", "-o"], description: "Sanitize items older than timespan", @@ -575,7 +595,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef get values() values(sanitizer.itemMap).toArray(), has: modules.Option.has.toggleAll, validator: function (values) values.length && - values.every(function (val) val === "all" || set.has(sanitizer.itemMap, val)) + values.every(function (val) val === "all" || Set.has(sanitizer.itemMap, val)) }); options.add(["sanitizeshutdown", "ss"], @@ -593,10 +613,10 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef sanitizer.runAtShutdown = false; else { sanitizer.runAtShutdown = true; - let have = set(value); + let have = Set(value); for (let item in values(sanitizer.itemMap)) prefs.set(item.shutdownPref, - Boolean(set.has(have, item.name) ^ set.has(have, "all"))); + Boolean(Set.has(have, item.name) ^ Set.has(have, "all"))); } return value; } @@ -667,7 +687,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef }, initialValue: true, persist: false, - validator: function (val) parseInt(val) == val || modules.Option.validateCompleter.call(this, val) + validator: function validator(val) parseInt(val) == val || validator.superapply(this, arguments) }); } }); diff --git a/common/modules/services.jsm b/common/modules/services.jsm index bceb265..b3372dd 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -25,6 +25,7 @@ var Services = Module("Services", { this.add("appStartup", "@mozilla.org/toolkit/app-startup;1", "nsIAppStartup"); this.add("autoCompleteSearch", "@mozilla.org/autocomplete/search;1?name=history", "nsIAutoCompleteSearch"); this.add("bookmarks", "@mozilla.org/browser/nav-bookmarks-service;1", "nsINavBookmarksService"); + this.add("bootstrap", "@dactyl.googlecode.com/base/bootstrap"); this.add("browserSearch", "@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"); this.add("cache", "@mozilla.org/network/cache-service;1", "nsICacheService"); this.add("charset", "@mozilla.org/charset-converter-manager;1", "nsICharsetConverterManager"); @@ -39,11 +40,13 @@ var Services = Module("Services", { this.add("extensionManager", "@mozilla.org/extensions/manager;1", "nsIExtensionManager"); this.add("externalProtocol", "@mozilla.org/uriloader/external-protocol-service;1", "nsIExternalProtocolService"); this.add("favicon", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"); + this.add("file:", "@mozilla.org/network/protocol;1?name=file", "nsIFileProtocolHandler"); this.add("focus", "@mozilla.org/focus-manager;1", "nsIFocusManager"); this.add("history", "@mozilla.org/browser/global-history;2", ["nsIBrowserHistory", "nsIGlobalHistory3", "nsINavHistoryService", "nsPIPlacesDatabase"]); this.add("io", "@mozilla.org/network/io-service;1", "nsIIOService"); this.add("json", "@mozilla.org/dom/json;1", "nsIJSON", "createInstance"); + this.add("listeners", "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"); this.add("livemark", "@mozilla.org/browser/livemark-service;2", "nsILivemarkService"); this.add("mime", "@mozilla.org/mime;1", "nsIMIMEService"); this.add("observer", "@mozilla.org/observer-service;1", "nsIObserverService"); @@ -58,6 +61,7 @@ var Services = Module("Services", { this.add("stylesheet", "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService"); this.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"); this.add("tagging", "@mozilla.org/browser/tagging-service;1", "nsITaggingService"); + this.add("tld", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"); this.add("threading", "@mozilla.org/thread-manager;1", "nsIThreadManager"); this.add("urifixup", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); this.add("versionCompare", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"); @@ -67,7 +71,6 @@ var Services = Module("Services", { this.addClass("CharsetConv", "@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter", "charset"); this.addClass("File", "@mozilla.org/file/local;1", "nsILocalFile"); - this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", "nsIFileProtocolHandler"); this.addClass("Find", "@mozilla.org/embedcomp/rangefind;1", "nsIFind"); this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", "nsIFormatConverter"); this.addClass("HtmlEncoder", "@mozilla.org/layout/htmlCopyEncoder;1", "nsIDocumentEncoder"); @@ -84,6 +87,7 @@ var Services = Module("Services", { this.addClass("Timer", "@mozilla.org/timer;1", "nsITimer", "initWithCallback"); this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest"); this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", "nsIDOMXPathEvaluator"); + this.addClass("XMLDocument", "@mozilla.org/xml/xml-document;1", ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]); this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open"); this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter"); }, @@ -122,7 +126,7 @@ var Services = Module("Services", { * * @param {string} name The service's cache key. * @param {string} class The class's contract ID. - * @param {string|string[]} ifaces The interface or array of + * @param {string|[string]} ifaces The interface or array of * interfaces implemented by this service. * @param {string} meth The name of the function used to instantiate * the service. @@ -140,7 +144,7 @@ var Services = Module("Services", { * * @param {string} name The class's cache key. * @param {string} class_ The class's contract ID. - * @param {nsISupports|nsISupports[]} ifaces The interface or array of + * @param {nsISupports|[nsISupports]} ifaces The interface or array of * interfaces implemented by this class. * @param {string} init Name of a property or method used to initialise the * class. See {@link #_create}. @@ -171,7 +175,7 @@ var Services = Module("Services", { * * @param {string} name The service's cache key. */ - has: function (name) set.has(this.services, name) && this.services[name].class in Cc && + has: function (name) Set.has(this.services, name) && this.services[name].class in Cc && this.services[name].interfaces.every(function (iface) iface in Ci) }, { }, { diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index 28ad0e8..896daf4 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -150,6 +150,10 @@ var Storage = Module("Storage", { init: function () { this.cleanup(); + + if (services.bootstrap && !services.bootstrap.session) + services.bootstrap.session = {}; + this.session = services.bootstrap ? services.bootstrap.session : {}; }, cleanup: function () { @@ -294,9 +298,9 @@ var File = Class("File", { let file = services.File(); if (path instanceof Ci.nsIFile) - file = path.QueryInterface(Ci.nsIFile).clone(); + file = path.clone(); else if (/file:\/\//.test(path)) - file = services["file:"]().getFileFromURLSpec(path); + file = services["file:"].getFileFromURLSpec(path); else { try { let expandedPath = File.expandPath(path); @@ -316,14 +320,19 @@ var File = Class("File", { return self; }, + /** + * @property {nsIFileURL} Returns the nsIFileURL object for this file. + */ + get URI() services.io.newFileURI(this), + /** * Iterates over the objects in this directory. */ iterDirectory: function () { if (!this.exists()) - throw Error("File does not exist"); + throw Error(_("io.noSuchFile")); if (!this.isDirectory()) - throw Error("Not a directory"); + throw Error(_("io.eNotDir")); for (let file in iter(this.directoryEntries)) yield File(file); }, @@ -358,11 +367,11 @@ var File = Class("File", { * * @param {boolean} sort Whether to sort the returned directory * entries. - * @returns {nsIFile[]} + * @returns {[nsIFile]} */ readDirectory: function (sort) { if (!this.isDirectory()) - throw Error("Not a directory"); + throw Error(_("io.eNotDir")); let array = [e for (e in this.iterDirectory())]; if (sort) @@ -426,14 +435,11 @@ var File = Class("File", { ofstream.init(this, mode, perms, 0); try { - if (callable(buf)) - buf(ofstream.QueryInterface(Ci.nsIOutputStream)); - else { - var ocstream = getStream(0); - ocstream.writeString(buf); - } + var ocstream = getStream(0); + ocstream.writeString(buf); } - catch (e if callable(buf) && e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) { + catch (e if e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) { + ocstream.close(); ocstream = getStream("?".charCodeAt(0)); ocstream.writeString(buf); return false; diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index adfc725..70608b9 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -68,17 +68,20 @@ update(Sheet.prototype, { get fullCSS() { let filter = this.sites; let css = this.css; + + let preamble = "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n" + namespace + "\n"; if (filter[0] == "*") - return namespace + css; + return preamble + css; let selectors = filter.map(function (part) - (/[*]$/.test(part) ? "url-prefix" : - /[\/:]/.test(part) ? "url" - : "domain") - + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")') + !/^(?:[a-z-]+[:*]|[a-z-.]+$)/i.test(part) ? "regexp(" + (".*(?:" + part + ").*").quote() + ")" : + (/[*]$/.test(part) ? "url-prefix" : + /[\/:]/.test(part) ? "url" + : "domain") + + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")') .join(",\n "); - return "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n" - + namespace + "\n@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n"; + + return preamble + "@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n"; } }); @@ -253,8 +256,8 @@ var Styles = Module("Styles", { services["dactyl:"].providers["style"] = function styleProvider(uri) { let id = /^\/(\d*)/.exec(uri.path)[1]; - if (set.has(styles.allSheets, id)) - return ["text/css", unescape(encodeURI(styles.allSheets[id].fullCSS))]; + if (Set.has(styles.allSheets, id)) + return ["text/css", styles.allSheets[id].fullCSS]; return null; }; }, @@ -296,13 +299,15 @@ var Styles = Module("Styles", { systemNames: Class.Property({ get: deprecated("Styles#system.names", function systemNames() this.system.names) }), sites: Class.Property({ get: deprecated("Styles#user.sites", function sites() this.user.sites) }), - list: function list(content, filter, name, hives) { + list: function list(content, sites, name, hives) { const { commandline, dactyl } = this.modules; hives = hives || styles.hives.filter(function (h) h.modifiable && h.sheets.length); function sheets(group) group.sheets.slice() + .filter(function (sheet) (!name || sheet.name === name) && + (!sites || sites.every(function (s) sheet.sites.indexOf(s) >= 0))) .sort(function (a, b) a.name && b.name ? String.localeCompare(a.name, b.name) : !!b.name - !!a.name || a.id - b.id); @@ -312,9 +317,9 @@ var Styles = Module("Styles", {
    - NameFilterCSS{_("title.Name")}{_("title.Filter")}{_("title.CSS")}
    - + + + { this.map(Iterator(elems), function ([idx, val]) @@ -371,7 +373,7 @@ var Template = Module("Template", { - + ) }
    jumptitleURI{_("title.Jump")}{_("title.Title")}{_("title.URI")}
    {idx == index ? ">" : ""} {Math.abs(idx - index)} {val.title}{val.URI.spec}{util.losslessDecodeURI(val.URI.spec)}
    ; @@ -494,7 +496,7 @@ var Template = Module("Template", { let (name = item.name || item.names[0], frame = item.definedAt) !frame ? name : template.helpLink(help(item), name, "Title") + - Defined at {sourceLink(frame)} + {_("io.definedAt")} {sourceLink(frame)} }
    { item.columns ? template.map(item.columns, function (c) {c}) : "" } diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 673366a..7941ebf 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("util", { exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"], require: ["services"], - use: ["commands", "config", "highlight", "storage", "template"] + use: ["commands", "config", "highlight", "messages", "storage", "template"] }, this); var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); @@ -115,12 +115,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * * @param {object} obj */ - addObserver: function (obj) { + addObserver: update(function addObserver(obj) { if (!obj.observers) obj.observers = obj.observe; function register(meth) { - for (let target in set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers)))) + for (let target in Set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers)))) try { services.observer[meth](obj, target, true); } @@ -137,7 +137,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } catch (e) { if (typeof util === "undefined") - dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl: ")); + addObserver.dump("dactyl: error: " + e + "\n" + (e.stack || addObserver.Error().stack).replace(/^/gm, "dactyl: ")); else util.reportError(e); } @@ -145,7 +145,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), obj.observe.unregister = function () register("removeObserver"); register("addObserver"); - }, + }, { dump: dump, Error: Error }), /* * Tests a condition and throws a FailedAssertion error on @@ -158,6 +158,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), assert: function (condition, message, quiet) { if (!condition) throw FailedAssertion(message, 1, quiet === undefined ? true : quiet); + return condition; }, /** @@ -165,7 +166,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @param {string} str The string to capitalize * @returns {string} */ - capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1), + capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1).toLowerCase(), /** * Returns a RegExp object that matches characters specified in the range @@ -181,12 +182,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), // check for chars not in the accepted range this.assert(RegExp("^[" + accepted + "-]+$").test(list), - "Character list outside the range " + accepted.quote()); + _("error.charactersOutsideRange", accepted.quote())); // check for illegal ranges for (let [match] in this.regexp.iterate(/.-./g, list)) this.assert(match.charCodeAt(0) <= match.charCodeAt(2), - "Invalid character range: " + list.slice(list.indexOf(match))) + _("error.invalidCharacterRange", list.slice(list.indexOf(match)))); return RegExp("[" + util.regexp.escape(list) + "]"); }, @@ -308,7 +309,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } else if (char === "]") { stack.pop(); - util.assert(stack.length, "Unmatched %] in format"); + util.assert(stack.length, /*L*/"Unmatched %] in format"); } else { let quote = function quote(obj, char) obj[char]; @@ -327,10 +328,35 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (end < format.length) stack.top.elements.push(format.substr(end)); - util.assert(stack.length === 1, "Unmatched %[ in format"); + util.assert(stack.length === 1, /*L*/"Unmatched %[ in format"); return stack.top; }, + /** + * Compiles a macro string into a function which generates a string + * result based on the input *macro* and its parameters. The + * definitive documentation for macro strings resides in :help + * macro-string. + * + * Macro parameters may have any of the following flags: + * e: The parameter is only tested for existence. Its + * interpolation is always empty. + * q: The result is quoted such that it is parsed as a single + * argument by the Ex argument parser. + * + * The returned function has the following additional properties: + * + * seen {set}: The set of parameters used in this macro. + * + * valid {function(object)}: Returns true if every parameter of + * this macro is provided by the passed object. + * + * @param {string} macro The macro string to compile. + * @param {boolean} keepUnknown If true, unknown macro parameters + * are left untouched. Otherwise, they are replaced with the null + * string. + * @returns {function} + */ compileMacro: function compileMacro(macro, keepUnknown) { let stack = [frame()]; stack.__defineGetter__("top", function () this[this.length - 1]); @@ -355,14 +381,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), ([^]*?) // 1 (?: (<\{) | // 2 - (< ((?:[a-z]-)?[a-z-]+?) >) | // 3 4 - (\}>) // 5 + (< ((?:[a-z]-)?[a-z-]+?) (?:\[([0-9]+)\])? >) | // 3 4 5 + (\}>) // 6 ) ]]>, "gixy"); macro = String(macro); let end = 0; for (let match in re.iterate(macro)) { - let [, prefix, open, full, macro, close] = match; + let [, prefix, open, full, macro, idx, close] = match; end += match[0].length; if (prefix) @@ -374,11 +400,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } else if (close) { stack.pop(); - util.assert(stack.length, "Unmatched %] in macro"); + util.assert(stack.length, /*L*/"Unmatched %] in macro"); } else { let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro); - flags = set(flags); + flags = Set(flags); let quote = util.identity; if (flags.q) @@ -386,12 +412,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (flags.e) quote = function quote(obj) ""; - if (set.has(defaults, name)) + if (Set.has(defaults, name)) stack.top.elements.push(quote(defaults[name])); else { - stack.top.elements.push(update( - function (obj) obj[name] != null ? quote(obj[name]) : set.has(obj, name) ? "" : unknown(full), - { test: function (obj) obj[name] != null && obj[name] !== false && (!flags.e || obj[name] != "") })); + if (idx) { + idx = Number(idx) - 1; + stack.top.elements.push(update( + function (obj) obj[name] != null && idx in obj[name] ? quote(obj[name][idx]) : Set.has(obj, name) ? "" : unknown(full), + { test: function (obj) obj[name] != null && idx in obj[name] && obj[name][idx] !== false && (!flags.e || obj[name][idx] != "") })); + } + else { + stack.top.elements.push(update( + function (obj) obj[name] != null ? quote(obj[name]) : Set.has(obj, name) ? "" : unknown(full), + { test: function (obj) obj[name] != null && obj[name] !== false && (!flags.e || obj[name] != "") })); + } for (let elem in array.iterValues(stack)) elem.seen[name] = true; @@ -401,10 +435,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (end < macro.length) stack.top.elements.push(macro.substr(end)); - util.assert(stack.length === 1, "Unmatched <{ in macro"); + util.assert(stack.length === 1, /*L*/"Unmatched <{ in macro"); return stack.top; }, + /** + * Compiles a CSS spec and XPath pattern matcher based on the given + * list. List elements prefixed with "xpath:" are parsed as XPath + * patterns, while other elements are parsed as CSS specs. The + * returned function will, given a node, return an iterator of all + * descendants of that node which match the given specs. + * + * @param {[string]} list The list of patterns to match. + * @returns {function(Node)} + */ compileMatcher: function compileMatcher(list) { let xpath = [], css = []; for (let elem in values(list)) @@ -428,10 +472,18 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }); }, - validateMatcher: function validateMatcher(values) { + /** + * Validates a list as input for {@link #compileMatcher}. Returns + * true if and only if every element of the list is a valid XPath or + * CSS selector. + * + * @param {[string]} list The list of patterns to test + * @returns {boolean} True when the patterns are all valid. + */ + validateMatcher: function validateMatcher(list) { let evaluator = services.XPathEvaluator(); - let node = util.xmlToDom(
    , document); - return this.testValues(values, function (value) { + let node = services.XMLDocument(); + return this.testValues(list, function (value) { if (/^xpath:/.test(value)) evaluator.createExpression(value.substr(6), util.evaluateXPath.resolver); else @@ -483,10 +535,28 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * Example: * "a{b,c}d" => ["abd", "acd"] * - * @param {string} pattern The pattern to deglob. + * @param {string|[string|Array]} pattern The pattern to deglob. * @returns [string] The resulting strings. */ debrace: function debrace(pattern) { + if (isArray(pattern)) { + let res = []; + let rec = function rec(acc) { + let vals; + + while (isString(vals = pattern[acc.length])) + acc.push(vals); + + if (acc.length == pattern.length) + res.push(acc.join("")) + else + for (let val in values(vals)) + rec(acc.concat(val)); + } + rec([]); + return res; + } + if (pattern.indexOf("{") == -1) return [pattern]; @@ -501,12 +571,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), res.push(pattern.substr(end)); return res.map(function (s) util.dequote(s, dequote)); } - let patterns = [], res = []; + let patterns = []; let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy, function (match) { patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy, null, ",{}")); }, "{}"); + + let res = []; function rec(acc) { if (acc.length == patterns.length) res.push(array(substrings).zip(acc).flatten().join("")); @@ -529,6 +601,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), dequote: function dequote(pattern, chars) pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0), + /** + * Converts a given DOM Node, Range, or Selection to a string. If + * *html* is true, the output is HTML, otherwise it is presentation + * text. + * + * @param {nsIDOMNode | nsIDOMRange | nsISelection} node The node to + * stringify. + * @param {boolean} html Whether the output should be HTML rather + * than presentation text. + */ domToString: function (node, html) { if (node instanceof Ci.nsISelection && node.isCollapsed) return ""; @@ -538,7 +620,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), range.selectNode(node); node = range; } - let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer.ownerDocument; + let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer; + doc = doc.ownerDocument || doc; let encoder = services.HtmlEncoder(); encoder.init(doc, "text/unicode", encoder.OutputRaw|encoder.OutputPreformatted); @@ -564,6 +647,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ dump: defineModule.dump, + /** + * Returns a list of reformatted stack frames from + * {@see Error#stack}. + * + * @param {string} stack The stack trace from an Error. + * @returns {[string]} The stack frames. + */ stackLines: function (stack) { let lines = []; let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g; @@ -589,7 +679,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * The set of input element type attribute values that mark the element as * an editable field. */ - editableInputs: set(["date", "datetime", "datetime-local", "email", "file", + editableInputs: Set(["date", "datetime", "datetime-local", "email", "file", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week"]), @@ -738,12 +828,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), [hours, minutes] = div(minutes, 60); [days, hours] = div(hours, 24); if (days) - return days + " days " + hours + " hours" + return /*L*/days + " days " + hours + " hours" if (hours) - return hours + "h " + minutes + "m"; + return /*L*/hours + "h " + minutes + "m"; if (minutes) - return minutes + ":" + pad(2, seconds); - return seconds + "s"; + return /*L*/minutes + ":" + pad(2, seconds); + return /*L*/seconds + "s"; }, /** @@ -758,12 +848,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), uri = util.newURI(util.fixURI(uri)); if (uri instanceof Ci.nsIFileURL) - return File(uri.QueryInterface(Ci.nsIFileURL).file); + return File(uri.file); let channel = services.io.newChannelFromURI(uri); channel.cancel(Cr.NS_BINDING_ABORTED); if (channel instanceof Ci.nsIFileChannel) - return File(channel.QueryInterface(Ci.nsIFileChannel).file); + return File(channel.file); } catch (e) {} return null; @@ -826,7 +916,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), return xmlhttp; } catch (e) { - util.dactyl.log("Error opening " + String.quote(url) + ": " + e, 1); + util.dactyl.log(_("error.cantOpen", String.quote(url), e), 1); return null; } }, @@ -939,6 +1029,35 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + // ripped from Firefox; modified + unsafeURI: Class.memoize(function () util.regexp(String.replace(, /U/g, "\\u"), + "gx")), + losslessDecodeURI: function losslessDecodeURI(url) { + return url.split("%25").map(function (url) { + // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors. + try { + return decodeURI(url).replace(this.unsafeURI, encodeURIComponent); + } + catch (e) { + return url; + } + }, this).join("%25"); + }, + /** * Returns an XPath union expression constructed from the specified node * tests. An expression is built with node tests for both the null and @@ -949,10 +1068,27 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ makeXPath: function makeXPath(nodes) { return array(nodes).map(util.debrace).flatten() - .map(function (node) [node, "xhtml:" + node]).flatten() + .map(function (node) /^[a-z]+:/.test(node) ? node : [node, "xhtml:" + node]).flatten() .map(function (node) "//" + node).join(" | "); }, + /** + * Creates a DTD fragment from the given object. Each property of + * the object is converted to an ENTITY declaration. SGML special + * characters other than ' and % are left intact. + * + * @param {object} obj The object to convert. + * @returns {string} The DTD fragment containing entity declaration + * for *obj*. + */ + makeDTD: let (map = { "'": "'", '"': """, "%": "%", "&": "&", "<": "<", ">": ">" }) + function makeDTD(obj) iter(obj) + .map(function ([k, v]) ["]/g, + function (m) map[m]), + "'>"].join("")) + .join("\n"), + map: deprecated("iter.map", function map(obj, fn, self) iter(obj).map(fn, self).toArray()), writeToClipboard: deprecated("dactyl.clipboardWrite", function writeToClipboard(str, verbose) util.dactyl.clipboardWrite(str, verbose)), readFromClipboard: deprecated("dactyl.clipboardRead", function readFromClipboard() util.dactyl.clipboardRead(false)), @@ -964,7 +1100,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @returns {nsIURI} */ // FIXME: createURI needed too? - newURI: function (uri, charset, base) services.io.newURI(uri, charset, base), + newURI: function newURI(uri, charset, base) this.withProperErrors("newURI", services.io, uri, charset, base), /** * Removes leading garbage prepended to URIs by the subscript @@ -1029,7 +1165,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }; let tag = "<" + [namespaced(elem)].concat( - [namespaced(a) + "=" + template.highlight(a.value, true) + [namespaced(a) + "=" + template.highlight(a.value, true) for ([i, a] in array.iterItems(elem.attributes))]).join(" "); return tag + (!hasChildren ? "/>" : ">..."); } @@ -1090,13 +1226,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, observers: { - "dactyl-cleanup-modules": function () { - defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules"); + "dactyl-cleanup-modules": function (subject, reason) { + defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason); for (let module in values(defineModule.modules)) if (module.cleanup) { util.dump("cleanup: " + module.constructor.className); - util.trapErrors(module.cleanup, module); + util.trapErrors(module.cleanup, module, reason); } JSMLoader.cleanup(); @@ -1120,6 +1256,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), "dactyl-purge": function () { this.rehashing = 1; }, + "toplevel-window-ready": function (window, data) { window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) { if (event.originalTarget === window.document) { @@ -1199,6 +1336,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }), true); }, + /** + * Overlays an object with the given property overrides. Each + * property in *overrides* is added to *object*, replacing any + * original value. Functions in *overrides* are augmented with the + * new properties *super*, *supercall*, and *superapply*, in the + * same manner as class methods, so that they man call their + * overridden counterparts. + * + * @param {object} object The object to overlay. + * @param {object} overrides An object containing properties to + * override. + * @returns {function} A function which, when called, will remove + * the overlay. + */ overlayObject: function (object, overrides) { let original = Object.create(object); overrides = update(Object.create(original), overrides); @@ -1208,24 +1359,26 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (desc.value instanceof Class.Property) desc = desc.value.init(k) || desc.value; - for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj)) - if (orig = Object.getOwnPropertyDescriptor(obj, k)) - Object.defineProperty(original, k, orig); + if (k in object) { + for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj)) + if (orig = Object.getOwnPropertyDescriptor(obj, k)) + Object.defineProperty(original, k, orig); + + if (!orig) + if (orig = Object.getPropertyDescriptor(object, k)) + Object.defineProperty(original, k, orig); + } // Guard against horrible add-ons that use eval-based monkey // patching. + let value = desc.value; if (callable(desc.value)) { - let value = desc.value; - - let sentinel = "(function DactylOverlay() {}())" - value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&"); - value.toSource = function toSource() toString.toSource.call(this).replace(/\}?$/, sentinel + "; $&"); delete desc.value; delete desc.writable; desc.get = function get() value; desc.set = function set(val) { - if (String(val).indexOf(sentinel) < 0) + if (!callable(val) || Function.prototype.toString(val).indexOf(sentinel) < 0) Class.replaceProperty(this, k, val); else { let package_ = util.newURI(util.fixURI(Components.stack.caller.filename)).host; @@ -1235,12 +1388,37 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }; } - Object.defineProperty(object, k, desc); + try { + Object.defineProperty(object, k, desc); + + if (callable(value)) { + let sentinel = "(function DactylOverlay() {}())" + value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&"); + value.toSource = function toSource() toSource.toSource.call(this).replace(/\}?$/, sentinel + "; $&"); + } + } + catch (e) { + try { + if (value) { + object[k] = value; + return; + } + } + catch (f) {} + util.reportError(e); + } }, this); return function unwrap() { for each (let k in Object.getOwnPropertyNames(original)) - Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k)); + if (Object.getOwnPropertyDescriptor(object, k).configurable) + Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k)); + else { + try { + object[k] = original[k]; + } + catch (e) {} + } }; }, @@ -1305,17 +1483,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (field instanceof Ci.nsIDOMHTMLInputElement && field.type == "submit") elems.push(encode(field.name, field.value)); - for (let [, elem] in iter(form.elements)) { - if (set.has(util.editableInputs, elem.type) - || /^(?:hidden|textarea)$/.test(elem.type) - || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) - elems.push(encode(elem.name, elem.value, elem === field)); - else if (elem instanceof Ci.nsIDOMHTMLSelectElement) { - for (let [, opt] in Iterator(elem.options)) - if (opt.selected) - elems.push(encode(elem.name, opt.value)); + for (let [, elem] in iter(form.elements)) + if (elem.name && !elem.disabled) { + if (Set.has(util.editableInputs, elem.type) + || /^(?:hidden|textarea)$/.test(elem.type) + || elem.type == "submit" && elem == field + || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) + elems.push(encode(elem.name, elem.value, elem === field)); + else if (elem instanceof Ci.nsIDOMHTMLSelectElement) { + for (let [, opt] in Iterator(elem.options)) + if (opt.selected) + elems.push(encode(elem.name, opt.value)); + } } - } + if (post) return [url, elems.join('&'), charset, elems]; return [url + "?" + elems.join('&'), null, charset, elems]; @@ -1400,7 +1581,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), // Replace replacement . if (tokens) - expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m); + expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && Set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m); // Strip comments and white space. if (/x/.test(flags)) @@ -1471,9 +1652,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }()) }), + /** + * Reloads dactyl in entirety by disabling the add-on and + * re-enabling it. + */ rehash: function (args) { - JSMLoader.commandlineArgs = args; + storage.session.commandlineArgs = args; this.timeout(function () { + services.observer.notifyObservers(null, "startupcache-invalidate", ""); this.rehashing = true; let addon = config.addon; addon.userDisabled = true; @@ -1484,6 +1670,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), errorCount: 0, errors: Class.memoize(function () []), maxErrors: 15, + /** + * Reports an error to the Error Console and the standard output, + * along with a stack trace and other relevant information. The + * error is appended to {@see #errors}. + */ reportError: function (error) { if (error.noTrace) return; @@ -1541,7 +1732,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), catch (e) {} let ary = host.split("."); - ary = [ary.slice(i).join(".") for (i in util.range(ary.length - 1, 0, -1))]; + ary = [ary.slice(i).join(".") for (i in util.range(ary.length, 0, -1))]; return ary.filter(function (h) h.length >= base.length); }, @@ -1586,20 +1777,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), return true; }, - highlightFilter: function highlightFilter(str, filter, highlight) { - return this.highlightSubstrings(str, (function () { - if (filter.length == 0) - return; - let lcstr = String.toLowerCase(str); - let lcfilter = filter.toLowerCase(); - let start = 0; - while ((start = lcstr.indexOf(lcfilter, start)) > -1) { - yield [start, filter.length]; - start += filter.length; - } - })(), highlight || template.filter); - }, - /** * Behaves like String.split, except that when *limit* is reached, * the trailing element contains the entire trailing portion of the @@ -1658,6 +1835,20 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, yielders: 0, + /** + * Yields execution to the next event in the current thread's event + * queue. This is a potentially dangerous operation, since any + * yielders higher in the event stack will prevent execution from + * returning to the caller until they have finished their wait. The + * potential for deadlock is high. + * + * @param {boolean} flush If true, flush all events in the event + * queue before returning. Otherwise, wait for an event to + * process before proceeding. + * @param {boolean} interruptable If true, this yield may be + * interrupted by pressing , in which case, + * Error("Interrupted") will be thrown. + */ threadYield: function (flush, interruptable) { this.yielders++; try { @@ -1667,7 +1858,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), do { mainThread.processNextEvent(!flush); if (util.interrupted) - throw new Error("Interrupted"); + throw Error("Interrupted"); } while (flush === true && mainThread.hasPendingEvents()); } @@ -1676,6 +1867,19 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + /** + * Waits for the function *test* to return true, or *timeout* + * milliseconds to expire. + * + * @param {function} test The predicate on which to wait. + * @param {object} self The 'this' object for *test*. + * @param {Number} timeout The maximum number of milliseconds to + * wait. + * @optional + * @param {boolean} interruptable If true, may be interrupted by + * pressing , in which case, Error("Interrupted") will be + * thrown. + */ waitFor: function waitFor(test, self, timeout, interruptable) { let end = timeout && Date.now() + timeout, result; @@ -1690,6 +1894,23 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), return result; }, + /** + * Makes the passed function yieldable. Each time the function calls + * yield, execution is suspended for the yielded number of + * milliseconds. + * + * Example: + * let func = yieldable(function () { + * util.dump(Date.now()); // 0 + * yield 1500; + * util.dump(Date.now()); // 1500 + * }); + * func(); + * + * @param {function} func The function to mangle. + * @returns {function} A new function which may not execute + * synchronously. + */ yieldable: function yieldable(func) function magic() { let gen = func.apply(this, arguments); @@ -1701,6 +1922,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), })(); }, + /** + * Wraps a callback function such that its errors are not lost. This + * is useful for DOM event listeners, which ordinarily eat errors. + * The passed function has the property *wrapper* set to the new + * wrapper function, while the wrapper has the property *wrapped* + * set to the original callback. + * + * @param {function} callback The callback to wrap. + * @returns {function} + */ wrapCallback: wrapCallback, /** @@ -1722,7 +1953,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ trapErrors: function trapErrors(func, self) { try { - if (isString(func)) + if (!callable(func)) func = self[func]; return func.apply(self || this, Array.slice(arguments, 2)); } @@ -1732,6 +1963,15 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + /** + * Returns the file path of a given *url*, for debugging purposes. + * If *url* points to a file (even if indirectly), the native + * filesystem path is returned. Otherwise, the URL itself is + * returned. + * + * @param {string} url The URL to mangle. + * @returns {string} The path to the file. + */ urlPath: function urlPath(url) { try { return util.getFile(url).path; @@ -1741,18 +1981,33 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + /** + * Returns a list of all domains and subdomains of documents in the + * given window and all of its descendant frames. + * + * @param {nsIDOMWindow} win The window for which to find domains. + * @returns {[string]} The visible domains. + */ visibleHosts: function (win) { let res = [], seen = {}; (function rec(frame) { try { - res = res.concat(util.subdomains(frame.location.host)); + if (frame.location.hostname) + res = res.concat(util.subdomains(frame.location.hostname)); } catch (e) {} Array.forEach(frame.frames, rec); })(win); - return res.filter(function (h) !set.add(seen, h)); + return res.filter(function (h) !Set.add(seen, h)); }, + /** + * Returns a list of URIs of documents in the given window and all + * of its descendant frames. + * + * @param {nsIDOMWindow} win The window for which to find URIs. + * @returns {[nsIURI]} The visible URIs. + */ visibleURIs: function (win) { let res = [], seen = {}; (function rec(frame) { @@ -1762,7 +2017,24 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), catch (e) {} Array.forEach(frame.frames, rec); })(win); - return res.filter(function (h) !set.add(seen, h.spec)); + return res.filter(function (h) !Set.add(seen, h.spec)); + }, + + /** + * Wraps native exceptions thrown by the called function so that a + * proper stack trace may be retrieved from them. + * + * @param {function|string} meth The method to call. + * @param {object} self The 'this' object of the method. + * @param ... Arguments to pass to *meth*. + */ + withProperErrors: function withProperErrors(meth, self) { + try { + return (callable(meth) ? meth : self[meth]).apply(self, Array.slice(arguments, withProperErrors.length)); + } + catch (e) { + throw e.stack ? e : Error(e); + } }, /** diff --git a/common/skin/dactyl.css b/common/skin/dactyl.css index 57b01d3..f4db095 100644 --- a/common/skin/dactyl.css +++ b/common/skin/dactyl.css @@ -7,8 +7,13 @@ -moz-binding: url(resource://dactyl-content/bindings.xml#frame) !important; } +[dactyl|modifiable][dactyl|modifiable] { + -moz-user-input: enabled !important; +} + [dactyl|highlight~=hints] { -moz-binding: url(resource://dactyl-content/bindings.xml#hints) !important; + position: static !important; } [dactyl|highlight~=HintImage], diff --git a/common/tests/functional/dactyl.jsm b/common/tests/functional/dactyl.jsm index 9b2a20e..5f7bda5 100644 --- a/common/tests/functional/dactyl.jsm +++ b/common/tests/functional/dactyl.jsm @@ -301,7 +301,7 @@ Controller.prototype = { }), /** - * Returns dactyl to normal mode. + * Returns dactyl to Normal mode. */ setNormalMode: wrapAssertNoErrors(function () { // XXX: Normal mode test @@ -313,9 +313,9 @@ Controller.prototype = { utils.assert("dactyl.setNormalMode", this.modules.modes.stack.length == 1, "Failed to return to Normal mode"); - this.assertMessageWindowOpen(false, "Returning to normal mode: Multi-line output not closed"); - this.assertMessageLine(function (msg) !msg, "Returning to normal mode: Message not cleared"); - }, "Returning to normal mode"), + this.assertMessageWindowOpen(false, "Returning to Normal mode: Multi-line output not closed"); + this.assertMessageLine(function (msg) !msg, "Returning to Normal mode: Message not cleared"); + }, "Returning to Normal mode"), /** * Returns dactyl to Ex mode. diff --git a/common/tests/functional/testCommands.js b/common/tests/functional/testCommands.js index 59724b5..c65365a 100644 --- a/common/tests/functional/testCommands.js +++ b/common/tests/functional/testCommands.js @@ -47,12 +47,22 @@ var tests = { "!": { multiOutput: ["echo foo"] }, + get Clistkeys() this.listcommands, + Cmap: {}, + Cnoremap: {}, + Cunmap: {}, + get Ilistkeys() this.listcommands, + Imap: {}, + Inoremap: {}, + Iunmap: {}, abbreviate: { + error: ["!"], someOutput: ["", "abc"], noOutput: ["abc def", "-js abc def"], completions: ["", "abc ", "-js abc "] }, addons: { + error: ["!"], multiOutput: ["", "dactyl", "-type=extension", "-type=extension dactyl"], completions: [ "", @@ -75,6 +85,8 @@ var tests = { anyOutput: ["about:pentadactyl"], completions: [["", hasItems]] }, + get blistkeys() this.listcommands, + bmap: {}, bmark: { singleOutput: ["", "-tags=foo -title=bar -keyword=baz -charset=UTF-8 -post=quux about:pentadactyl"], error: ["-tags=foo -title=bar -keyword=baz -charset=nonExistentCharset -post=quux about:pentadactyl"], @@ -95,6 +107,7 @@ var tests = { "-max=1 -keyword=foo -tags=bar -title=baz about:" ] }, + bnoremap: {}, buffer: { anyOutput: ["", "1"], noOutput: ["!", "! 1"], @@ -104,15 +117,21 @@ var tests = { ] }, buffers: { + error: ["!"], multiOutput: ["", "1"], completions: ["", "1"] }, + bunmap: {}, cd: { + error: ["!"], singleOutput: ["", "~/"], completions: ["", "~/"] }, + get clistkeys() this.listcommands, + cmap: {}, + cnoremap: {}, colorscheme: { - error: ["", "some-nonexistent-scheme"] + error: ["!", "", "some-nonexistent-scheme"] }, command: { init: ["delc!"], @@ -131,15 +150,22 @@ var tests = { ["-group=user ", hasItems] ] }, - contexts: {}, // Not testable in this manner + completions: { + error: ["!", ""] + }, + contexts: { // Not testable in this manner + error: ["!"] + }, cookies: { anyOutput: ["dactyl.sf.net", "dactyl.sf.net list"], - error: [""], + error: ["!", ""], completions: [ "", ["dactyl.sf.net ", hasItems] ] }, + cunabbreviate: {}, + cunmap: {}, delbmarks: { anyOutput: ["", "about:pentadactyl"] }, delcommand: [ { @@ -157,8 +183,12 @@ var tests = { error: ["foo"] } ], + delgroup: { + error: ["", "! foo", "builtin"], + completions: [""] + }, delmacros: { - error: [""], + error: ["", "! foo"], noOutput: ["x"], completions: ["", "x"] }, @@ -168,17 +198,27 @@ var tests = { completions: ["", "-name=", "-name=foo ", "-index=", "-index="] }, dialog: { + error: ["!", ""], // Skip implementation for now completions: [ ["", hasntNullItems] ] }, - doautoall: {}, // Skip for now - doautocmd: {}, // Skip for now + dlclear: { + error: ["!"] + }, + doautoall: { + error: ["!"] + }, + doautocmd: { + error: ["!"] + }, downloads: { + error: ["!"], multiOutput: ["", "dactyl", "dactyl"] }, echo: { + error: ["!"], singleOutput: [ ["' - '", " - "] ], @@ -199,25 +239,35 @@ var tests = { __proto__: this.echo, }), get echomsg() this.echo, - else: {}, // Skip for now - elseif: {}, // Skip for now + else: { + error: ["!", "foo"] + }, + elseif: { + error: ["!", ""] + }, emenu: { noOutput: ["View.Zoom.Zoom In", "View.Zoom.Zoom Out"], - error: [""], + error: ["!", ""], completions: [ ["", hasItems], ["View.", hasItems] ] }, - endif: {}, // Skip for now + endif: { + error: ["!", "foo"] + }, execute: { + error: ["!"], noOutput: ["", "'js " + "".quote() + "'"], someOutput: ["'ls'"], completions: [["", hasItems]] }, + exit: { + error: ["foo"] + }, extadd: { completions: [["", hasItems]], - error: [""] + error: ["!", ""] }, extdelete: { completions: [["", hasItems]], @@ -239,12 +289,14 @@ var tests = { noOutput: [""], error: [""] }, - finish: { noOutput: [""] }, + finish: { + error: ["!", "foo"], + noOutput: [""] + }, forward: { noOutput: [""] }, - frameonly: { noOutput: [""] }, - delgroup: { - error: ["builtin"], - completions: [""] + frameonly: { + error: ["!", "foo"], + noOutput: [""] }, group: { multiOutput: [""], @@ -263,6 +315,7 @@ var tests = { }, hardcopy: {}, // Skip for now help: { + error: ["!"], noOutput: ["", "intro"], cleanup: ["tabdelete", "tabdelete"], completions: [ @@ -272,6 +325,7 @@ var tests = { }, get helpall() this.help, highlight: { + error: ["!"], multiOutput: ["", "Help"], noOutput: [ "Help foo: bar;", @@ -299,9 +353,17 @@ var tests = { "-sort=+date about:" ] }, - if: {}, // Skip for now + if: { + error: ["!", ""], + }, + iabbreviate: {}, + get ilistkeys() this.listcommands, + imap: {}, + inoremap: {}, + iunabbreviate: {}, + iunmap: {}, javascript: { - noOutput: ["''", "'\\n'", "
    foo bar
    ", "window"], + noOutput: ["''", "'\\n'", "
    foo bar
    ", "window", "<"], singleOutput: [""], multiOutput: ["g"] }, + nunmap: {}, open: { + error: ["!"], noOutput: ["about:blank | about:home"], completions: [ ["", hasItems], @@ -442,13 +526,18 @@ var tests = { pageinfo: { multiOutput: ["", "fgm"], completions: [["", hasItems]], - error: ["abcdefghijklmnopqrstuvwxyz", "f g m"] + error: ["!", "abcdefghijklmnopqrstuvwxyz", "f g m"] }, pagestyle: { + error: ["!"], completions: [""] }, - preferences: {}, // Skip for now + pintab: {}, + preferences: { + error: ["foo"] + }, pwd: { + error: ["!", "foo"], singleOutput: [""] }, qmark: { @@ -456,13 +545,16 @@ var tests = { "m", "m foo bar" ], - error: ["", "#"], + error: ["!", "", "#"], completions: [ ["", hasItems], ["m ", hasItems] ] }, qmarks: [ + { + error: ["!"] + }, { init: ["delqmarks a-zA-Z0-9"], error: ["", "x"], @@ -473,19 +565,30 @@ var tests = { completions: [["", hasItems]] } ], - quit: {}, // Skip for now - quitall: {}, // Skip for now + quit: { + error: ["foo"] + }, + quitall: { + error: ["!", "foo"] + }, redraw: { + error: ["!", "foo"], noOutput: [""] }, - rehash: {}, // Skip for now + rehash: { + error: ["!"] + }, reload: { + error: ["foo"], noOutput: [""] }, reloadall: { + error: ["foo"], noOutput: [""] }, - restart: {}, // Skip + restart: { + error: ["!", "foo"] + }, runtime: { init: [ "js File('~/.pentadactyl/some-nonexistent/good.css').write('')", @@ -501,6 +604,7 @@ var tests = { "some-nonexistent/good.penta" ], error: [ + "", "some-nonexistent/bad.js", "some-nonexistent/bad.penta" ], @@ -539,13 +643,16 @@ var tests = { }, saveas: {}, sbclose: { + error: ["!", "foo"], noOutput: [""] }, - scriptnames: {}, + scriptnames: { + error: ["!", "foo"] + }, set: { multiOutput: [ "vb?", "cpt?", "messages?", "titlestring?", "au?", "eht?", - "cpt", "messages", "titlestring", "au", "eht" + "cpt", "messages", "titlestring", "au", "eht", "! " ], noOutput: ["vb", "novb"], completions: [ @@ -595,6 +702,7 @@ var tests = { ] }, silent: { + error: ["!"], noOutput: [ "echo 'foo'", "echo " + "foo\nbar".quote(), @@ -613,6 +721,7 @@ var tests = { ".pentadactyl/some-nonexistent/good.penta" ], error: [ + "", ".pentadactyl/some-nonexistent/really-nonexistent.js", "~/.pentadactyl/some-nonexistent/bad.js", "~/.pentadactyl/some-nonexistent/bad.penta", @@ -629,9 +738,16 @@ var tests = { ["resource://dactyl/", hasItems] ] }), - stop: { noOutput: [""] }, - stopall: { noOutput: [""] }, + stop: { + error: ["!", "foo"], + noOutput: [""] + }, + stopall: { + error: ["!", "foo"], + noOutput: [""] + }, style: { + error: ["!"], cleanup: ["delstyle -n foo"], noOutput: [ "-name=foo http://does.not.exist/* div { display: inline; }", @@ -671,19 +787,59 @@ var tests = { ["-index=", hasNItems(2)] ] }, - tab: {}, - tabattach: {}, - tabdetach: {}, - tabdo: {}, - tabduplicate: {}, - tablast: {}, - tabmove: {}, - tabnext: {}, - tabonly: {}, + tab: { + error: ["!", "", "some-nonexistent-command"], + noOutput: ["js ''"], + anyOutput: ["echo 'foo'"], + completions: [["", hasItems]] + }, + tabattach: { + error: ["!", ""] + }, + tabdetach: { + error: ["!", "foo"] + }, + tabdo: { + error: ["!", "", "some-nonexistent-command"], + noOutput: ["js ''"], + anyOutput: ["echo 'foo'"], + completions: [["", hasItems]] + }, + tabduplicate: { + error: ["foo"] + }, + tablast: { + error: ["!", "foo"] + }, + tabmove: { + error: [""], + noOutput: ["1", "$", "999", "-1", "+1", "! +1", "! -1", "-999", "+999", "! +999", "! -999"], + completions: [ + ["", hasItems], + ["1", hasItems] + ] + }, + tabnext: { + error: ["!", "foo"] + }, + tabonly: { + error: ["!", "foo"] + }, tabopen: {}, - tabprevious: {}, - tabrewind: {}, - time: {}, + tabprevious: { + error: ["!", "foo"] + }, + tabrewind: { + error: ["!", "foo"] + }, + time: { + error: ["", ":some-nonexistent-command"/*, "some_nonexistent_reference"*/], // FIXME + singleOutput: [":js null", "null"] + + }, + get tlistkeys() this.listcommands, + tmap: {}, + tnoremap: {}, toolbarhide: { init: [ ["tbs Navigation Toolbar", toolbarState("#nav-bar", true)], @@ -694,7 +850,7 @@ var tests = { ["Navigation Toolbar", toolbarState("#nav-bar", false)], ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)] ], - error: ["", "foo"] + error: ["!", "", "foo"] }, toolbarshow: { completions: [["", hasItems]], @@ -702,7 +858,7 @@ var tests = { ["Navigation Toolbar", toolbarState("#nav-bar", true)], ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", true)] ], - error: ["", "foo"] + error: ["!", "", "foo"] }, toolbartoggle: { completions: [["", hasItems]], @@ -714,15 +870,25 @@ var tests = { ["Navigation Toolbar", toolbarState("#nav-bar", false)], ["Bookmarks Toolbar", toolbarState("#PersonalToolbar", false)] ], - error: ["", "foo"] + error: ["!", "", "foo"] }, + tunmap: {}, unabbreviate: { noOutput: ["abc", "! "], error: [""] }, - undo: {}, - undoall: {}, - unlet: {}, + undo: { + error: ["!"] + }, + undoall: { + error: ["!", "foo"] + }, + unpintab: { + error: ["!"] + }, + unlet: { + error: [""], + }, unmap: { noOutput: [ "i", @@ -738,8 +904,11 @@ var tests = { "-group=" ] }, - verbose: {}, + verbose: { + error: ["!", ""] + }, version: { + error: ["foo"], multiOutput: [ ["", function (msg) { var res = /(\w+dactyl) (\S+) \(([\^)]+)\) running on:\nMozilla/; @@ -748,11 +917,25 @@ var tests = { ] }, viewsource: {}, - winclose: {}, - window: {}, - winonly: {}, - winopen: {}, - wqall: {}, + get vlistkeys() this.listcommands, + vmap: {}, + vnoremap: {}, + vunmap: {}, + winclose: { + error: ["!", "foo"] + }, + window: { + error: ["!", ""] + }, + winonly: { + error: ["!", "foo"] + }, + winopen: { + error: ["!"] + }, + wqall: { + error: ["!", "foo"] + }, yank: { multiOutput: [ ["foo".quote(), /foo/], @@ -760,6 +943,7 @@ var tests = { [":addons", /Pentadactyl/] ], error: [ + "!", "", ":echoerr " + "foo".quote() ], completions: [ diff --git a/common/tests/functional/testOptions.js b/common/tests/functional/testOptions.js index c268743..8930132 100644 --- a/common/tests/functional/testOptions.js +++ b/common/tests/functional/testOptions.js @@ -31,7 +31,7 @@ var options = {}; function testCompleters() { for (var option in dactyl.modules.options) - for (var [,value] in Iterator([""].concat(options[option.name] || []))) { + for (var [, value] in Iterator([""].concat(options[option.name] || []))) { dump("OPT COMP " + option.name + " " + value + "\n"); dactyl.testCompleter(option, "completer", value, "Option '" + option.name + "' completer failed"); } diff --git a/melodactyl/content/config.js b/melodactyl/content/config.js index 0270203..0213a57 100644 --- a/melodactyl/content/config.js +++ b/melodactyl/content/config.js @@ -133,7 +133,7 @@ const Config = Module("config", ConfigBase, { }, /*** optional options, there are checked for existence and a fallback provided ***/ - features: set(["bookmarks", "hints", "marks", "history", "quickmarks", "session", "tabs", "player"]), + features: Set(["bookmarks", "hints", "marks", "history", "quickmarks", "session", "tabs", "player"]), defaults: { guioptions: "bCmprs", diff --git a/melodactyl/content/library.js b/melodactyl/content/library.js index be88474..95f1659 100644 --- a/melodactyl/content/library.js +++ b/melodactyl/content/library.js @@ -22,7 +22,7 @@ const Library = Module("library", { /** * Returns an array of all the artist names in the main library. * - * @returns {string[]} + * @returns {[string]} */ getArtists: function getArtists() this._toJSArray(this.MAIN_LIBRARY.getDistinctValuesForProperty(SBProperties.artistName)), @@ -32,7 +32,7 @@ const Library = Module("library", { * library. * * @param {string} artist The artist's name. - * @returns {string[]} + * @returns {[string]} */ getAlbums: function getAlbums(artist) { let albums = this._toJSArray(this.MAIN_LIBRARY.getItemsByProperty(SBProperties.artistName, artist)) @@ -46,7 +46,7 @@ const Library = Module("library", { * * @param {string} artist The artist's name. * @param {string} album The album's name. - * @returns {string[]} + * @returns {[string]} */ getTracks: function getTracks(artist, album) { let properties = services.MutablePropertyArray(); diff --git a/melodactyl/content/player.js b/melodactyl/content/player.js index 8eb8f11..f3b943a 100644 --- a/melodactyl/content/player.js +++ b/melodactyl/content/player.js @@ -44,34 +44,35 @@ const Player = Module("player", { onMediacoreEvent: function (event) { switch (event.type) { case Ci.sbIMediacoreEvent.BEFORE_TRACK_CHANGE: - dactyl.log("Before track changed: " + event.data); + dactyl.log(_("player.preTrackChange", event.data)); autocommands.trigger("TrackChangePre", { track: event.data }); break; case Ci.sbIMediacoreEvent.TRACK_CHANGE: + dactyl.log(_("player.trackChanged", event.data)); autocommands.trigger("TrackChange", { track: event.data }); break; case Ci.sbIMediacoreEvent.BEFORE_VIEW_CHANGE: - dactyl.log("Before view changed: " + event.data); + dactyl.log(_("player.preViewChange", event.data)); autocommands.trigger("ViewChangePre", { view: event.data }); break; case Ci.sbIMediacoreEvent.VIEW_CHANGE: - dactyl.log("View changed: " + event.data); + dactyl.log(_("player.viewChange", event.data)); autocommands.trigger("ViewChange", { view: event.data }); break; case Ci.sbIMediacoreEvent.STREAM_START: - dactyl.log("Track started: " + gMM.sequencer.currentItem); + dactyl.log(_("player.trackStart", gMM.sequencer.currentItem)); autocommands.trigger("StreamStart", { track: gMM.sequencer.currentItem }); break; case Ci.sbIMediacoreEvent.STREAM_PAUSE: - dactyl.log("Track paused: " + gMM.sequencer.currentItem); + dactyl.log(_("player.trackPause", gMM.sequencer.currentItem)); autocommands.trigger("StreamPause", { track: gMM.sequencer.currentItem }); break; case Ci.sbIMediacoreEvent.STREAM_END: - dactyl.log("Track ended: " + gMM.sequencer.currentItem); + dactyl.log(_("player.trackEnd", gMM.sequencer.currentItem)); autocommands.trigger("StreamEnd", { track: gMM.sequencer.currentItem }); break; case Ci.sbIMediacoreEvent.STREAM_STOP: - dactyl.log("Track stopped: " + gMM.sequencer.currentItem); + dactyl.log(_("player.trackStop", gMM.sequencer.currentItem)); autocommands.trigger("StreamStop", { track: gMM.sequencer.currentItem }); break; } @@ -332,7 +333,7 @@ const Player = Module("player", { /** * Returns an array of all available playlists. * - * @returns {sbIMediaList[]} + * @returns {[sbIMediaList]} */ getPlaylists: function getPlaylists() { let mainLibrary = LibraryUtils.mainLibrary; @@ -366,7 +367,7 @@ const Player = Module("player", { /** * Returns an array of all available media pages. * - * @returns {sbIMediaPageInfo[]} + * @returns {[sbIMediaPageInfo]} */ getMediaPages: function getMediaPages() { let list = SBGetBrowser().currentMediaPage.mediaListView.mediaList; @@ -375,7 +376,7 @@ const Player = Module("player", { }, /** - * Loads the the specified media page into *view* with the given *list* of + * Loads the specified media page into *view* with the given *list* of * media items. * * @param {sbIMediaPage} page @@ -660,7 +661,7 @@ const Player = Module("player", { let arg = args[0]; dactyl.assert(arg, _("error.argumentRequired")); - dactyl.assert(/^[+-]?\d+$/.test(arg), _("error.trailing")); + dactyl.assert(/^[+-]?\d+$/.test(arg), _("error.trailingCharacters")); let level = parseInt(arg, 10) / 100; diff --git a/melodactyl/locale/en-US/gui.xml b/melodactyl/locale/en-US/gui.xml index fd788b6..c4d308c 100644 --- a/melodactyl/locale/en-US/gui.xml +++ b/melodactyl/locale/en-US/gui.xml @@ -7,30 +7,6 @@ xmlns="&xmlns.dactyl;" xmlns:html="&xmlns.html;"> -
    -
    about
    About &dactyl.host;
    -
    addons
    Manage Add-ons
    -
    checkupdates
    Check for updates
    -
    cleardata
    Clear private data
    -
    cookies
    List your cookies
    -
    console
    JavaScript console
    -
    dominspector
    DOM Inspector
    -
    downloads
    Manage Downloads
    -
    jumpto
    Jump to a media item
    -
    newsmartplaylist
    Create a new smart playlist
    -
    openfile
    Open the file selector dialog
    -
    pagesource
    View page source
    -
    places
    Places Organizer: Manage your bookmarks and history
    -
    preferences
    Show &dactyl.host; preferences dialog
    -
    printsetup
    Setup the page size and orientation before printing
    -
    print
    Show print dialog
    -
    saveframe
    Save frame to disk
    -
    savepage
    Save page to disk
    -
    searchengines
    Manage installed search engines
    -
    selectionsource
    View selection source
    -
    subscribe
    Add a new subscription
    -
    - :dpcl :dpclose :dpclose pane diff --git a/melodactyl/locale/en-US/intro.xml b/melodactyl/locale/en-US/intro.xml index 7e49891..e2d3c36 100644 --- a/melodactyl/locale/en-US/intro.xml +++ b/melodactyl/locale/en-US/intro.xml @@ -41,7 +41,7 @@ etc.)
  • - Command-line mode: + Command Line mode: Command-line editing.
  • diff --git a/melodactyl/locale/en-US/messages.properties b/melodactyl/locale/en-US/messages.properties new file mode 100644 index 0000000..f5ec217 --- /dev/null +++ b/melodactyl/locale/en-US/messages.properties @@ -0,0 +1,10 @@ +player.preTrackChange-1 = Before track changed: %S +player.postTrackChange-1 = Track changed: %S +player.preViewChange-1 = Before view changed: %S +player.postViewChange-1 = View changed: %S +player.trackStart-1 = Track started: %S +player.trackPause-1 = Track paused: %S +player.trackEnd-1 = Track ended: %S +player.trackStop-1 = Track stopped: %S + +# vim:se ft=jproperties tw=0: diff --git a/pentadactyl/AUTHORS b/pentadactyl/AUTHORS index 6ebc13d..da3e1e8 100644 --- a/pentadactyl/AUTHORS +++ b/pentadactyl/AUTHORS @@ -1,8 +1,8 @@ Developers: * Kris Maglione * Doug Kearns - * anekos - * teramako + +Contributers: * Å těpán Němec Inactive/former developers: @@ -15,6 +15,8 @@ Inactive/former developers: * janus_wel * Martin Stubenschrott * Conrad Irwin + * anekos + * teramako Patches (in no special order): * Ruud Grosmann ('followhints' option) diff --git a/pentadactyl/Makefile b/pentadactyl/Makefile index 74afcb9..daf282e 100644 --- a/pentadactyl/Makefile +++ b/pentadactyl/Makefile @@ -5,8 +5,8 @@ NAME = pentadactyl FIREFOX ?= firefox HOSTAPP ?= $(FIREFOX) PROFILEPATHS ?= "$$HOME/.mozilla/firefox" \ - "$$HOME/Library/Mozilla/Firefox" \ - "$$APPDATA/Mozilla/Firefox" \ - "$$AppData/Mozilla/Firefox" + "$$HOME/Library/Application Support/Firefox" \ + "$$APPDATA/Mozilla/Firefox/Profiles" \ + "$$AppData/Mozilla/Firefox/Profiles" include ../common/Makefile diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index aece912..af99705 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -1,4 +1,4 @@ -1.0b6: +1.0b7pre: • Extensive Firefox 4 support, including: - Fully restartless. Can now be installed, uninstalled, enabled, disabled, and upgraded without restarting Firefox. @@ -48,8 +48,8 @@ quotes, i.e., 'fo\o''bar' ⇒ fo\o'bar [b1] • The command line is now hidden by default. Added c, C, and M to 'guioptions'. [b4] - • Hint mode improvements, including: - - Added g; continued extended hint mode, which allows + • Hints mode improvements, including: + - Added g; continued extended hints mode, which allows selecting multiple hints. Removed ;F. [b1] - Hints are now updated after scrolling and window resizing. [b3] - ;s now prompts for a filename on the command-line rather @@ -69,12 +69,15 @@ • The external editor can now be configured to open to a given line number and column, used for opening source links and editing input fields with i_. See :h 'editor'. [b4] + • Improved [macro-string] support, including automatic elision + of optional elements, and array subscripts. [b4][b7] • Mapping changes: - It's now possible to map keys in many more modes, including Hint, Multi-line Output, and Menu. [b4] - Added site-specific mapping groups and related command changes. [b6] - Added 'timeout' and 'timeoutlen' options. [b6] + - Added n_{, n_}, n_[, and n_] mappings. [b7] - Added to execute a builtin mapping. [b6] - Added l and s to aid in the construction of macros. [b6] @@ -89,7 +92,11 @@ listing keys for modes other than Normal, filtering the output, and linking to source code locations). [b4] - :downloads now opens a download list in the multi-line output - buffer. [b6] + buffer. Added -sort flag. [b6][b7] + - :style now supports regexp site-filters on Firefox 6+. [b7] + - :qa closes only the current window, per Vim. [b7] + - Added :exit command. [b7] + - Added :dlclear command. [b7] - :extensions has been replaced with a more powerful :addons. [b6] - :javascript! now opens a Read Eval Print Loop. [b6] - Added -arg flag to :map. [b6] @@ -108,6 +115,13 @@ - Added several new options, including -javascript, to :abbreviate and :map. [b2] - Added :mksyntax command to auto-generate Vim syntax files. [b4] + - Added 's' and 'e' flags to :pageinfo command. [b7] + - Added :pintab and :unpintab commands. [b7] + - Extension manipulation commands, including :extenable, :extdisable, + and :exttoggle, now accept a -types argument. [b7] + - :tabmove now moves to the position of tab N, not after it. + Without arguments it no longer moves the tab to the end of + the list. [b7] - :open now only opens files beginning with /, ./, ../, or ~/ [b1] - :saveas now provides completions for default file names, and automatically chooses a filename when the save target is a @@ -132,19 +146,19 @@ - CSS property name completion is now available. [b4] - :delstyle, :styleenable, :styledisable and :styletoggle accept a ! to operate on all styles. [b6] + - Added Find highlighting group. [b7] • IMPORTANT option changes: - Option value quoting has changed. List options will no longer be split at quoted commas and the option name, operators, and = sign may no longer be quoted. This will break certain automatically-generated configuration files. - See :help - [stringlist]. [b2] + See :help [stringlist]. [b2] - Boolean options no longer accept an argument. [b4] - 'cdpath' and 'runtimepath' no longer treat ",," specially. Use "." instead. [b2] - 'extendedhinttags' is now a [regexpmap] rather than a string. [b2] - - 'guioptions' default value has changed. [b4] + - 'guioptions' default value has changed. [b4][b7] - 'hinttags' and 'extendedhinttags' now treat their values as CSS selectors by default. [b6] - 'incsearch', 'hlsearch', 'ignorecase', and 'smartcase' have @@ -153,14 +167,14 @@ 'guioptions'. [b4] - 'linksearch' has been removed. The \l search modifier can still be used for this purpose. [b4] - - 'loadplugins' is now a regexplist option rather than + - 'loadplugins' is now a [regexplist] option rather than a boolean. [b2] - 'mapleader' is now an option rather than a :let variable. [b4] - - 'passkeys' is now a sitemap with key chain support rather - than a regexpmap. [b6] - - 'showmode' is now a regexplist. [b6] - - 'showstatuslinks' and 'showtabline' are now string options. [b4] + - 'passkeys' is now a [sitemap] with key chain support rather + than a [regexpmap]. [b6] + - 'showmode' is now a [regexplist]. [b6] + - 'showstatuslinks' and 'showtabline' are now [string] options. [b4] • IMPORTANT: Command script files now use the *.penta file extension. [b2] • IMPORTANT: Plugins are now loaded from the 'plugins/' directory in 'runtimepath' rather than 'plugin/'. [b1] @@ -174,14 +188,20 @@ - Added 'autocomplete' option for specifying which completion groups should be auto-completed. [b2] - Added 'banghist' option. [b1] + - Added 'cookies', 'cookieaccept', and 'cookielifetime' options. [b3] + - Added 'downloadsort' option. [b7] - Replaced 'focuscontent' with 'strictfocus'. [b1] + - Made 'strictfocus' a [sitemap]. [b7] - 'complete' now defaults to "slf" but file completion only triggers when the URL begins as above. [b1] + - Added 'jumptags' option. [b7] + - Added 's' flag to 'pageinfo' and changed default value. [b7] - Added 'passkeys' option. [b3] + - Added 'passunknown' option. [b7] - Changed 'urlseparator' default value to "|". [b3] - Added "passwords" and "venkman" dialogs to :dialog. [b2] + - Make 'showmode' a [stringlist] option. [b7] - Added 'wildanchor' option. [b2] - - Added 'cookies', 'cookieaccept', and 'cookielifetime' options. [b3] • Added BookmarkChange, BookmarkRemove autocommands. [b2] • Removed the :source line at the end of files generated by :mkpentadactylrc. [b2] diff --git a/pentadactyl/TODO b/pentadactyl/TODO index 9e11e72..809af63 100644 --- a/pentadactyl/TODO +++ b/pentadactyl/TODO @@ -2,69 +2,53 @@ Priority list: 1-9 as in Vim (9 = required for next release, 5 = would be nice, 1 = probably not) ARCHITECTURE: -- decide on how to document Arrays in our jsdoc(ish) documentation. Host docs - on the website - is there even a documentation tool that can parse our source - sensibly? +- Host docs on the website - is there even a documentation tool that + can parse our source sensibly? - unify the disgusting hodge podge of contract specification styles -REFACTORING: -- remove unnecessary usage of "self" - BUGS: -- insert abbreviations broken on -- :sidebar improvements (:sidebar! Downloads while downloads is open should refocus the sidebar) - ;s saves the page rather than the image - RC file is sourced once per window FEATURES: +9 / should work as in Vim (i.e., save page positions as well as + locations in the history list). +9 clean up error message codes and document +9 option groups, including buffer-local and site-specific +9 proper motion maps +8 wherever possible: get rid of dialogs and ask console-like dialog questions + or write error prompts directly on the webpage or with :echo() +8 add search capability to MOW 8 registers 8 Document Caret and Visual modes. 8 replace global variables with plugin scoped user options -8 fix local options 8 adaptive timeout for auto-completions, :set completions can be updated more often than :open foo -8 use the storage module for autocommands 8 add support for filename special characters such as % 8 :redir and 'verbosefile' 8 middleclick in content == p, and if command line is open, paste there the clipboard buffer 8 all search commands should start searching from the top of the visible viewport -8 / should work as in Vim (i.e., save page positions as well as - locations in the history list). -8 jump to the next heading with ]h, next image ]i, previous textbox [t and so on -7 clean up error message codes and document -7 add search capability to MOW +8 Add information to dactyl/HACKING file about testing and optimization 7 describe-key command (prompt for a key and display its binding with documentation) 7 Specify all INSERT mode mappings rather than falling through to the host apps mystery meat mappings. 7 use ctrl-n/p in insert mode for word completion 7 implement QuickFix window based on ItemList -7 wherever possible: get rid of dialogs and ask console-like dialog questions - or write error prompts directly on the webpage or with :echo() 7 [d could go to the last domain in the history stack. So if I browse from google to another page and click 10 links there, [d would take me back to the google page opera's fast forward does something like this 7 make an option to disable session saving by default when you close Firefox -7 The output of the pageinfo-command should contain the security-information of ssl-encrypted sites 7 :grep support (needs location list) -7 map in command-line mode to something useful (such as Down/Up) and - possibly to Vim-like behaviour 6 :mksession 6 add [count] support to :b* and :tab* commands where missing 6 check/correct spellings in insert mode with some mappings 6 add more autocommands (TabClose, TabOpen, TabChanged etc) 6 Use ctrl-w+j/k/w to switch between sidebar, content, preview window -6 Command :tags for getting a list of used tags 6 ;? should show more information -6 Add information to dactyl/HACKING file about testing and optimization 5 when looking at a zoomed out image (because it's large), zi should zoom in maybe with this? : http://mxr.mozilla.org/seamonkey/source/content/html/document/public/nsIImageDocument.idl -5 make a command to search within google search results - (http://gadelkareem.com/2007/01/28/using-google-ajax-api-as-an-array/) - maybe impossible, needs a per-site key from google -4 } { should jump to the next paragraph of the page 3 A format for 'guitablabel' and 'statusline' 3 add a command-line window (:help cmdline-window in Vim). 3 Splitting Windows with [:sp :vsp ctrl-w,s ctrl-w,v] and closing with [ctrl-w,q], moving with [ctrl-w,w or tab] have a look into the split browser extension -1 Reformat dactyl/HACKING so that git diff can find sections and report changes @ somewhere diff --git a/pentadactyl/content/config.js b/pentadactyl/content/config.js index 2dd3b53..6c52b2c 100644 --- a/pentadactyl/content/config.js +++ b/pentadactyl/content/config.js @@ -117,7 +117,7 @@ var Config = Module("config", ConfigBase, { LocationChange: "Triggered when changing tabs or when navigation to a new location", PageLoadPre: "Triggered after a page load is initiated", PageLoad: "Triggered when a page gets (re)loaded/opened", - PrivateMode: "Triggered when private mode is activated or deactivated", + PrivateMode: "Triggered when private browsing mode is activated or deactivated", Sanitize: "Triggered when a sanitizeable item is cleared", ShellCmdPost: "Triggered after executing a shell command with :!cmd", Enter: "Triggered after Firefox starts", @@ -132,7 +132,7 @@ var Config = Module("config", ConfigBase, { titlestring: "Pentadactyl" }, - features: set([ + features: Set([ "bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer", "session", "tabs", "tabs_undo", "windows" ]), @@ -205,7 +205,7 @@ var Config = Module("config", ConfigBase, { let title = document.getElementById("sidebar-title"); dactyl.assert(args.length || title.value || args.bang && config.lastSidebar, - "Argument required"); + _("error.argumentRequired")); if (!args.length) return window.toggleSidebar(title.value ? null : config.lastSidebar); @@ -227,7 +227,7 @@ var Config = Module("config", ConfigBase, { return; } - return dactyl.echoerr("No sidebar " + args[0] + " found"); + return dactyl.echoerr(_("error.invalidArgument", args[0])); }, { argCount: "?", @@ -248,7 +248,7 @@ var Config = Module("config", ConfigBase, { }); }, { - argCount: "+", + argCount: "1", completer: function (context) completion.ex(context), literal: 0, subCommand: 0 diff --git a/pentadactyl/install.rdf b/pentadactyl/install.rdf index 8b811cd..45de1fa 100644 --- a/pentadactyl/install.rdf +++ b/pentadactyl/install.rdf @@ -5,7 +5,7 @@ em:id="pentadactyl@dactyl.googlecode.com" em:type="2" em:name="Pentadactyl" - em:version="1.0b6" + em:version="1.0b7.1" em:description="Firefox for Vim and Links addicts" em:homepageURL="http://dactyl.sourceforge.net/pentadactyl" em:iconURL="resource://dactyl-local-skin/icon.png" @@ -14,7 +14,6 @@ Kris Maglione, Doug Kearns Kris Maglione Doug Kearns - Å těpán Němec anekos teramako @@ -27,12 +26,13 @@ janus_wel Martin Stubenschrott Conrad Irwin + Å těpán Němec + em:minVersion="3.6" + em:maxVersion="8.*"/> diff --git a/pentadactyl/locale/en-US/autocommands.xml b/pentadactyl/locale/en-US/autocommands.xml index 3726d8b..d2aa0ce 100644 --- a/pentadactyl/locale/en-US/autocommands.xml +++ b/pentadactyl/locale/en-US/autocommands.xml @@ -18,7 +18,7 @@
    LocationChange
    Triggered when changing tabs or when navigating to a new location
    PageLoadPre
    Triggered after a page load is initiated
    PageLoad
    Triggered when a page gets (re)loaded/opened
    -
    PrivateMode
    Triggered when private mode is activated or deactivated
    +
    PrivateMode
    Triggered when private browsing mode is activated or deactivated
    Sanitize
    Triggered when private data are sanitized
    ShellCmdPost
    Triggered after executing a shell command with :!cmd
    Enter
    Triggered after &dactyl.host; starts
    @@ -40,6 +40,7 @@
    <tags>
    The tags applied to <url>. Only for Bookmark*.
    <title>
    The page, bookmark or download title.
    <url>
    The URL against which the event was selected.
    +
    <win>
    The window for which the event occurred. Only for DOMLoad, PageLoad and PageLoadPre.
    diff --git a/pentadactyl/locale/en-US/gui.xml b/pentadactyl/locale/en-US/gui.xml deleted file mode 100644 index 1c4e9fb..0000000 --- a/pentadactyl/locale/en-US/gui.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - -
    -
    about
    About Mozilla &dactyl.host;
    -
    addbookmark
    Add bookmark for the current page
    -
    addons
    Manage Add-ons
    -
    bookmarks
    List your bookmarks
    -
    checkupdates
    Check for updates
    -
    cookies
    List your cookies
    -
    console
    JavaScript console
    -
    customizetoolbar
    Customize the Toolbar
    -
    dominspector
    DOM Inspector
    -
    downloads
    Manage Downloads
    -
    history
    List your history
    -
    import
    Import Preferences, Bookmarks, History, etc. from other browsers
    -
    openfile
    Open the file selector dialog
    -
    pageinfo
    Show information about the current page
    -
    pagesource
    View page source
    -
    passwords
    Passwords dialog
    -
    places
    Places Organizer: Manage your bookmarks and history
    -
    preferences
    Show &dactyl.host; preferences dialog
    -
    printpreview
    Preview the page before printing
    -
    printsetup
    Setup the page size and orientation before printing
    -
    print
    Show print dialog
    -
    saveframe
    Save frame to disk
    -
    savepage
    Save page to disk
    -
    searchengines
    Manage installed search engines
    -
    selectionsource
    View selection source
    -
    venkman
    The JavaScript debugger
    -
    - -
    - - diff --git a/pentadactyl/locale/en-US/intro.xml b/pentadactyl/locale/en-US/intro.xml index bc8bd7f..2f3b1d9 100644 --- a/pentadactyl/locale/en-US/intro.xml +++ b/pentadactyl/locale/en-US/intro.xml @@ -35,7 +35,7 @@ etc.)
  • - Command-line mode: + Command Line mode: Command-line editing.
  • @@ -134,7 +134,7 @@
  • Quick and powerful keyboard navigation for links, input fields, etc.
  • Vim-like status line
  • Keyboard macros, along with custom key mappings and commands
  • -
  • Minimal GUI, along with commands to hide and toggle toolbars and menus
  • +
  • Minimal GUI, along with commands to hide and toggle toolbars and menus
  • Ability to :source JavaScript, CSS, and &dactyl.appName; command files
  • Ability to mark the current page position and return to it
  • Count support for many commands (3 will go back 3 pages)
  • diff --git a/pentadactyl/locale/en-US/messages.properties b/pentadactyl/locale/en-US/messages.properties new file mode 100644 index 0000000..e3d5b3b --- /dev/null +++ b/pentadactyl/locale/en-US/messages.properties @@ -0,0 +1,2 @@ + +# vim:se ft=jproperties tw=0: diff --git a/pentadactyl/locale/en-US/tutorial.xml b/pentadactyl/locale/en-US/tutorial.xml index 4ab27d6..1a88ee5 100644 --- a/pentadactyl/locale/en-US/tutorial.xml +++ b/pentadactyl/locale/en-US/tutorial.xml @@ -47,7 +47,7 @@ &dactyl.appName;'s power, like Vim's, comes from its modal interface. Keys have different meanings depending on which mode the browser is in. &dactyl.appName; has several modes, but the 2 most important are Normal mode and - Command-line mode. + Command Line mode.

    @@ -56,13 +56,13 @@

    - The other core mode of &dactyl.appName;, Command-line mode, can be entered from + The other core mode of &dactyl.appName;, Command Line mode, can be entered from Normal mode by typing a : (colon). You will frequently see &dactyl.appName; commands start with a :, indicating that what follows is a command.

    - To return to Normal mode from Command-line mode, type . Pressing + To return to Normal mode from Command Line mode, type . Pressing will also return you to Normal mode from most other modes in &dactyl.appName;.

    @@ -229,19 +229,19 @@

    - The most common hint mode is called quick-hints. - To activate QuickHint mode, press either f or F. The lower-case - f will open the resulting link in the current tab, while the upper-case - F will open it in a new tab. + To activate Hints mode, press either f or F. The lower-case + f will open the resulting link in the current tab, while the + upper-case F will open it in a new tab.

    To test it, try this link: &dactyl.appName; Homepage. - Activate QuickHint mode with f or F to highlight all currently + Activate Hints mode with f or F to highlight all currently visible links. Then start typing the text of the link. The link should be - uniquely identified soon, and &dactyl.appName; will open it. Once you're done, - remember to use (History Back) or d (Delete Buffer) - to return here, depending on which key you used to activate QuickHint mode. + uniquely identified soon, and &dactyl.appName; will open it. Once you're + done, remember to use (History Back) or d + (Delete Buffer) to return here, depending on which key you used to + activate Hints mode.

    Common issues

    diff --git a/teledactyl/bootstrap.js b/teledactyl/bootstrap.js new file mode 120000 index 0000000..ff1024d --- /dev/null +++ b/teledactyl/bootstrap.js @@ -0,0 +1 @@ +../common/bootstrap.js \ No newline at end of file diff --git a/teledactyl/content/addressbook.js b/teledactyl/content/addressbook.js index 2a7dd1e..ecc5524 100644 --- a/teledactyl/content/addressbook.js +++ b/teledactyl/content/addressbook.js @@ -4,7 +4,7 @@ // given in the LICENSE.txt file included with this file. "use strict"; -const Addressbook = Module("addressbook", { +var Addressbook = Module("addressbook", { init: function () { }, @@ -63,9 +63,9 @@ const Addressbook = Module("addressbook", { if (addresses.length < 1) { if (!filter) - dactyl.echoerr("Exxx: No contacts", commandline.FORCE_SINGLELINE); + dactyl.echoerr(_("addressbook.noContacts"), commandline.FORCE_SINGLELINE); else - dactyl.echoerr("Exxx: No contacts matching string '" + filter + "'", commandline.FORCE_SINGLELINE); + dactyl.echoerr(_("addressbook.noMatchingContacts"), filter, commandline.FORCE_SINGLELINE); return false; } @@ -100,9 +100,9 @@ const Addressbook = Module("addressbook", { displayName = this.generateDisplayName(firstName, lastName); if (addressbook.add(mailAddr, firstName, lastName, displayName)) - dactyl.echomsg("Added address: " + displayName + " <" + mailAddr + ">", 1, commandline.FORCE_SINGLELINE); + dactyl.echomsg(_("addressbook.added", displayName, mailAddr), 1, commandline.FORCE_SINGLELINE); else - dactyl.echoerr("Exxx: Could not add contact `" + mailAddr + "'", commandline.FORCE_SINGLELINE); + dactyl.echoerr(_("addressbook.cantAdd", mailAddr), commandline.FORCE_SINGLELINE); }, { diff --git a/teledactyl/content/config.js b/teledactyl/content/config.js index 67586e8..3a883ae 100644 --- a/teledactyl/content/config.js +++ b/teledactyl/content/config.js @@ -4,7 +4,7 @@ // given in the LICENSE.txt file included with this file. "use strict"; -const Config = Module("config", ConfigBase, { +var Config = Module("config", ConfigBase, { name: "teledactyl", appName: "Teledactyl", idName: "TELEDACTYL", @@ -16,12 +16,16 @@ const Config = Module("config", ConfigBase, { init: function init() { init.superapply(this, arguments); - modules.__defineGetter__("content", function () window.content); + if (!("content" in modules)) + modules.__defineGetter__("content", function () window.content); util.overlayWindow(window, { append: <> }); }, - get browser() window.getBrowser(), + get browser() + let (tabmail = document.getElementById('tabmail')) + tabmail && tabmail.tabInfo.length ? tabmail.getBrowserForSelectedTab() + : document.getElementById("messagepane"), get commandContainer() document.documentElement.id, @@ -33,6 +37,8 @@ const Config = Module("config", ConfigBase, { get mStrip() this.tabStrip, get browsers() [browser for (browser in Iterator(this.mTabs))], + removeTab: function removeTab(tab) this.closeTab(tab), + loadOneTab: function loadOneTab(uri) { return this.openTab("contentTab", { contentPage: uri }); }, @@ -137,7 +143,7 @@ const Config = Module("config", ConfigBase, { }, /*** optional options, there are checked for existence and a fallback provided ***/ - features: Class.memoize(function () set( + features: Class.memoize(function () Set( this.isComposeWindow ? ["addressbook"] : ["hints", "mail", "marks", "addressbook", "tabs"])), diff --git a/teledactyl/content/mail.js b/teledactyl/content/mail.js index ff01820..3b67b45 100644 --- a/teledactyl/content/mail.js +++ b/teledactyl/content/mail.js @@ -4,7 +4,7 @@ // given in the LICENSE.txt file included with this file. "use strict"; -const Mail = Module("mail", { +var Mail = Module("mail", { init: function init() { // used for asynchronously selecting messages after wrapping folders this._selectMessageKeys = []; @@ -70,9 +70,9 @@ const Mail = Module("mail", { _moveOrCopy: function (copy, destinationFolder, operateOnThread) { let folders = mail.getFolders(destinationFolder); if (folders.length == 0) - return void dactyl.echoerr("Exxx: No matching folder for " + destinationFolder); + return void dactyl.echoerr(_("addressbook.noMatchingFolder", destinationFolder)); else if (folders.length > 1) - return dactyl.echoerr("Exxx: More than one match for " + destinationFolder); + return dactyl.echoerr(_("addressbook.multipleFolderMatches", destinationFolder)); let count = gDBView.selection.count; if (!count) @@ -129,7 +129,7 @@ const Mail = Module("mail", { get currentFolder() gFolderTreeView.getSelectedFolders()[0], - /** @property {nsISmtpServer[]} The list of configured SMTP servers. */ + /** @property {[nsISmtpServer]} The list of configured SMTP servers. */ get smtpServers() { let servers = services.smtp.smtpServers; let res = []; @@ -168,7 +168,7 @@ const Mail = Module("mail", { let url = args.attachments.pop(); let file = io.getFile(url); if (!file.exists()) - return void dactyl.echoerr("Exxx: Could not attach file `" + url + "'", commandline.FORCE_SINGLELINE); + return void dactyl.echoerr(_("mail.cantAttachFile", url), commandline.FORCE_SINGLELINE); attachment = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment); attachment.url = "file://" + file.path; @@ -402,7 +402,7 @@ const Mail = Module("mail", { let folder = mail.getFolders(arg, true, true)[count]; if (!folder) - dactyl.echoerr("Exxx: Folder \"" + arg + "\" does not exist"); + dactyl.echoerr(_("command.goto.folderNotExist", arg)); else if (dactyl.forceNewTab) MsgOpenNewTabForFolder(folder.URI); else @@ -434,7 +434,7 @@ const Mail = Module("mail", { // TODO: is there a better way to check for validity? if (addresses.some(function (recipient) !(/\S@\S+\.\S/.test(recipient)))) - return void dactyl.echoerr("Exxx: Invalid e-mail address"); + return void dactyl.echoerr(_("command.mail.invalidEmailAddress")); mail.composeNewMail(mailargs); }, @@ -453,7 +453,7 @@ const Mail = Module("mail", { "Copy selected messages", function (args) { mail._moveOrCopy(true, args.literalArg); }, { - argCount: 1, + argCount: "1", completer: function (context) completion.mailFolder(context), literal: 0 }); @@ -462,7 +462,7 @@ const Mail = Module("mail", { "Move selected messages", function (args) { mail._moveOrCopy(false, args.literalArg); }, { - argCount: 1, + argCount: "1", completer: function (context) completion.mailFolder(context), literal: 0 }); diff --git a/teledactyl/install.rdf b/teledactyl/install.rdf index d68eb62..ae82206 100644 --- a/teledactyl/install.rdf +++ b/teledactyl/install.rdf @@ -5,17 +5,17 @@ em:id="teledactyl@dactyl.googlecode.com" em:type="2" em:name="Teledactyl" - em:version="0.5b1pre" + em:version="0.6" em:description="Thunderbird for Mutt and Vim addicts" em:creator="Kris Maglione" em:homepageURL="http://dactyl.sf.net/Teledactyl" em:iconURL="chrome://teledactyl/skin/icon.png" - em:optionsURL="chrome://dactyl/content/preferences.xul"> + em:bootstrap="true"> {3550f703-e582-4d05-9a08-453d09bdfdc6} - 3.0b3 - 3.2 + 5.0 + 8.* diff --git a/teledactyl/locale/en-US/gui.xml b/teledactyl/locale/en-US/gui.xml deleted file mode 100644 index 4f8b449..0000000 --- a/teledactyl/locale/en-US/gui.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - -
    -
    about
    About &dactyl.host;
    -
    addons
    Manage Add-ons
    -
    addressbook
    Address book
    -
    checkupdates
    Check for updates
    -
    console
    JavaScript console
    -
    dominspector
    DOM Inspector
    -
    downloads
    Manage Downloads
    -
    openfile
    Open the file selector dialog
    -
    pageinfo
    Show information about the current page
    -
    pagesource
    View page source
    -
    preferences
    Show &dactyl.host; preferences dialog
    -
    printsetup
    Setup the page size and orientation before printing
    -
    print
    Show print dialog
    -
    saveframe
    Save frame to disk
    -
    savepage
    Save page to disk
    -
    - -
    - - diff --git a/teledactyl/locale/en-US/intro.xml b/teledactyl/locale/en-US/intro.xml index 4f111d5..328fb1c 100644 --- a/teledactyl/locale/en-US/intro.xml +++ b/teledactyl/locale/en-US/intro.xml @@ -35,7 +35,7 @@ etc.)
  • - Command-line mode: + Command Line mode: Command-line editing.
  • diff --git a/teledactyl/locale/en-US/messages.properties b/teledactyl/locale/en-US/messages.properties new file mode 100644 index 0000000..ddc929b --- /dev/null +++ b/teledactyl/locale/en-US/messages.properties @@ -0,0 +1,14 @@ +addressbook.noContacts = No contacts +addressbook.noMatchingContacts-1 = No contacts matching '%S' +addressbook.added-2 = Added address: %S <%S> +addressbook.cantAdd-1 = Could not add contact '%S' + +command.goto.folderNotExist-1 = Folder '%S' does not exist +command.mail.invalidEmailAddress = Invalid e-mail address + +mail.noMatchingFolder-1 = No matching folder for %S +mail.multipleFolderMatches-1 = More than one match for %S +mail.cantAttachFile = Could not attach file '%S' + +# vim:se ft=jproperties tw=0: + -- 2.39.2