diff options
-rw-r--r-- | common/content/abbreviations.js | 2 | ||||
-rw-r--r-- | common/content/autocommands.js | 4 | ||||
-rw-r--r-- | common/content/buffer.js | 43 | ||||
-rw-r--r-- | common/content/commandline.js | 21 | ||||
-rw-r--r-- | common/content/commands.js | 222 | ||||
-rw-r--r-- | common/content/contexts.js | 318 | ||||
-rw-r--r-- | common/content/dactyl.js | 16 | ||||
-rw-r--r-- | common/content/mappings.js | 136 | ||||
-rw-r--r-- | common/content/mow.js | 8 | ||||
-rw-r--r-- | common/content/options.js | 2 | ||||
-rw-r--r-- | common/modules/addons.jsm | 4 | ||||
-rw-r--r-- | common/modules/base.jsm | 14 | ||||
-rw-r--r-- | common/modules/io.jsm | 11 | ||||
-rw-r--r-- | common/modules/overlay.jsm | 6 | ||||
-rw-r--r-- | common/modules/storage.jsm | 2 | ||||
-rw-r--r-- | common/modules/util.jsm | 11 |
16 files changed, 472 insertions, 348 deletions
diff --git a/common/content/abbreviations.js b/common/content/abbreviations.js index c87c3bf9..030adc90 100644 --- a/common/content/abbreviations.js +++ b/common/content/abbreviations.js @@ -241,7 +241,7 @@ var Abbreviations = Module("abbreviations", { abbreviations.list(modes, lhs || ""); else { if (args["-javascript"]) - rhs = Command.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]); + rhs = contexts.bindMacro({ literalArg: rhs }, "-javascript", ["editor"]); abbreviations.add(modes, lhs, rhs); } }, { diff --git a/common/content/autocommands.js b/common/content/autocommands.js index a60b64a5..ed962466 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -156,7 +156,7 @@ var AutoCommands = Module("autocommands", { if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern if (args.bang) autocommands.remove(event, regexp); - cmd = Command.bindMacro(args, "-ex", function (params) params); + cmd = contexts.bindMacro(args, "-ex", function (params) params); autocommands.add(events, regexp, cmd); } else { @@ -245,7 +245,7 @@ var AutoCommands = Module("autocommands", { }; }, javascript: function () { - JavaScript.setCompleter(this.get, [function () Iterator(config.autocommands)]); + JavaScript.setCompleter(autocommands.get, [function () Iterator(config.autocommands)]); }, options: function () { options.add(["eventignore", "ei"], diff --git a/common/content/buffer.js b/common/content/buffer.js index 62d5a69e..5388e226 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -262,7 +262,7 @@ var Buffer = Module("buffer", { dactylLoadCount: 0, // XXX: function may later be needed to detect a canceled synchronous openURL() - onStateChange: function onStateChange(webProgress, request, flags, status) { + 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 @@ -286,9 +286,9 @@ var Buffer = Module("buffer", { statusline.updateUrl(); } } - }, + }), // for notifying the user about secure web pages - onSecurityChange: function onSecurityChange(webProgress, request, state) { + onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) { onSecurityChange.superapply(this, arguments); if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) statusline.security = "broken"; @@ -300,22 +300,27 @@ var Buffer = Module("buffer", { statusline.security = "insecure"; if (webProgress && webProgress.DOMWindow) webProgress.DOMWindow.document.dactylSecurity = statusline.security; - }, - onStatusChange: function onStatusChange(webProgress, request, status, message) { + }), + onStatusChange: util.wrapCallback(function onStatusChange(webProgress, request, status, message) { onStatusChange.superapply(this, arguments); statusline.updateUrl(message); - }, - onProgressChange: function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { - onProgressChange.superapply(this, arguments); - if (webProgress.DOMWindow) - webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress; - statusline.progress = curTotalProgress / maxTotalProgress; - }, + }), + onProgressChange: util.wrapCallback(function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { + try { + onProgressChange.superapply(this, arguments); + if (webProgress && webProgress.DOMWindow) + webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress; + statusline.progress = curTotalProgress / maxTotalProgress; + } + catch (e) { + util.reportError(e); + } + }), // happens when the users switches tabs - onLocationChange: function onLocationChange(webProgress, request, uri) { + onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) { onLocationChange.superapply(this, arguments); - delete mappings.hives; + delete contexts.groups; statusline.updateUrl(); statusline.progress = ""; @@ -352,13 +357,13 @@ var Buffer = Module("buffer", { if (loaded.commandline) commandline.clear(); }, 500); - }, + }), // called at the very end of a page load - asyncUpdateUI: function asyncUpdateUI() { + asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() { asyncUpdateUI.superapply(this, arguments); util.timeout(function () { statusline.updateUrl(); }, 100); - }, - setOverLink: function setOverLink(link, b) { + }), + setOverLink: util.wrapCallback(function setOverLink(link, b) { setOverLink.superapply(this, arguments); switch (options["showstatuslinks"]) { case "status": @@ -371,7 +376,7 @@ var Buffer = Module("buffer", { commandline.clear(); break; } - }, + }), }, /** diff --git a/common/content/commandline.js b/common/content/commandline.js index 4dd50eee..0e052bf4 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -352,8 +352,8 @@ var CommandMode = Class("CommandMode", { if (this.history) this.history.save(); - commandline.hideCompletions(); this.resetCompletions(); + commandline.hideCompletions(); modes.delay(function () { if (!this.keepCommand || commandline.silent || commandline.quiet) @@ -423,11 +423,13 @@ var CommandExMode = Class("CommandExMode", CommandMode, { }, onSubmit: function onSubmit(command) { - io.withSavedValues(["readHeredoc", "sourcing"], function () { - this.sourcing = { file: "[Command Line]", line: 1 }; + io.withSavedValues(["readHeredoc"], function () { this.readHeredoc = commandline.readHeredoc; - commands.repeat = command; - dactyl.execute(command); + contexts.withSavedValues(["context"], function () { + this.context = { file: "[Command Line]", line: 1 }; + commands.repeat = command; + dactyl.execute(command); + }); }); } }); @@ -504,11 +506,6 @@ var CommandLine = Module("commandline", { }, message)); } }; //}}} - - this._silent = false; - this._quiet = false; - this._lastEcho = null; - }, /** @@ -546,12 +543,14 @@ var CommandLine = Module("commandline", { get completionContext() this._completions.context, + _silent: false, get silent() this._silent, set silent(val) { this._silent = val; this.quiet = this.quiet; }, + _quite: false, get quiet() this._quiet, set quiet(val) { this._quiet = val; @@ -665,6 +664,8 @@ var CommandLine = Module("commandline", { } }, + _lastEcho: null, + /** * Output the given string onto the command line. With no flags, the * message will be shown in the status line if it's short enough to diff --git a/common/content/commands.js b/common/content/commands.js index 131589e0..3c1a66fb 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -139,8 +139,9 @@ var Command = Class("Command", { * @param {Object} modifiers Any modifiers to be passed to {@link #action}. */ execute: function (args, modifiers) { - if (this.deprecated && !set.add(this.complained, io.sourcing ? io.sourcing.file : "[Command Line]")) { - let loc = io.sourcing ? io.sourcing.file + ":" + io.sourcing.line + ": " : ""; + 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 : "")); } @@ -155,7 +156,7 @@ var Command = Class("Command", { return !dactyl.trapErrors(function exec(command) { if (this.always) this.always(args, modifiers); - if (!io.sourcing || !io.sourcing.noExecute) + if (!contexts.context || !contexts.context.noExecute) this.action(args, modifiers); }, this); }, @@ -271,9 +272,15 @@ var Command = Class("Command", { .toObject(), { __iterator__: function () array.iterItems(this), + command: this, + + get context() contexts.context, + explicitOpts: Class.memoize(function () ({})), + get literalArg() this.command.literal != null && this[this.command.literal] || "", + // TODO: string: Class.memoize(function () { ... }), verify: function verify() { if (this.command.argCount) { @@ -321,51 +328,6 @@ var Command = Class("Command", { */ replacementText: null }, { - bindMacro: function (args, default_, params) { - let process = util.identity; - - if (callable(params)) - var makeParams = function makeParams(self, args) - iter.toObject([k, process(v)] - for ([k, v] in iter(params.apply(self, args)))); - else if (params) - makeParams = function makeParams(self, args) - iter.toObject([name, process(args[i])] - for ([i, name] in Iterator(params))); - - let rhs = args.literalArg; - let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_); - switch (type) { - case "-builtin": - let noremap = true; - /* fallthrough */ - case "-keys": - let silent = args["-silent"]; - rhs = events.canonicalKeys(rhs, true); - var action = function action() events.feedkeys(action.macro(makeParams(this, arguments)), - noremap, silent); - action.macro = util.compileMacro(rhs, true); - break; - case "-ex": - action = function action() commands.execute(action.macro, makeParams(this, arguments), - false, null, action.sourcing); - action.macro = util.compileMacro(rhs, true); - action.sourcing = io.sourcing && update({}, io.sourcing); - break; - case "-javascript": - if (callable(params)) - action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })"); - else - action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg).array); - process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param; - action.makeParams = makeParams; - break; - } - action.toString = function toString() (type === default_ ? "" : type + " ") + rhs; - args = null; - return action; - }, - // TODO: do we really need more than longNames as a convenience anyway? /** * Converts command name abbreviation specs of the form @@ -576,60 +538,62 @@ var Commands = Module("commands", { * interpolated into the command string. * @param {object} args Optional arguments object to be passed to * command actions. - * @param {object} sourcing An object containing information about + * @param {object} context An object containing information about * the file that is being or has been sourced to obtain the * command string. */ - execute: function (string, tokens, silent, args, sourcing) { - io.withSavedValues(["readHeredoc", "sourcing"], function () { - sourcing = sourcing || this.sourcing || { file: "[Command Line]", line: 1 }; - this.sourcing = update({}, sourcing); - - args = update({}, args || {}); - - if (tokens && !callable(string)) - string = util.compileMacro(string, true); - if (callable(string)) - string = string(tokens || {}); - - let lines = string.split(/\r\n|[\r\n]/); - - this.readHeredoc = function (end) { - let res = []; - this.sourcing.line++; - while (++i < lines.length) { - if (lines[i] === end) - return res.join("\n"); - res.push(lines[i]); - } - dactyl.assert(false, "Unexpected end of file waiting for " + end); - }; + execute: function (string, tokens, silent, args, context) { + contexts.withSavedValues(["context"], function () { + context = update({}, context || this.context || { file: "[Command Line]", line: 1 }); + this.context = context; + + io.withSavedValues(["readHeredoc"], function () { + this.readHeredoc = function (end) { + let res = []; + contexts.context.line++; + while (++i < lines.length) { + if (lines[i] === end) + return res.join("\n"); + res.push(lines[i]); + } + dactyl.assert(false, "Unexpected end of file waiting for " + end); + }; - for (var i = 0; i < lines.length && !this.sourcing.finished; i++) { - // Deal with editors from Silly OSs. - let line = lines[i].replace(/\r$/, ""); + args = update({}, args || {}); - this.sourcing.line = sourcing.line + i; + if (tokens && !callable(string)) + string = util.compileMacro(string, true); + if (callable(string)) + string = string(tokens || {}); - // Process escaped new lines - while (i < lines.length && /^\s*\\/.test(lines[i + 1])) - line += "\n" + lines[++i].replace(/^\s*\\/, ""); + let lines = string.split(/\r\n|[\r\n]/); - try { - dactyl.execute(line, args); - } - catch (e) { - if (!silent || silent === "loud") { - if (silent !== "loud") - e.message = this.sourcing.file + ":" + this.sourcing.line + ": " + e.message; - else { - dactyl.echoerr("Error detected while processing " + this.sourcing.file); - dactyl.echomsg("line\t" + this.sourcing.line + ":"); + for (var i = 0; i < lines.length && !context.finished; i++) { + // Deal with editors from Silly OSs. + let line = lines[i].replace(/\r$/, ""); + + context.line = context.line + i; + + // Process escaped new lines + while (i < lines.length && /^\s*\\/.test(lines[i + 1])) + line += "\n" + lines[++i].replace(/^\s*\\/, ""); + + try { + dactyl.execute(line, args); + } + catch (e) { + if (!silent || silent === "loud") { + if (silent !== "loud") + e.message = context.file + ":" + context.line + ": " + e.message; + else { + dactyl.echoerr("Error detected while processing " + context.file); + dactyl.echomsg("line\t" + context.line + ":"); + } + dactyl.reportError(e, true); } - dactyl.reportError(e, true); } } - } + }); }); }, @@ -1172,12 +1136,12 @@ var Commands = Module("commands", { * @param {nsIStackFrame} frame */ getCaller: function (frame) { - if (io.sourcing) + if (contexts.context) return { __proto__: frame, - filename: io.sourcing.file[0] == "[" ? io.sourcing.file : - services.io.newFileURI(File(io.sourcing.file)).spec, - lineNumber: io.sourcing.line + filename: contexts.context.file[0] == "[" ? contexts.context.file : + services.io.newFileURI(File(contexts.context.file)).spec, + lineNumber: contexts.context.line }; return frame; }, @@ -1302,11 +1266,11 @@ var Commands = Module("commands", { if (/^custom,/.test(completer)) { completer = completer.substr(7); - let sourcing = update({}, io.sourcing || {}); + let context = update({}, contexts.context || {}); completerFunc = function (context) { try { - var result = io.withSavedValues(["sourcing"], function () { - io.sourcing = sourcing; + var result = contextswithSavedValues(["context"], function () { + contexts.context = context; return dactyl.userEval(completer); }); } @@ -1328,7 +1292,7 @@ var Commands = Module("commands", { let added = commands.addUserCommand(cmd.split(","), args["-description"], - Command.bindMacro(args, "-ex", + contexts.bindMacro(args, "-ex", function makeParams(args, modifiers) ({ args: { __proto__: args, @@ -1345,7 +1309,7 @@ var Commands = Module("commands", { literal: args["-literal"], persist: !args["-nopersist"], replacementText: args.literalArg, - sourcing: io.sourcing && update({}, io.sourcing) + context: contexts.context && update({}, contexts.context) }, args.bang); if (!added) @@ -1484,62 +1448,6 @@ var Commands = Module("commands", { } }); - function checkStack(cmd) { - util.assert(io.sourcing && io.sourcing.stack && - io.sourcing.stack[cmd] && io.sourcing.stack[cmd].length, - "Invalid use of conditional"); - } - function pop(cmd) { - checkStack(cmd); - return io.sourcing.stack[cmd].pop(); - } - function push(cmd, value) { - util.assert(io.sourcing, "Invalid use of conditional"); - if (arguments.length < 2) - value = io.sourcing.noExecute; - io.sourcing.stack = io.sourcing.stack || {}; - io.sourcing.stack[cmd] = (io.sourcing.stack[cmd] || []).concat([value]); - } - - commands.add(["if"], - "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", - function (args) { io.sourcing.noExecute = !dactyl.userEval(args[0]); }, - { - always: function (args) { push("if"); }, - argCount: "1", - literal: 0 - }); - commands.add(["elsei[f]", "elif"], - "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", - function (args) {}, - { - always: function (args) { - checkStack("if"); - io.sourcing.noExecute = io.sourcing.stack.if.slice(-1)[0] || - !io.sourcing.noExecute || !dactyl.userEval(args[0]); - }, - argCount: "1", - literal: 0 - }); - commands.add(["el[se]"], - "Execute commands until the next :endif only if the previous conditionals were not executed", - function (args) {}, - { - always: function (args) { - checkStack("if"); - io.sourcing.noExecute = io.sourcing.stack.if.slice(-1)[0] || - !io.sourcing.noExecute; - }, - argCount: "0" - }); - commands.add(["en[dif]", "fi"], - "End a string of :if/:elseif/:else conditionals", - function (args) {}, - { - always: function (args) { io.sourcing.noExecute = pop("if"); }, - argCount: "0" - }); - commands.add(["y[ank]"], "Yank the output of the given command to the clipboard", function (args) { diff --git a/common/content/contexts.js b/common/content/contexts.js new file mode 100644 index 00000000..2b81fd02 --- /dev/null +++ b/common/content/contexts.js @@ -0,0 +1,318 @@ +// Copyright (c) 2010-2011 by Kris Maglione <maglione.k@gmail.com> +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +var Group = Class("Group", { + init: function init(name, description, filter, persist) { + const self = this; + this.name = name; + this.description = description; + this.filter = filter || function (uri) true; + this.persist = persist || false; + + this.subGroups = { __proto__: this.subGroups, owner: this }; + this.subGroups.instance = this.subGroups; + }, + + get toStringParams() [this.name], + + get builtin() dactyl.builtinGroups.indexOf(this) >= 0, + + subGroups: {} + +}, { + subGroup: {}, + + subGroups: {}, + + SubGroup: Class("SubGroup", Class.Property, { + init: function init(name, constructor) { + const self = this; + if (this.Group) + return { + enumerable: true, + + get: function () array(contexts.groups[self.name]) + }; + + + this.Group = constructor; + this.name = name; + memoize(Group.prototype.subGroups, name, + function () constructor(this.owner.name, this.owner.description, + this.owner.filter, this.owner.persist)); + + memoize(Group.subGroup, name, + function () Object.create({ _subGroup: name, __proto__: contexts.subGroupProto })); + + memoize(Group.subGroups, name, + function () [g.subGroups[name] for (g in values(this.groups)) if (set.has(g.subGroups, name))]); + } + }) +}); + +var Contexts = Module("contexts", { + init: function () { + this.groupList = []; + this.groupMap = {}; + this.subGroupProto = {}; + this.builtinGroups = [this.addGroup("builtin", "Builtin items"), + this.addGroup("user", "User-defined items", null, true)]; + }, + + context: null, + + groups: Class.memoize(function () ({ + __proto__: Group.subGroups, + groups: this.groupList.filter(function (g) g.filter(buffer.uri)) + })), + + allGroups: Class.memoize(function () ({ + __proto__: Group.subGroups, + groups: this.groupList + })), + + get subGroup() Group.subGroup, + + addGroup: function addGroup(name, description, filter, persist) { + this.removeGroup(name); + + let group = Group(name, description, filter, persist); + this.groupList.unshift(group); + this.groupMap[name] = group; + this.subGroupProto.__defineGetter__(name, function () group.subGroups[this._subGroup]); + return group; + }, + + removeGroup: function removeGroup(name, filter) { + let group = this.getGroup(name); + dactyl.assert(!group || !group.builtin, "Cannot remove builtin group"); + + if (group) + this.groupList.splice(this.groupList.indexOf(group), 1); + + if (this.context && this.context.group === group) + this.context.group = null; + + delete this.groupMap[name]; + delete this.subGroupProto[name]; + delete this.groups; + return group; + }, + + getGroup: function getGroup(name, subGroup) { + let group = array.nth(this.groupList, function (h) h.name == name, 0) || null; + if (group && subGroup) + return group.subGroups[subGroup]; + return group; + }, + + bindMacro: function (args, default_, params) { + let process = util.identity; + + if (callable(params)) + var makeParams = function makeParams(self, args) + iter.toObject([k, process(v)] + for ([k, v] in iter(params.apply(self, args)))); + else if (params) + makeParams = function makeParams(self, args) + iter.toObject([name, process(args[i])] + for ([i, name] in Iterator(params))); + + let rhs = args.literalArg; + let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_); + switch (type) { + case "-builtin": + let noremap = true; + /* fallthrough */ + case "-keys": + let silent = args["-silent"]; + rhs = events.canonicalKeys(rhs, true); + var action = function action() events.feedkeys(action.macro(makeParams(this, arguments)), + noremap, silent); + action.macro = util.compileMacro(rhs, true); + break; + case "-ex": + action = function action() commands.execute(action.macro, makeParams(this, arguments), + false, null, action.context); + action.macro = util.compileMacro(rhs, true); + action.context = this.context && update({}, this.context); + break; + case "-javascript": + if (callable(params)) + action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })"); + else + action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg).array); + process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param; + action.makeParams = makeParams; + break; + } + action.toString = function toString() (type === default_ ? "" : type + " ") + rhs; + args = null; + return action; + } +}, { +}, { + commands: function initCommands() { + + commands.add(["gr[oup]", "mapg[roup]"], + "Create or select a group", + function (args) { + dactyl.assert(args.length <= 2, "Trailing characters"); + + if (args.length == 0) + return void completion.listCompleter("group", ""); + + let name = Option.dequote(args[0]); + dactyl.assert(commands.validName.test(name), "Invalid group name"); + + let group = contexts.getGroup(name); + + if (args.length == 2) { + dactyl.assert(!group || args.bang, "Group exists"); + + let filter = function siteFilter(uri) + siteFilter.filters.every(function (f) f(uri) == f.result); + + update(filter, { + toString: function () this.filters.join(","), + filters: Option.splitList(args[1], true).map(function (pattern) { + let [, res, filter] = /^(!?)(.*)/.exec(pattern); + + return update(Styles.matchFilter(Option.dequote(filter)), { + result: !res, + toString: function () pattern + }); + }) + }); + + group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); + } + + dactyl.assert(group, "No such group: " + name); + dactyl.assert(group.name != "builtin", "Cannot modify builtin group"); + if (args.context) + args.context.group = group; + }, + { + argCount: "*", + bang: true, + completer: function (context, args) { + if (args.length == 1) + completion.group(context); + else { + Option.splitList(context.filter); + context.advance(Option._splitAt); + + context.compare = CompletionContext.Sort.unsorted; + context.completions = [ + [buffer.uri.host, "Current Host"], + [buffer.uri.spec, "Current Page"] + ]; + } + }, + keepQuotes: true, + options: [ + { + names: ["-description", "-desc", "-d"], + description: "A description of this group", + type: CommandOption.STRING + }, + { + names: ["-nopersist", "-n"], + description: "Do not save this group to an auto-generated RC file" + } + ] + }); + + commands.add(["delg[roup]", "delmapg[roup]"], + "Delete a group", + function (args) { + dactyl.assert(contexts.getGroup(args[0]), "No such group: " + args[0]); + contexts.removeGroup(args[0]); + }, + { + argCount: "1", + completer: function (context, args) { + completion.group(context); + context.filters.push(function ({ item }) !item.builtin); + } + }); + + + commands.add(["fini[sh]"], + "Stop sourcing a script file", + function (args) { + dactyl.assert(args.context, "E168: :finish used outside of a sourced file"); + args.context.finished = true; + }, + { argCount: "0" }); + + + function checkStack(cmd) { + util.assert(contexts.context && contexts.context.stack && + contexts.context.stack[cmd] && contexts.context.stack[cmd].length, + "Invalid use of conditional"); + } + function pop(cmd) { + checkStack(cmd); + return contexts.context.stack[cmd].pop(); + } + function push(cmd, value) { + util.assert(contexts.context, "Invalid use of conditional"); + if (arguments.length < 2) + value = contexts.context.noExecute; + contexts.context.stack = contexts.context.stack || {}; + contexts.context.stack[cmd] = (contexts.context.stack[cmd] || []).concat([value]); + } + + commands.add(["if"], + "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", + function (args) { args.context.noExecute = !dactyl.userEval(args[0]); }, + { + always: function (args) { push("if"); }, + argCount: "1", + literal: 0 + }); + commands.add(["elsei[f]", "elif"], + "Execute commands until the next :elseif, :else, or :endif only if the argument returns true", + function (args) {}, + { + always: function (args) { + checkStack("if"); + args.context.noExecute = args.context.stack.if.slice(-1)[0] || + !args.context.noExecute || !dactyl.userEval(args[0]); + }, + argCount: "1", + literal: 0 + }); + commands.add(["el[se]"], + "Execute commands until the next :endif only if the previous conditionals were not executed", + function (args) {}, + { + always: function (args) { + checkStack("if"); + args.context.noExecute = args.context.stack.if.slice(-1)[0] || + !args.context.noExecute; + }, + argCount: "0" + }); + commands.add(["en[dif]", "fi"], + "End a string of :if/:elseif/:else conditionals", + function (args) {}, + { + always: function (args) { args.context.noExecute = pop("if"); }, + argCount: "0" + }); + }, + completion: function initCompletion() { + completion.group = function group(context, modes) { + context.title = ["Group"]; + context.keys = { text: "name", description: function (h) h.description || h.filter }; + context.completions = contexts.groupList.slice(0, -1); + }; + } +}); + diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 1defb6cf..00d0452d 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -12,7 +12,6 @@ default xml namespace = XHTML; XML.ignoreWhitespace = false; XML.prettyPrinting = false; -var plugins = { __proto__: modules }; var userContext = { __proto__: modules }; var _userContext = newContext(userContext); @@ -53,7 +52,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { destroy: function () { autocommands.trigger("LeavePre", {}); - storage.saveAll(); dactyl.triggerObserver("shutdown", null); util.dump("All dactyl modules destroyed\n"); autocommands.trigger("Leave", {}); @@ -337,7 +335,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.reportError(str); if (typeof str == "object" && "echoerr" in str) str = str.echoerr; - else if (isinstance(str, ["Error"])) + else if (isinstance(str, ["Error"]) && str.fileName) str = <>{str.fileName.replace(/^.* -> /, "")}: {str.lineNumber}: {str}</>; if (options["errorbells"]) @@ -395,9 +393,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { if (jsmodules.__proto__ != window) str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }"; + let info = contexts.context; if (fileName == null) - if (io.sourcing && io.sourcing.file[0] !== "[") - ({ file: fileName, line: lineNumber, context: ctxt }) = io.sourcing; + if (info && info.file[0] !== "[") + ({ file: fileName, line: lineNumber, context: ctxt }) = info; else try { if (!context) context = userContext || ctxt; @@ -408,8 +407,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { this.loadScript("resource://dactyl-content/eval.js", context); if (context[EVAL_ERROR]) { try { - context[EVAL_ERROR].fileName = io.sourcing.file; - context[EVAL_ERROR].lineNumber += io.sourcing.line; + context[EVAL_ERROR].fileName = context.file; + context[EVAL_ERROR].lineNumber += context.line; } catch (e) {} throw context[EVAL_ERROR]; @@ -1356,7 +1355,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { reportError: function reportError(error, echo) { if (error instanceof FailedAssertion || error.message === "Interrupted") { - let prefix = io.sourcing ? io.sourcing.file + ":" + io.sourcing.line + ": " : ""; + let context = contexts.context; + let prefix = context ? context.file + ":" + context.line + ": " : ""; if (error.message && error.message.indexOf(prefix) !== 0) error.message = prefix + error.message; diff --git a/common/content/mappings.js b/common/content/mappings.js index dd19bda4..db25c858 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -107,6 +107,9 @@ var Map = Class("Map", { .map(function ([i, prop]) [prop, this[i]], arguments) .toObject(); + if (!args.context) + args.context = contexts.context; + let self = this; function repeat() self.action(args) if (this.names[0] != ".") // FIXME: Kludge. @@ -299,16 +302,15 @@ var MapHive = Class("MapHive", { */ var Mappings = Module("mappings", { init: function () { - this.user = MapHive("user", "User-defined mappings"); - this.builtin = MapHive("builtin", "Builtin mappings"); - - this.builtinHives = array([this.user, this.builtin]); - this.allHives = [this.user, this.builtin]; + this.user = contexts.subGroup.mappings.user; + this.builtin = contexts.subGroup.mappings.builtin; }, repeat: Modes.boundProperty(), - hives: Class.memoize(function () array(this.allHives.filter(function (h) h.filter(buffer.uri)))), + hives: Group.SubGroup("mappings", MapHive), + + get allHives() contexts.allGroups.mappings, get userHives() this.allHives.filter(function (h) h !== this.builtin, this), @@ -365,29 +367,6 @@ var Mappings = Module("mappings", { return map; }, - addHive: function addHive(name, filter, description) { - this.removeHive(name); - - let hive = MapHive(name, description, filter); - this.allHives.unshift(hive); - return hive; - }, - - removeHive: function removeHive(name, filter) { - let hive = this.getHive(name); - dactyl.assert(!hive || !hive.builtin, "Cannot remove builtin group"); - if (hive) - this.allHives.splice(this.allHives.indexOf(hive), 1); - - if (io.sourcing && io.sourcing.mapHive == hive) - io.sourcing.mapHive = null; - - delete this.hives; - return hive; - }, - - getHive: function getHive(name) array.nth(this.allHives, function (h) h.name == name, 0) || null, - /** * Returns the map from *mode* named *cmd*. * @@ -482,7 +461,7 @@ var Mappings = Module("mappings", { else { args["-group"].add(mapmodes, [lhs], args["-description"], - Command.bindMacro(args, "-keys", function (params) params), + contexts.bindMacro(args, "-keys", function (params) params), { arg: args["-arg"], count: args["-count"], @@ -650,96 +629,12 @@ var Mappings = Module("mappings", { }); } - commands.add(["mapg[roup]"], - "Create or select a mapping group", - function (args) { - dactyl.assert(args.length <= 2, "Trailing characters"); - - if (args.length == 0) - return void completion.listCompleter("mapGroup", ""); - - let name = Option.dequote(args[0]); - let hive = mappings.getHive(name); - - if (args.length == 2) { - dactyl.assert(!hive || args.bang, "Group exists"); - - let filter = function siteFilter(uri) - siteFilter.filters.every(function (f) f(uri) == f.result); - - update(filter, { - toString: function () this.filters.join(","), - filters: Option.splitList(args[1], true).map(function (pattern) { - let [, res, filter] = /^(!?)(.*)/.exec(pattern); - - return update(Styles.matchFilter(Option.dequote(filter)), { - result: !res, - toString: function () pattern - }); - }) - }); - - hive = mappings.addHive(name, filter, args["-description"]); - if (args["-nopersist"]) - hive.noPersist = true; - } - - dactyl.assert(hive, "No mapping group: " + name); - dactyl.assert(hive.name != "builtin", "Can't map to builtin hive"); - if (io.sourcing) - io.sourcing.mapHive = hive; - }, - { - argCount: "*", - bang: true, - completer: function (context, args) { - if (args.length == 1) - completion.mapGroup(context); - else { - Option.splitList(context.filter); - context.advance(Option._splitAt); - - context.compare = CompletionContext.Sort.unsorted; - context.completions = [ - [buffer.uri.host, "Current Host"], - [buffer.uri.spec, "Current Page"] - ]; - } - }, - keepQuotes: true, - options: [ - { - names: ["-description", "-desc", "-d"], - description: "A description of this mapping group", - type: CommandOption.STRING - }, - { - names: ["-nopersist", "-n"], - description: "Do not save this mapping group to an auto-generated RC file" - } - ] - }); - - commands.add(["delmapg[roup]"], - "Delete a mapping group", - function (args) { - dactyl.assert(mappings.getHive(args[0]), "No mapping group: " + args[0]); - mappings.removeHive(args[0]); - }, - { - argCount: "1", - completer: function (context, args) { - completion.mapGroup(context); - context.filters.push(function ({ item }) !item.builtin); - } - }); - let groupFlag = { names: ["-group", "-g"], description: "Mapping group to which to add this mapping", - type: ArgType("map-group", function (group) isString(group) ? mappings.getHive(group) : group), - get default() io.sourcing && io.sourcing.mapHive || mappings.user, - completer: function (context) completion.mapGroup(context) + type: ArgType("map-group", function (group) isString(group) ? contexts.getGroup(group, "mappings") : group), + get default() (contexts.context && contexts.context.group || contexts.user).subGroups.mappings, + completer: function (context) completion.group(context) }; let modeFlag = { names: ["-mode", "-m"], @@ -848,11 +743,6 @@ var Mappings = Module("mappings", { [mode.name.toLowerCase()]); }, completion: function () { - completion.mapGroup = function mapGroup(context, modes) { - context.title = ["Map group"]; - context.keys = { text: "name", description: function (h) h.description || h.filter }; - context.completions = mappings.userHives; - }; completion.userMapping = function userMapping(context, modes, hive) { // FIXME: have we decided on a 'standard' way to handle this clash? --djk hive = hive || mappings.user; @@ -862,7 +752,7 @@ var Mappings = Module("mappings", { }; }, javascript: function () { - JavaScript.setCompleter(this.get, + JavaScript.setCompleter(mappings.get, [ null, function (context, obj, args) { diff --git a/common/content/mow.js b/common/content/mow.js index 1cea5fb5..8a130fbb 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -4,6 +4,7 @@ // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. +"use strict"; var MOW = Module("mow", { init: function () { @@ -71,7 +72,6 @@ var MOW = Module("mow", { if (!commandline.commandVisible) commandline.hide(); - this._startHints = false; if (modes.main != modes.OUTPUT_MULTILINE) { modes.push(modes.OUTPUT_MULTILINE, null, { onKeyPress: this.closure.onKeyPress, @@ -170,6 +170,7 @@ var MOW = Module("mow", { event.preventDefault(); } }, + contextEvents: { popupshowing: function (event) { let menu = commandline.widgets.contextMenu; @@ -261,8 +262,7 @@ var MOW = Module("mow", { if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow) document.commandDispatcher.focusedWindow = content; } - }), - + }) }, { }, { mappings: function () { @@ -277,7 +277,7 @@ var MOW = Module("mow", { mow.echo(mow.lastOutput, "Normal"); }); - bind = function bind(keys, description, action, test, default_) { + let bind = function bind(keys, description, action, test, default_) { mappings.add([modes.OUTPUT_MULTILINE], keys, description, function (command) { diff --git a/common/content/options.js b/common/content/options.js index de3a713c..5ac9218e 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -1295,7 +1295,7 @@ var Options = Module("options", { }; }, javascript: function () { - JavaScript.setCompleter(this.get, [function () ([o.name, o.description] for (o in options))]); + JavaScript.setCompleter(options.get, [function () ([o.name, o.description] for (o in options))]); }, sanitizer: function () { sanitizer.addItem("options", { diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index 9e0d04fb..3824f149 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -35,12 +35,12 @@ var AddonListener = Class("AddonListener", { onExternalInstall: function (addon, existingAddon, needsRestart) {}, onDownloadStarted: listener("download", "started"), onDownloadEnded: listener("download", "complete"), - onDownloadCancelled: listener("download", "cancelled"), + onDownloadCancelled: listener("download", "canceled"), onDownloadFailed: listener("download", "failed"), onDownloadProgress: function (install) {}, onInstallStarted: function (install) {}, onInstallEnded: listener("installation", "complete"), - onInstallCancelled: listener("installation", "cancelled"), + onInstallCancelled: listener("installation", "canceled"), onInstallFailed: listener("installation", "failed") }); diff --git a/common/modules/base.jsm b/common/modules/base.jsm index c3da6d2c..f147ebf1 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -616,12 +616,12 @@ function update(target) { if (desc.value instanceof Class.Property) desc = desc.value.init(k) || desc.value; if (typeof desc.value == "function" && Object.getPrototypeOf(target)) { - let func = desc.value; - desc.value.__defineGetter__("super", function () Object.getPrototypeOf(target)[k]); - desc.value.superapply = function superapply(self, args) + 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); - desc.value.supercall = function supercall(self) + func.supercall = function supercall(self) func.superapply(self, Array.slice(arguments, 1)); } Object.defineProperty(target, k, desc); @@ -663,7 +663,7 @@ function Class() { superclass = args.shift(); var Constructor = eval(String.replace(<![CDATA[ - (function constructor() { + (function constructor(PARAMS) { var self = Object.create(Constructor.prototype, { constructor: { value: Constructor }, }); @@ -671,7 +671,9 @@ function Class() { var res = self.init.apply(self, arguments); return res !== undefined ? res : self; })]]>, - "constructor", (name || superclass.className).replace(/\W/g, "_"))); + "constructor", (name || superclass.className).replace(/\W/g, "_")) + .replace("PARAMS", /^function .*?\((.*?)\)/.exec(args[0] && args[0].init || Class.prototype.init)[1])); + Constructor.className = name || superclass.className || superclass.name; if ("init" in superclass.prototype) diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 3e85b02d..e0fdc00f 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -546,7 +546,7 @@ var IO = Module("io", { PATH_SEP: deprecated("File.PATH_SEP", { get: function PATH_SEP() File.PATH_SEP }) }, { init: function init(dactyl, modules, window) { - modules.plugins.contexts = {}; + Class.replaceProperty(modules.plugins, "contexts", {}); modules.Script = function Script(file) { const { io, plugins } = modules; @@ -615,15 +615,6 @@ var IO = Module("io", { literal: 0 }); - // NOTE: this command is only used in :source - commands.add(["fini[sh]"], - "Stop sourcing a script file", - function () { - dactyl.assert(io.sourcing, "E168: :finish used outside of a sourced file"); - io.sourcing.finished = true; - }, - { argCount: "0" }); - commands.add(["pw[d]"], "Print the current directory name", function () { dactyl.echomsg(io.cwd.path); }, diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 7b73be7c..45aaf449 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -144,6 +144,7 @@ var Overlay = Module("Overlay", { return sandbox; } }); + modules.plugins = create(modules); modules.modules = modules; window.dactyl = { modules: modules }; @@ -163,12 +164,13 @@ var Overlay = Module("Overlay", { "util" ].forEach(function (name) defineModule.time("load", name, require, null, jsmodules, name)); - ["dactyl", + ["contexts", + "dactyl", "modes", + "commandline", "abbreviations", "autocommands", "buffer", - "commandline", "commands", "editor", "events", diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index b41e86cf..64a7d7f2 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -152,6 +152,8 @@ var Storage = Module("Storage", { }, cleanup: function () { + this.saveAll(); + for (let key in keys(this.keys)) { if (this[key].timer) this[key].timer.flush(); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 6b423c66..9bf6e2ee 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -32,8 +32,8 @@ memoize(this, "Commands", function () { var FailedAssertion = Class("FailedAssertion", ErrorBase); var Point = Struct("x", "y"); -var wrapCallback = function wrapCallback(fn) - fn.wrapper = (function wrappedCallback () { +var wrapCallback = function wrapCallback(fn) { + fn.wrapper = function wrappedCallback () { try { return fn.apply(this, arguments); } @@ -41,7 +41,10 @@ var wrapCallback = function wrapCallback(fn) util.reportError(e); return undefined; } - }) + }; + fn.wrapper.wrapped = fn; + return fn.wrapper; +} var getAttr = function getAttr(elem, ns, name) elem.hasAttributeNS(ns, name) ? elem.getAttributeNS(ns, name) : null; @@ -1585,6 +1588,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), })(); }, + wrapCallback: wrapCallback, + /** * Traps errors in the called function, possibly reporting them. * |