From 9b3ad1c7c6b2a8afd11ea6a392e38666c5aafd01 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:02:36 -0500 Subject: [PATCH 01/52] imported patch groups --HG-- branch : groups --- common/content/abbreviations.js | 2 +- common/content/autocommands.js | 4 +- common/content/buffer.js | 43 +++-- common/content/commandline.js | 21 ++- common/content/commands.js | 214 ++++++--------------- common/content/contexts.js | 318 ++++++++++++++++++++++++++++++++ common/content/dactyl.js | 16 +- common/content/mappings.js | 136 ++------------ common/content/mow.js | 8 +- common/content/options.js | 2 +- common/modules/addons.jsm | 4 +- common/modules/base.jsm | 14 +- common/modules/io.jsm | 11 +- common/modules/overlay.jsm | 6 +- common/modules/storage.jsm | 2 + common/modules/util.jsm | 11 +- 16 files changed, 468 insertions(+), 344 deletions(-) create mode 100644 common/content/contexts.js 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); + execute: function (string, tokens, silent, args, context) { + contexts.withSavedValues(["context"], function () { + context = update({}, context || this.context || { file: "[Command Line]", line: 1 }); + this.context = context; - args = update({}, args || {}); + 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); + }; - if (tokens && !callable(string)) - string = util.compileMacro(string, true); - if (callable(string)) - string = string(tokens || {}); + args = update({}, args || {}); - let lines = string.split(/\r\n|[\r\n]/); + if (tokens && !callable(string)) + string = util.compileMacro(string, true); + if (callable(string)) + string = string(tokens || {}); - 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); - }; + let lines = string.split(/\r\n|[\r\n]/); - for (var i = 0; i < lines.length && !this.sourcing.finished; i++) { - // Deal with editors from Silly OSs. - let line = lines[i].replace(/\r$/, ""); + for (var i = 0; i < lines.length && !context.finished; i++) { + // Deal with editors from Silly OSs. + let line = lines[i].replace(/\r$/, ""); - this.sourcing.line = sourcing.line + i; + context.line = context.line + i; - // Process escaped new lines - while (i < lines.length && /^\s*\\/.test(lines[i + 1])) - line += "\n" + lines[++i].replace(/^\s*\\/, ""); + // 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 = 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 + ":"); + 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 +// +// 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(, - "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. * From 3b5f2fc07f043f49faab05ca1fe3126343977cdc Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:06:54 -0500 Subject: [PATCH 02/52] Fix problem with last commit. --HG-- branch : groups --- common/content/dactyl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 00d0452d..a9ea5907 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -407,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 = context.file; - context[EVAL_ERROR].lineNumber += context.line; + context[EVAL_ERROR].fileName = info.file; + context[EVAL_ERROR].lineNumber += info.line; } catch (e) {} throw context[EVAL_ERROR]; From 01935dbb6415f80443bae9552cf1b96591726d4a Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:37:20 -0500 Subject: [PATCH 03/52] Fix failing tests. --HG-- branch : groups --- common/content/buffer.js | 13 ++++--------- common/content/contexts.js | 8 +++++--- common/content/history.js | 2 +- common/modules/sanitizer.jsm | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/common/content/buffer.js b/common/content/buffer.js index 5388e226..88c94528 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -306,15 +306,10 @@ var Buffer = Module("buffer", { statusline.updateUrl(message); }), 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); - } + onProgressChange.superapply(this, arguments); + if (webProgress && webProgress.DOMWindow) + webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress; + statusline.progress = curTotalProgress / maxTotalProgress; }), // happens when the users switches tabs onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) { diff --git a/common/content/contexts.js b/common/content/contexts.js index 2b81fd02..61be2411 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -18,7 +18,7 @@ var Group = Class("Group", { get toStringParams() [this.name], - get builtin() dactyl.builtinGroups.indexOf(this) >= 0, + get builtin() contexts.builtinGroups.indexOf(this) >= 0, subGroups: {} @@ -58,8 +58,10 @@ var Contexts = Module("contexts", { this.groupList = []; this.groupMap = {}; this.subGroupProto = {}; - this.builtinGroups = [this.addGroup("builtin", "Builtin items"), - this.addGroup("user", "User-defined items", null, true)]; + + this.system = this.addGroup("builtin", "Builtin items"); + this.user = this.addGroup("user", "User-defined items", null, true); + this.builtinGroups = [this.system, this.user]; }, context: null, diff --git a/common/content/history.js b/common/content/history.js index 980dccb1..b768a02c 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -250,7 +250,7 @@ var History = Module("history", { context.generate = function () [ Array.slice(row.rev_host).reverse().join("").slice(1) for (row in iter(services.history.DBConnection - .createStatement("SELECT DISTINCT rev_host FROM moz_places;"))) + .createStatement("SELECT DISTINCT rev_host FROM moz_places WHERE rev_host IS NOT NULL;"))) ].slice(2); }; diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index 5b6be968..4fe875f9 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -434,7 +434,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef bang: true, completer: function (context) { context.title = ["Privacy Item", "Description"]; - context.completions = modules.options.get("sanitizeitems").completer(); + context.completions = modules.options.get("sanitizeitems").values; }, domains: function (args) args["-host"] || [], options: [ From 1f02fedfb33dfa2b1cf286625676f99b531778d2 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:49:44 -0500 Subject: [PATCH 04/52] Minor changes. --HG-- branch : groups --- common/modules/downloads.jsm | 8 ++++++-- common/modules/sanitizer.jsm | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index 234028e2..5a9a499b 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -106,9 +106,13 @@ var Download = Class("Download", { let file = io.File(this.targetFile); if (file.isExecutable() && prefs.get("browser.download.manager.alertOnEXEOpen", true)) - this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]) ", + this.list.modules.commandline.input("This will launch an executable download. Continue? (yes/[no]/always) ", function (resp) { - if (resp && resp.match(/^y(es)?$/i)) + if (/^a(lways)$/i.test(resp)) { + prefs.set("browser.download.manager.alertOnEXEOpen", false); + resp = "yes"; + } + if (/^y(es)?$/i.test(resp)) action.call(self); }); else diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index 4fe875f9..26837d54 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -601,12 +601,12 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef context.completions = this.values; }, values: [ - ["all", "Everything"], - ["session", "The current session"], - ["10m", "Last ten minutes"], - ["1h", "Past hour"], - ["1d", "Past day"], - ["1w", "Past week"] + ["all", "Everything"], + ["session", "The current session"], + ["10m", "Last ten minutes"], + ["1h", "Past hour"], + ["1d", "Past day"], + ["1w", "Past week"] ], validator: function (value) /^(a(ll)?|s(ession)|\d+[mhdw])$/.test(value) }); From e0a483d46a63142b4a029a4014e63aadeb8358a4 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:56:48 -0500 Subject: [PATCH 05/52] Fix small completion.addonType issue. --HG-- branch : groups --- common/modules/addons.jsm | 4 +--- common/modules/downloads.jsm | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index 3824f149..e30129d6 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -437,10 +437,8 @@ var Addons = Module("addons", { context.completions = types.map(function (t) [t, util.capitalize(t)]); } - if (AddonManager.getAllAddons) - context.incomplete = true; - context.generate = function generate() { + context.incomplete = true; update(base); if (AddonManager.getAllAddons) AddonManager.getAllAddons(function (addons) { diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index 5a9a499b..a2d00a14 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -148,10 +148,7 @@ var Download = Class("Download", { updateStatus: function updateStatus() { - if (this.alive) - this.nodes.row.setAttribute("active", "true"); - else - this.nodes.row.removeAttribute("active"); + this.nodes.row[this.alive ? "setAttribute" : "removeAttribute"]("active", "true"); this.nodes.row.setAttribute("status", this.status); this.nodes.state.textContent = util.capitalize(this.status); From ba0b5bdd9e0c3285a6065bc6f4b1f2e2d6debacb Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 03:57:29 -0500 Subject: [PATCH 06/52] Fix small completion.addonType issue on FF36. --HG-- branch : groups --- common/modules/addons.jsm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index e30129d6..ca3dde2c 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -438,14 +438,15 @@ var Addons = Module("addons", { } context.generate = function generate() { - context.incomplete = true; update(base); - if (AddonManager.getAllAddons) + if (AddonManager.getAllAddons) { + context.incomplete = true; AddonManager.getAllAddons(function (addons) { context.incomplete = false; update(array.uniq(base.concat(addons.map(function (a) a.type)), true)); }); + } } } From dd583dba9c571dd58da777e64caaeb94540b83f5 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 04:22:05 -0500 Subject: [PATCH 07/52] Simplify some things that were decidedly unsimple. --HG-- branch : groups --- common/content/contexts.js | 15 ++++++--------- common/content/mappings.js | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 61be2411..5f785e6b 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -11,9 +11,6 @@ var Group = Class("Group", { 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], @@ -40,15 +37,15 @@ var Group = Class("Group", { 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.prototype, name, + function () constructor(this.name, this.description, + this.filter, this.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))]); + function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); } }) }); @@ -84,7 +81,7 @@ var Contexts = Module("contexts", { let group = Group(name, description, filter, persist); this.groupList.unshift(group); this.groupMap[name] = group; - this.subGroupProto.__defineGetter__(name, function () group.subGroups[this._subGroup]); + this.subGroupProto.__defineGetter__(name, function () group[this._subGroup]); return group; }, @@ -107,7 +104,7 @@ var Contexts = Module("contexts", { 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[subGroup]; return group; }, diff --git a/common/content/mappings.js b/common/content/mappings.js index db25c858..dac1e459 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -633,7 +633,7 @@ var Mappings = Module("mappings", { names: ["-group", "-g"], description: "Mapping group to which to add this mapping", type: ArgType("map-group", function (group) isString(group) ? contexts.getGroup(group, "mappings") : group), - get default() (contexts.context && contexts.context.group || contexts.user).subGroups.mappings, + get default() (contexts.context && contexts.context.group || contexts.user).mappings, completer: function (context) completion.group(context) }; let modeFlag = { From 5d6f8833f53de42c68f5ff5845e9e68a3578f18d Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 04:28:31 -0500 Subject: [PATCH 08/52] Ameliorate some naming issues. --HG-- branch : groups --- common/content/contexts.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 5f785e6b..8fc22bd6 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -20,9 +20,9 @@ var Group = Class("Group", { subGroups: {} }, { - subGroup: {}, + groupsProto: {}, - subGroups: {}, + subGroupMap: {}, SubGroup: Class("SubGroup", Class.Property, { init: function init(name, constructor) { @@ -41,10 +41,11 @@ var Group = Class("Group", { function () constructor(this.name, this.description, this.filter, this.persist)); - memoize(Group.subGroup, name, - function () Object.create({ _subGroup: name, __proto__: contexts.subGroupProto })); + memoize(Group.subGroupMap, name, + function () Object.create(Object.create(contexts.subGroupProto, + { _subGroup: { value: name } }))); - memoize(Group.subGroups, name, + memoize(Group.groupsProto, name, function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); } }) @@ -63,17 +64,15 @@ var Contexts = Module("contexts", { context: null, - groups: Class.memoize(function () ({ - __proto__: Group.subGroups, - groups: this.groupList.filter(function (g) g.filter(buffer.uri)) + groups: Class.memoize(function () Object.create(Group.groupsProto, { + groups: { value: this.groupList.filter(function (g) g.filter(buffer.uri)) } })), - allGroups: Class.memoize(function () ({ - __proto__: Group.subGroups, - groups: this.groupList + allGroups: Class.memoize(function () Object.create(Group.groupsProto, { + groups: { value: this.groupList } })), - get subGroup() Group.subGroup, + get subGroup() Group.subGroupMap, addGroup: function addGroup(name, description, filter, persist) { this.removeGroup(name); From f06e7075eee908684a1fb89993e0fc4eedda4ddc Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 04:39:33 -0500 Subject: [PATCH 09/52] Standardize misspellings of the 'class' reserved word. --HG-- branch : groups --- common/content/tabs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/content/tabs.js b/common/content/tabs.js index 5b129953..13ec4df4 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -43,7 +43,7 @@ var Tabs = Module("tabs", { cleanup: function cleanup() { for (let [i, tab] in Iterator(this.allTabs)) { - let node = function node(clas) document.getAnonymousElementByAttribute(tab, "class", clas); + let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_); for (let elem in values(["dactyl-tab-icon-number", "dactyl-tab-number"].map(node))) if (elem) elem.parentNode.parentNode.removeChild(elem.parentNode); @@ -53,7 +53,7 @@ var Tabs = Module("tabs", { updateTabCount: function () { for (let [i, tab] in Iterator(this.visibleTabs)) { if (dactyl.has("Gecko2")) { - let node = function node(clas) document.getAnonymousElementByAttribute(tab, "class", clas); + let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_); if (!node("dactyl-tab-number")) { let img = node("tab-icon-image"); if (img) { From 020ad9819717c0ec1a74ca151a9e33320b0dab61 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 05:18:57 -0500 Subject: [PATCH 10/52] Create 'group's for sourced scripts. --HG-- branch : groups --- common/content/contexts.js | 51 ++++++++++++++++++++++++++++-- common/content/dactyl.js | 8 ++--- common/modules/io.jsm | 64 +++++++++++++------------------------- 3 files changed, 74 insertions(+), 49 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 8fc22bd6..f674b3bc 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -51,6 +51,8 @@ var Group = Class("Group", { }) }); +plugins.contexts = {}; + var Contexts = Module("contexts", { init: function () { this.groupList = []; @@ -72,6 +74,10 @@ var Contexts = Module("contexts", { groups: { value: this.groupList } })), + activeGroups: function (subgroup) + let (need = subgroup ? [subgroup] : Object.keys(this.subGroup)) + this.groupList.filter(function (group) need.some(function (name) set.has(group, name))), + get subGroup() Group.subGroupMap, addGroup: function addGroup(name, description, filter, persist) { @@ -152,6 +158,44 @@ var Contexts = Module("contexts", { return action; } }, { + Context: modules.Script = function Context(file, group, args) { + let isPlugin = io.getRuntimeDirectories("plugins") + .some(function (dir) dir.contains(file, false)) + + let self = set.has(plugins, file.path) && plugins[file.path]; + if (self) { + if (set.has(self, "onUnload")) + self.onUnload(); + } + else { + self = update(modules.newContext.apply(null, args || [userContext]), { + NAME: file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()), + PATH: file.path, + CONTEXT: self + }); + Class.replaceProperty(plugins, file.path, self); + + // This belongs elsewhere + if (isPlugin && args) + Object.defineProperty(plugins, self.NAME, { + configurable: true, + enumerable: true, + value: self, + writeable: false + }); + } + + self.GROUP = group || + contexts.addGroup(isPlugin ? "plugin-" + self.NAME + : "script-" + array(commands.nameRegexp.iterate(file.path)).join("-"), + "Script group for " + file.path, + null, false); + + return plugins.contexts[file.path] = self; + }, + Script: function Script(file, group) { + return this.Context(file, group, [plugins, true]); + } }, { commands: function initCommands() { @@ -161,7 +205,7 @@ var Contexts = Module("contexts", { dactyl.assert(args.length <= 2, "Trailing characters"); if (args.length == 0) - return void completion.listCompleter("group", ""); + return void completion.listCompleter("group", "", null, null); let name = Option.dequote(args[0]); dactyl.assert(commands.validName.test(name), "Invalid group name"); @@ -306,10 +350,11 @@ var Contexts = Module("contexts", { }); }, completion: function initCompletion() { - completion.group = function group(context, modes) { + completion.group = function group(context, active) { context.title = ["Group"]; context.keys = { text: "name", description: function (h) h.description || h.filter }; - context.completions = contexts.groupList.slice(0, -1); + context.completions = (active === undefined ? contexts.groupList : contexts.activeGroups(active)) + .slice(0, -1); }; } }); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index a9ea5907..e889769c 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1040,7 +1040,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dir.readDirectory(true).forEach(function (file) { if (file.isFile() && loadplugins.getKey(file.path) && !(file.path in dactyl.pluginFiles)) { try { - io.source(file.path, false); + io.source(file.path); dactyl.pluginFiles[file.path] = true; } catch (e) { @@ -2075,14 +2075,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { if (dactyl.commandLineOptions.rcFile) { let filename = dactyl.commandLineOptions.rcFile; if (!/^(NONE|NORC)$/.test(filename)) - io.source(io.File(filename).path, false); // let io.source handle any read failure like Vim + io.source(io.File(filename).path, { group: contexts.user }); } else { if (init) dactyl.execute(init); else { if (rcFile) { - io.source(rcFile.path, false); + io.source(rcFile.path, { group: contexts.user }); services.environment.set("MY_" + config.idName + "RC", rcFile.path); } else @@ -2092,7 +2092,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { if (options["exrc"] && !dactyl.commandLineOptions.rcFile) { let localRCFile = io.getRCFile(io.cwd); if (localRCFile && !localRCFile.equals(rcFile)) - io.source(localRCFile.path, false); + io.source(localRCFile.path, { group: contexts.user }); } } diff --git a/common/modules/io.jsm b/common/modules/io.jsm index e0fdc00f..3354ddd9 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -29,7 +29,7 @@ var IO = Module("io", { this.config = config; }, - Local: function (dactyl, modules, window) let ({ Script, io, plugins } = modules) ({ + Local: function (dactyl, modules, window) let ({ io, plugins } = modules) ({ init: function init() { this.config = modules.config; @@ -144,18 +144,23 @@ var IO = Module("io", { * Reads Ex commands, JavaScript or CSS from *filename*. * * @param {string} filename The name of the file to source. - * @param {boolean} silent Whether errors should be reported. + * @param {object} params Extra parameters: + * group: The group in which to execute commands. + * silent: Whether errors should not be reported. */ - source: function source(filename, silent) { + source: function source(filename, params) { + const { Contexts, contexts } = modules; defineModule.loadLog.push("sourcing " + filename); + params = params || {}; + let time = Date.now(); - this.withSavedValues(["sourcing"], function _source() { - this.sourcing = null; + contexts.withSavedValues(["context"], function _source() { + contexts.context = null; try { var file = util.getFile(filename) || io.File(filename); if (!file.exists() || !file.isReadable() || file.isDirectory()) { - if (!silent) + if (!params.silent) dactyl.echoerr("E484: Can't open file " + filename.quote()); return; } @@ -167,7 +172,7 @@ var IO = Module("io", { // handle pure JavaScript files specially if (/\.js$/.test(filename)) { try { - dactyl.loadScript(uri.spec, Script(file)); + dactyl.loadScript(uri.spec, Contexts.Script(file, params.group)); dactyl.helpInitialized = false; } catch (e) { @@ -185,10 +190,14 @@ var IO = Module("io", { else if (/\.css$/.test(filename)) styles.registerSheet(uri.spec, false, true); else { - if (!(file.path in plugins)) - plugins[file.path] = modules.newContext(modules.userContext); - modules.commands.execute(file.read(), null, silent || "loud", null, - { file: file.path, line: 1, context: plugins[file.path] }); + let context = Contexts.Context(file, params.group); + modules.commands.execute(file.read(), null, params.silent || "loud", + null, { + context: context, + file: file.path, + group: context.GROUP, + line: 1 + }); } if (this._scriptNames.indexOf(file.path) == -1) @@ -202,13 +211,13 @@ var IO = Module("io", { if (!(e instanceof FailedAssertion)) dactyl.reportError(e); let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); - if (!silent) + if (!params.silent) dactyl.echoerr(message); } finally { defineModule.loadLog.push("done sourcing " + filename + ": " + (Date.now() - time) + "ms"); } - }); + }, this); }, }), @@ -545,35 +554,6 @@ var IO = Module("io", { */ PATH_SEP: deprecated("File.PATH_SEP", { get: function PATH_SEP() File.PATH_SEP }) }, { - init: function init(dactyl, modules, window) { - Class.replaceProperty(modules.plugins, "contexts", {}); - modules.Script = function Script(file) { - const { io, plugins } = modules; - - let self = set.has(plugins, file.path) && plugins[file.path]; - if (self) { - if (set.has(self, "onUnload")) - self.onUnload(); - } - else { - self = update(modules.newContext(plugins, true), { - NAME: file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()), - PATH: file.path, - CONTEXT: self - }); - Class.replaceProperty(plugins, file.path, self); - - // This belongs elsewhere - if (io.getRuntimeDirectories("plugins").some( - function (dir) dir.contains(file, false))) - Class.replaceProperty(plugins, self.NAME, self); - } - plugins.contexts[file.path] = self; - return self; - } - - init.superapply(this, arguments); - }, commands: function (dactyl, modules, window) { const { commands, completion, io } = modules; From 4d7794d888f3453194a4e34f68451ceed7200881 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 05:22:05 -0500 Subject: [PATCH 11/52] Don't duplicate group properties in subgroups. --HG-- branch : groups --- common/content/contexts.js | 4 +--- common/content/mappings.js | 10 ++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index f674b3bc..1f3b63cb 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -37,9 +37,7 @@ var Group = Class("Group", { this.Group = constructor; this.name = name; - memoize(Group.prototype, name, - function () constructor(this.name, this.description, - this.filter, this.persist)); + memoize(Group.prototype, name, function () constructor(this)); memoize(Group.subGroupMap, name, function () Object.create(Object.create(contexts.subGroupProto, diff --git a/common/content/mappings.js b/common/content/mappings.js index dac1e459..3930400c 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -138,16 +138,14 @@ var Map = Class("Map", { }); var MapHive = Class("MapHive", { - init: function init(name, description, filter) { - this.name = name; + init: function init(group) { + this.group = group; this.stacks = {}; - this.description = description; - this.filter = filter || function (uri) true; }, - get toStringParams() [this.name], + get toStringParams() [this.group.name], - get builtin() mappings.builtinHives.indexOf(this) >= 0, + get builtin() this.group.builtin, /** * Iterates over all mappings present in all of the given *modes*. From 60059bcbc3c79d569a47234c2a9ff09851dfce52 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 05:31:13 -0500 Subject: [PATCH 12/52] Constify certain script global properties. Provide 'unload' method. --HG-- branch : groups --- common/content/contexts.js | 17 ++++++++++++++--- common/modules/io.jsm | 8 +++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 1f3b63cb..d24be85f 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -157,6 +157,8 @@ var Contexts = Module("contexts", { } }, { Context: modules.Script = function Context(file, group, args) { + function Const(val) Class.Property({ enumerable: true, value: val }); + let isPlugin = io.getRuntimeDirectories("plugins") .some(function (dir) dir.contains(file, false)) @@ -167,9 +169,18 @@ var Contexts = Module("contexts", { } else { self = update(modules.newContext.apply(null, args || [userContext]), { - NAME: file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()), - PATH: file.path, - CONTEXT: self + NAME: Const(file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())), + PATH: Const(file.path), + CONTEXT: Const(self), + unload: Const(function unload() { + if (plugins[this.NAME] === this || plugins[this.PATH] === this) + if (this.onUnload) + this.onUnload(); + if (plugins[this.NAME] === this) + delete plugins[this.NAME]; + if (plugins[this.PATH] === this) + delete plugins[this.PATH]; + }) }); Class.replaceProperty(plugins, file.path, self); diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 3354ddd9..4603141e 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -154,7 +154,7 @@ var IO = Module("io", { params = params || {}; let time = Date.now(); - contexts.withSavedValues(["context"], function _source() { + return contexts.withSavedValues(["context"], function _source() { contexts.context = null; try { var file = util.getFile(filename) || io.File(filename); @@ -172,7 +172,8 @@ var IO = Module("io", { // handle pure JavaScript files specially if (/\.js$/.test(filename)) { try { - dactyl.loadScript(uri.spec, Contexts.Script(file, params.group)); + var context = Contexts.Script(file, params.group); + dactyl.loadScript(uri.spec, context); dactyl.helpInitialized = false; } catch (e) { @@ -190,7 +191,7 @@ var IO = Module("io", { else if (/\.css$/.test(filename)) styles.registerSheet(uri.spec, false, true); else { - let context = Contexts.Context(file, params.group); + context = Contexts.Context(file, params.group); modules.commands.execute(file.read(), null, params.silent || "loud", null, { context: context, @@ -206,6 +207,7 @@ var IO = Module("io", { dactyl.echomsg("finished sourcing " + filename.quote(), 2); dactyl.log("Sourced: " + filename, 3); + return context; } catch (e) { if (!(e instanceof FailedAssertion)) From 5429db5c53924556a630fb5723ff99f516a600ca Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 05:39:40 -0500 Subject: [PATCH 13/52] Implement some cleanup code for groups. --HG-- branch : groups --- common/content/contexts.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index d24be85f..1903a8fa 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -11,6 +11,13 @@ var Group = Class("Group", { this.description = description; this.filter = filter || function (uri) true; this.persist = persist || false; + this.subGroups = []; + }, + + cleanup: function cleanup() { + for (let subGroup in values(this.subGroups)) + if (subGroup.cleanup) + subGroup.cleanup(); }, get toStringParams() [this.name], @@ -37,7 +44,11 @@ var Group = Class("Group", { this.Group = constructor; this.name = name; - memoize(Group.prototype, name, function () constructor(this)); + memoize(Group.prototype, name, function () { + let group = constructor(this); + this.subGroups.push(group); + return group; + }); memoize(Group.subGroupMap, name, function () Object.create(Object.create(contexts.subGroupProto, @@ -89,11 +100,20 @@ var Contexts = Module("contexts", { }, removeGroup: function removeGroup(name, filter) { + if (isObject(name)) { + if (this.groupList.indexOf(name) === -1) + return; + name = name.name; + } + let group = this.getGroup(name); + dactyl.assert(!group || !group.builtin, "Cannot remove builtin group"); - if (group) + if (group) { this.groupList.splice(this.groupList.indexOf(group), 1); + group.cleanup(); + } if (this.context && this.context.group === group) this.context.group = null; @@ -180,6 +200,7 @@ var Contexts = Module("contexts", { delete plugins[this.NAME]; if (plugins[this.PATH] === this) delete plugins[this.PATH]; + contexts.removeGroup(this.GROUP); }) }); Class.replaceProperty(plugins, file.path, self); @@ -189,8 +210,7 @@ var Contexts = Module("contexts", { Object.defineProperty(plugins, self.NAME, { configurable: true, enumerable: true, - value: self, - writeable: false + value: self }); } From 6ee586087a2687f1f04bf8016b4d0a9452add18e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 07:30:46 -0500 Subject: [PATCH 14/52] Use current group to select :style hive. Clear previous colorscheme's styles on :colo. --HG-- branch : groups --- common/content/contexts.js | 18 +++++++++++++++--- common/content/mappings.js | 13 +++---------- common/modules/highlight.jsm | 11 ++++++++--- common/modules/io.jsm | 5 ++--- common/modules/styles.jsm | 35 +++++++++++++++++++++++++---------- 5 files changed, 53 insertions(+), 29 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 1903a8fa..793b960b 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -76,11 +76,11 @@ var Contexts = Module("contexts", { context: null, groups: Class.memoize(function () Object.create(Group.groupsProto, { - groups: { value: this.groupList.filter(function (g) g.filter(buffer.uri)) } + groups: { value: this.activeGroups().filter(function (g) g.filter(buffer.uri)) } })), allGroups: Class.memoize(function () Object.create(Group.groupsProto, { - groups: { value: this.groupList } + groups: { value: this.activeGroups() } })), activeGroups: function (subgroup) @@ -174,7 +174,19 @@ var Contexts = Module("contexts", { action.toString = function toString() (type === default_ ? "" : type + " ") + rhs; args = null; return action; - } + }, + + GroupFlag: function (name) ({ + names: ["-group", "-g"], + + description: "Group to which to add", + + type: ArgType("group", function (group) isString(group) ? contexts.getGroup(group, name) : group[name]), + + get default() (contexts.context && contexts.context.group || contexts.user)[name], + + completer: function (context) completion.group(context) + }) }, { Context: modules.Script = function Context(file, group, args) { function Const(val) Class.Property({ enumerable: true, value: val }); diff --git a/common/content/mappings.js b/common/content/mappings.js index 3930400c..7c7884e4 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -508,7 +508,7 @@ var Mappings = Module("mappings", { names: ["-ex", "-e"], description: "Execute this mapping as an Ex command rather than keys" }, - groupFlag, + contexts.GroupFlag("mappings"), { names: ["-javascript", "-js", "-j"], description: "Execute this mapping as JavaScript rather than keys" @@ -587,7 +587,7 @@ var Mappings = Module("mappings", { { argCount: "0", options: [ - groupFlag, + contexts.GroupFlag("mappings"), update({}, modeFlag, { names: ["-modes", "-mode", "-m"], type: CommandOption.LIST, @@ -616,7 +616,7 @@ var Mappings = Module("mappings", { argCount: "1", completer: opts.completer, options: [ - groupFlag, + contexts.GroupFlag("mappings"), update({}, modeFlag, { names: ["-modes", "-mode", "-m"], type: CommandOption.LIST, @@ -627,13 +627,6 @@ var Mappings = Module("mappings", { }); } - let groupFlag = { - names: ["-group", "-g"], - description: "Mapping group to which to add this mapping", - type: ArgType("map-group", function (group) isString(group) ? contexts.getGroup(group, "mappings") : group), - get default() (contexts.context && contexts.context.group || contexts.user).mappings, - completer: function (context) completion.group(context) - }; let modeFlag = { names: ["-mode", "-m"], type: CommandOption.STRING, diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm index 5c557fc5..02d8e15b 100644 --- a/common/modules/highlight.jsm +++ b/common/modules/highlight.jsm @@ -291,16 +291,21 @@ var Highlights = Module("Highlight", { }, { commands: function (dactyl, modules) { const { autocommands, commands, completion, CommandOption, config, io } = modules; + + let lastScheme; commands.add(["colo[rscheme]"], "Load a color scheme", function (args) { let scheme = args[0]; + if (lastScheme) + lastScheme.unload(); if (scheme == "default") highlight.clear(); - else - dactyl.assert(modules.io.sourceFromRuntimePath(["colors/" + scheme + "." + config.fileExtension]), - "E185: Cannot find color scheme " + scheme); + else { + lastScheme = modules.io.sourceFromRuntimePath(["colors/" + scheme + "." + config.fileExtension]); + dactyl.assert(lastScheme, "E185: Cannot find color scheme " + scheme); + } autocommands.trigger("ColorScheme", { name: scheme }); }, { diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 4603141e..1248e5a0 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -113,7 +113,7 @@ var IO = Module("io", { */ sourceFromRuntimePath: function sourceFromRuntimePath(paths, all) { let dirs = modules.options.get("runtimepath").files; - let found = false; + let found = null; dactyl.echomsg("Searching for " + paths.join(" ").quote() + " in " + modules.options.get("runtimepath").stringValue, 2); @@ -125,8 +125,7 @@ var IO = Module("io", { dactyl.echomsg("Searching for " + file.path.quote(), 3); if (file.exists() && file.isFile() && file.isReadable()) { - io.source(file.path, false); - found = true; + found = io.source(file.path, false) || true; if (!all) break outer; diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index ed893775..3dd52401 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -229,8 +229,8 @@ try { var Styles = Module("Styles", { init: function () { this._id = 0; - this.user = Hive("user"); - this.system = Hive("system"); + this.hives = []; + this.cleanup(); this.allSheets = {}; services["dactyl:"].providers["style"] = function styleProvider(uri) { @@ -242,8 +242,19 @@ var Styles = Module("Styles", { }, cleanup: function cleanup() { - for each (let hive in [this.user, this.system]) + for each (let hive in this.hives) hive.cleanup(); + this.user = this.addHive("user"); + this.system = this.addHive("system"); + }, + + addHive: function addHive(name) { + let hive = array.nth(this.hives, function (h) h.name === name, 0); + if (!hive) { + hive = Hive(name); + this.hives.push(hive); + } + return hive; }, __iterator__: function () Iterator(this.user.sheets.concat(this.system.sheets)), @@ -425,7 +436,7 @@ var Styles = Module("Styles", { }) }, { commands: function (dactyl, modules, window) { - const commands = modules.commands; + const { commands, contexts } = modules; commands.add(["sty[le]"], "Add or list user styles", @@ -434,17 +445,17 @@ var Styles = Module("Styles", { if (css) { if ("-append" in args) { - let sheet = styles.user.get(args["-name"]); + let sheet = args["-group"].get(args["-name"]); if (sheet) { filter = sheet.sites.concat(filter).join(","); css = sheet.css + " " + css; } } - styles.user.add(args["-name"], filter, css, args["-agent"]); + args["-group"].add(args["-name"], filter, css, args["-agent"]); } else { - let list = styles.user.sheets.slice() + let list = args["-group"].sheets.slice() .sort(function (a, b) a.name && b.name ? String.localeCompare(a.name, b.name) : !!b.name - !!a.name || a.id - b.id); let uris = util.visibleURIs(window.content); @@ -455,7 +466,7 @@ var Styles = Module("Styles", { "padding: 0 1em 0 1ex; vertical-align: top;", "padding: 0 1em 0 0; vertical-align: top;"], ([sheet.enabled ? "" : UTF8("×"), - sheet.name || styles.user.sheets.indexOf(sheet), + sheet.name || args["-group"].sheets.indexOf(sheet), sheet.formatSites(uris), sheet.css] for (sheet in values(list)) @@ -466,7 +477,7 @@ var Styles = Module("Styles", { bang: true, completer: function (context, args) { let compl = []; - let sheet = styles.user.get(args["-name"]); + let sheet = args["-group"].get(args["-name"]); if (args.completeArg == 0) { if (sheet) context.completions = [[sheet.sites.join(","), "Current Value"]]; @@ -483,10 +494,11 @@ var Styles = Module("Styles", { options: [ { names: ["-agent", "-A"], description: "Apply style as an Agent sheet" }, { names: ["-append", "-a"], description: "Append site filter and css to an existing, matching sheet" }, + contexts.GroupFlag("styles"), { names: ["-name", "-n"], description: "The name of this stylesheet", - completer: function () [[k, v.css] for ([k, v] in Iterator(styles.user.names))], + completer: function () [[k, v.css] for ([k, v] in Iterator(args["-group"].names))], type: modules.CommandOption.STRING } ], @@ -587,6 +599,9 @@ var Styles = Module("Styles", { }); }); }, + contexts: function (dactyl, modules, window) { + modules.Group.SubGroup("styles", function (group) styles.addHive(group.name)); + }, completion: function (dactyl, modules, window) { const names = Array.slice(util.computedStyle(window.document.createElement("div"))); modules.completion.css = function (context) { From 0cc69f8932c4d911a343bb5fdb4e7a85f67ba6d0 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 08:13:56 -0500 Subject: [PATCH 15/52] Also group :autocmds. --HG-- branch : groups --- common/content/autocommands.js | 149 +++++++++++++++++++-------------- common/content/contexts.js | 35 ++++---- common/modules/template.jsm | 4 +- 3 files changed, 110 insertions(+), 78 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index ed962466..347a77a2 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -8,16 +8,25 @@ /** @scope modules */ -var AutoCommand = Struct("event", "patterns", "command"); +var AutoCommand = Struct("event", "filter", "command"); +update(AutoCommand.prototype, { + eventName: Class.memoize(function () this.event.toLowerCase()), -/** - * @instance autocommands - */ -var AutoCommands = Module("autocommands", { - init: function () { + match: function (event, pattern) { + return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === pattern); + } +}); + +var AutoCmdHive = Class("AutoCmdHive", { + init: function init(group) { + this.group = group; this._store = []; }, + get toStringParams() [this.group.name], + + get builtin() this.group.builtin, + __iterator__: function () array.iterValues(this._store), /** @@ -26,24 +35,26 @@ var AutoCommands = Module("autocommands", { * * @param {Array} events The array of event names for which this * autocommand should be executed. - * @param {string} regexp The URL pattern to match against the buffer URL. + * @param {string} pattern The URL pattern to match against the buffer URL. * @param {string} cmd The Ex command to run. */ - add: function (events, regexp, cmd) { - events.forEach(function (event) { - this._store.push(AutoCommand(event, Option.parse.regexplist(regexp.source || regexp), cmd)); - }, this); + add: function (events, pattern, cmd) { + if (!callable(pattern)) + pattern = Group.compileFilter(pattern); + + for (let event in values(events)) + this._store.push(AutoCommand(event, pattern, cmd)); }, /** * Returns all autocommands with a matching *event* and *regexp*. * * @param {string} event The event name filter. - * @param {string} regexp The URL pattern filter. + * @param {string} pattern The URL pattern filter. * @returns {AutoCommand[]} */ - get: function (event, regexp) { - return this._store.filter(function (autoCmd) AutoCommands.matchAutoCmd(autoCmd, event, regexp)); + get: function (event, pattern) { + return this._store.filter(function (autoCmd) autoCmd.match(event, regexp)); }, /** @@ -53,8 +64,25 @@ var AutoCommands = Module("autocommands", { * @param {string} regexp The URL pattern filter. */ remove: function (event, regexp) { - this._store = this._store.filter(function (autoCmd) !AutoCommands.matchAutoCmd(autoCmd, event, regexp)); + this._store = this._store.filter(function (autoCmd) !autoCmd.match(event, regexp)); }, +}); + +/** + * @instance autocommands + */ +var AutoCommands = Module("autocommands", { + init: function () { + this.user = contexts.subGroup.autocmd.user; + }, + + hives: Group.SubGroup("autocmd", AutoCmdHive), + + get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd), + + add: deprecated("autocommand.user.add", { get: function add() autocommands.user.closure.add }), + get: deprecated("autocommand.user.get", { get: function get() autocommands.user.closure.get }), + remove: deprecated("autocommand.user.remove", { get: function remove() autocommands.user.closure.remove }), /** * Lists all autocommands with a matching *event* and *regexp*. @@ -63,32 +91,39 @@ var AutoCommands = Module("autocommands", { * @param {string} regexp The URL pattern filter. */ list: function (event, regexp) { - let cmds = {}; - // XXX - this._store.forEach(function (autoCmd) { - if (AutoCommands.matchAutoCmd(autoCmd, event, regexp)) { - cmds[autoCmd.event] = cmds[autoCmd.event] || []; - cmds[autoCmd.event].push(autoCmd); - } - }); + function cmds(hive) { + let cmds = {}; + hive._store.forEach(function (autoCmd) { + if (autoCmd.match(event, regexp)) { + cmds[autoCmd.event] = cmds[autoCmd.event] || []; + cmds[autoCmd.event].push(autoCmd); + } + }); + return cmds; + } commandline.commandOutput( - + { - template.map(cmds, function ([event, items]) + template.map(this.activeHives, function (hive) - - - + - template.map(items, function (item) - - - - )) + + + + + + template.map(cmds(hive), function ([event, items]) + + + template.map(items, function (item, i) + + + + + ) + + ) + + ) }
----- Auto Commands ---------- Auto Commands -----
{event}
 {item.patterns}{item.command}
{hive.group.name}
{i == 0 ? event : ""}{item.filter}{item.command}
); }, @@ -104,29 +139,26 @@ var AutoCommands = Module("autocommands", { if (options.get("eventignore").has(event)) return; - let autoCmds = this._store.filter(function (autoCmd) autoCmd.event == event); dactyl.echomsg('Executing ' + event + ' Auto commands for "*"', 8); let lastPattern = null; - let url = args.url || ""; + let uri = args.url ? util.newURI(args.url) : buffer.uri; - for (let [, autoCmd] in Iterator(autoCmds)) { - if (autoCmd.patterns.some(function (re) re.test(url) ^ !re.result)) { - if (!lastPattern || String(lastPattern) != String(autoCmd.patterns)) - dactyl.echomsg("Executing " + event + " Auto commands for " + autoCmd.patterns, 8); + event = event.toLowerCase(); + for (let hive in this.hives.iterValues()) + for (let autoCmd in values(hive._store)) + if (autoCmd.eventName === event && autoCmd.filter(uri)) { + if (!lastPattern || lastPattern !== String(autoCmd.filter)) + dactyl.echomsg("Executing " + event + " Auto commands for " + autoCmd.filter, 8); - lastPattern = autoCmd.patterns; - dactyl.echomsg("autocommand " + autoCmd.command, 9); + lastPattern = String(autoCmd.filter); + dactyl.echomsg("autocommand " + autoCmd.command, 9); - dactyl.trapErrors(autoCmd.command, autoCmd, args); - } - } + dactyl.trapErrors(autoCmd.command, autoCmd, args); + } } }, { - matchAutoCmd: function (autoCmd, event, regexp) { - return (!event || autoCmd.event == event) && (!regexp || String(autoCmd.patterns) == regexp); - } }, { commands: function () { commands.add(["au[tocmd]"], @@ -135,29 +167,21 @@ var AutoCommands = Module("autocommands", { let [event, regexp, cmd] = args; let events = []; - try { - if (args.length > 1) - Option.parse.regexplist(regexp); - } - catch (e) { - dactyl.assert(false, "E475: Invalid argument: " + regexp); - } - if (event) { // NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}| - let validEvents = Object.keys(config.autocommands); + let validEvents = Object.keys(config.autocommands).map(String.toLowerCase); validEvents.push("*"); events = Option.parse.stringlist(event); - dactyl.assert(events.every(function (event) validEvents.indexOf(event) >= 0), - "E216: No such group or event: " + event); + dactyl.assert(events.every(function (event) validEvents.indexOf(event.toLowerCase()) >= 0), + "E216: No such group or event: " + event); } if (args.length > 2) { // add new command, possibly removing all others with the same event/pattern if (args.bang) - autocommands.remove(event, regexp); + args["-group"].remove(event, regexp); cmd = contexts.bindMacro(args, "-ex", function (params) params); - autocommands.add(events, regexp, cmd); + args["-group"].add(events, regexp, cmd); } else { if (event == "*") @@ -166,7 +190,7 @@ var AutoCommands = Module("autocommands", { if (args.bang) { // TODO: "*" only appears to work in Vim when there is a {group} specified if (args[0] != "*" || args.length > 1) - autocommands.remove(event, regexp); // remove all + args["-group"].remove(event, regexp); // remove all } else autocommands.list(event, regexp); // list all @@ -183,6 +207,7 @@ var AutoCommands = Module("autocommands", { keepQuotes: true, literal: 2, options: [ + contexts.GroupFlag("autocmd"), { names: ["-javascript", "-js"], description: "Interpret the action as JavaScript code rather than an Ex command" @@ -245,7 +270,7 @@ var AutoCommands = Module("autocommands", { }; }, javascript: function () { - JavaScript.setCompleter(autocommands.get, [function () Iterator(config.autocommands)]); + JavaScript.setCompleter(autocommands.user.get, [function () Iterator(config.autocommands)]); }, options: function () { options.add(["eventignore", "ei"], diff --git a/common/content/contexts.js b/common/content/contexts.js index 793b960b..b743cab9 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -27,6 +27,26 @@ var Group = Class("Group", { subGroups: {} }, { + compileFilter: function (patterns) { + + function siteFilter(uri) siteFilter.filters.every(function (f) f(uri) == f.result); + + patterns = Option.splitList(patterns, true); + + return update(siteFilter, { + toString: function () this.filters.join(","), + + filters: patterns.map(function (pattern) { + let [, res, filter] = /^(!?)(.*)/.exec(pattern); + + return update(Styles.matchFilter(Option.dequote(filter)), { + result: !res, + toString: function () pattern + }); + }) + }); + }, + groupsProto: {}, subGroupMap: {}, @@ -256,20 +276,7 @@ var Contexts = Module("contexts", { 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 - }); - }) - }); + let filter = Group.compileFilter(args[1]); group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); } diff --git a/common/modules/template.jsm b/common/modules/template.jsm index cd85bb54..d3e7dcd0 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -86,10 +86,10 @@ var Template = Module("Template", { let ret = <>; let n = 0; for each (let i in Iterator(iter)) { - let val = func(i); + let val = func(i, n); if (val == undefined) continue; - if (sep && n++) + if (n++ && sep) ret += sep; if (interruptable && n % interruptable == 0) util.threadYield(true, true); From 2e27dc3571c910ed4f8c01ae0c13c367a75e1cb9 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 08:26:38 -0500 Subject: [PATCH 16/52] Highlighting and stuff. --HG-- branch : groups --- common/content/autocommands.js | 2 +- common/content/contexts.js | 16 +++++++++++++++- common/content/mappings.js | 6 +++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 347a77a2..05f17678 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -119,7 +119,7 @@ var AutoCommands = Module("autocommands", { template.map(items, function (item, i) {i == 0 ? event : ""} - {item.filter} + {item.filter.toXML ? item.filter.toXML() : item.filter} {item.command} ) + ) + diff --git a/common/content/contexts.js b/common/content/contexts.js index b743cab9..f86d6a1f 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -36,6 +36,8 @@ var Group = Class("Group", { return update(siteFilter, { toString: function () this.filters.join(","), + toXML: function () template.map(this.filters, function (f) {f}, <>,), + filters: patterns.map(function (pattern) { let [, res, filter] = /^(!?)(.*)/.exec(pattern); @@ -400,9 +402,21 @@ var Contexts = Module("contexts", { completion: function initCompletion() { completion.group = function group(context, active) { context.title = ["Group"]; - context.keys = { text: "name", description: function (h) h.description || h.filter }; + let uri = buffer.uri; + context.keys = { + active: function (group) group.filter(uri), + text: "name", + description: function (g) <>{g.filter.toXML ? g.filter.toXML() + <>  : ""}{g.description || ""} + }; context.completions = (active === undefined ? contexts.groupList : contexts.activeGroups(active)) .slice(0, -1); + + iter({ Active: true, Inactive: false }).forEach(function ([name, active]) { + context.split(name, null, function (context) { + context.title[0] = name + " Groups"; + context.filters.push(function (item) item.active == active); + }); + }); }; } }); diff --git a/common/content/mappings.js b/common/content/mappings.js index 7c7884e4..7ca3b61e 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -417,12 +417,12 @@ var Mappings = Module("mappings", { { - template.map(hives, function (hive) let (i = 0) + template.map(hives, function (hive) + template.map(maps(hive), function (map) - template.map(map.names, function (name) + template.map(map.names, function (name, i) - {!i++ ? hive.name : ""} + {!i ? hive.group.name : ""} {modeSign} {name} {map.rhs || map.action.toSource()} From 4b8e834fa697454b3c3331575f2994918def0f25 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 08:30:44 -0500 Subject: [PATCH 17/52] Use broader heuristic for regexps in Styles.compileFilter. --HG-- branch : groups --- common/modules/styles.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 3dd52401..89a1a0e7 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -335,7 +335,7 @@ var Styles = Module("Styles", { matchFilter: function (filter) { if (filter === "*") var test = function test(uri) true; - else if (filter[0] === "^") { + else if (!/^(?:[a-z-]+:|[a-z-.]+$)/.test(filter)) { let re = RegExp(filter); test = function test(uri) re.test(uri.spec); } From 5a00fb0c62b2e922f7fdc8c359dbbe8d5a0875df Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 5 Feb 2011 08:46:35 -0500 Subject: [PATCH 18/52] Make :rehash|echomsg "foo" work as expected. --HG-- branch : groups --- common/content/autocommands.js | 2 +- common/content/commands.js | 2 ++ common/content/dactyl.js | 11 ++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 05f17678..76a45a41 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -78,7 +78,7 @@ var AutoCommands = Module("autocommands", { hives: Group.SubGroup("autocmd", AutoCmdHive), - get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd), + get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd).filter(function (h) h._store.length), add: deprecated("autocommand.user.add", { get: function add() autocommands.user.closure.add }), get: deprecated("autocommand.user.get", { get: function get() autocommands.user.closure.get }), diff --git a/common/content/commands.js b/common/content/commands.js index 3c1a66fb..28a749d4 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -1090,6 +1090,8 @@ var Commands = Module("commands", { args.commandString = str.substr(0, len) + args.string; str = args.trailing; yield [command, args]; + if (args.break) + break; } while (str); }, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index e889769c..5623eb17 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1791,7 +1791,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { commands.add(["reh[ash]"], "Reload the " + config.appName + " add-on", - function (args) { util.rehash(args); }, + function (args) { + if (args.trailing) + JSMLoader.rehashCmd = args.trailing; // Hack. + args.break = true; + util.rehash(args); + }, { argCount: "0", options: [ @@ -2117,6 +2122,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.execute(cmd); }); + if (JSMLoader.rehashCmd) + dactyl.execute(JSMLoader.rehashCmd); + JSMLoader.rehashCmd = null; + dactyl.fullyInitialized = true; dactyl.triggerObserver("enter", null); autocommands.trigger("Enter", {}); From 4a4347798038b14040e4ba71d87d9ed120ab203b Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 11:21:58 -0500 Subject: [PATCH 19/52] More group tweaks: colors- prefix for color scheme groups. :group default to return to the default group for your script. --HG-- branch : groups --- common/content/commands.js | 2 +- common/content/contexts.js | 24 ++++++++++++++++++------ common/modules/io.jsm | 2 +- common/modules/util.jsm | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/common/content/commands.js b/common/content/commands.js index 28a749d4..4392bb51 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -982,7 +982,7 @@ var Commands = Module("commands", { ] [^ ]* - ]]>, "", { + ]]>, "g", { forbid: util.regexp(String.replace( Date: Sun, 6 Feb 2011 11:42:33 -0500 Subject: [PATCH 20/52] Change the signature of :group. Remove the now useless :mapgroup alias. --HG-- branch : groups --- common/content/commands.js | 2 ++ common/content/contexts.js | 58 ++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/common/content/commands.js b/common/content/commands.js index 4392bb51..8e577a9d 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -279,6 +279,8 @@ var Command = Class("Command", { explicitOpts: Class.memoize(function () ({})), + has: function (opt) set.has(this.explicitOpts, opt) || typeof opt === "number" && set.has(this, opt), + get literalArg() this.command.literal != null && this[this.command.literal] || "", // TODO: string: Class.memoize(function () { ... }), diff --git a/common/content/contexts.js b/common/content/contexts.js index 76fed5a7..97e7f9d0 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -31,7 +31,8 @@ var Group = Class("Group", { function siteFilter(uri) siteFilter.filters.every(function (f) f(uri) == f.result); - patterns = Option.splitList(patterns, true); + if (!isArray(patterns)) + patterns = Option.splitList(patterns, true); return update(siteFilter, { toString: function () this.filters.join(","), @@ -274,56 +275,59 @@ var Contexts = Module("contexts", { }, { commands: function initCommands() { - commands.add(["gr[oup]", "mapg[roup]"], + commands.add(["gr[oup]"], "Create or select a group", function (args) { - dactyl.assert(args.length <= 2, "Trailing characters"); + if (args.length > 0) { + var name = Option.dequote(args[0]); + dactyl.assert(name !== "builtin", "Cannot modify builtin group"); + dactyl.assert(commands.validName.test(name), "Invalid group name"); - if (args.length == 0) + var group = contexts.getGroup(name); + } + else if (args.bang) + var group = args.context && args.context.group; + else return void completion.listCompleter("group", "", null, null); - 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 = Group.compileFilter(args[1]); + dactyl.assert(group || name, "No current group"); + let filter = Group.compileFilter(args["-locations"]); + if (!group) group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); + else { + if (args.has("-locations")) + group.filter = filter; + if (args.has("-description")) + group.description = args["-description"] + if (args.has("-nopersist")) + group.persist = !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: "*", + 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", + default: ["User-defined group"], type: CommandOption.STRING }, + { + names: ["-locations", "-locs", "-loc", "-l"], + description: ["The URLs for which this group should be active"], + default: ["*"], + type: CommandOption.LIST + }, { names: ["-nopersist", "-n"], description: "Do not save this group to an auto-generated RC file" @@ -331,7 +335,7 @@ var Contexts = Module("contexts", { ] }); - commands.add(["delg[roup]", "delmapg[roup]"], + commands.add(["delg[roup]"], "Delete a group", function (args) { dactyl.assert(contexts.getGroup(args[0]), "No such group: " + args[0]); From 78e8c3c20ea7ae1d9b7f246e3d8f7e833440c34f Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 13:50:00 -0500 Subject: [PATCH 21/52] Fix lost keys when opening command line for the first time. Fix cleanup issue. --HG-- branch : groups --- common/content/commandline.js | 16 ++++++++-------- common/content/dactyl.js | 16 ++++++++++------ common/modules/base.jsm | 1 + common/modules/overlay.jsm | 12 ++++++------ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/common/content/commandline.js b/common/content/commandline.js index 0e052bf4..1af40583 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -306,12 +306,6 @@ var CommandWidgets = Class("CommandWidgets", { var CommandMode = Class("CommandMode", { init: function init() { this.keepCommand = userContext.hidden_option_command_afterimage; - - if (this.historyKey) - this.history = CommandLine.History(commandline.widgets.active.command.inputField, this.historyKey, this); - - if (this.complete) - this.completions = CommandLine.Completions(commandline.widgets.active.command.inputField, this); }, open: function (command) { @@ -325,7 +319,13 @@ var CommandMode = Class("CommandMode", { this.widgets.prompt = this.prompt; this.widgets.command = command || ""; - if (this.completions && command && options["autocomplete"].length) + if (this.historyKey) + this.history = CommandLine.History(commandline.widgets.active.command.inputField, this.historyKey, this); + + if (this.complete) + this.completions = CommandLine.Completions(commandline.widgets.active.command.inputField, this); + + if (this.completions && command && options["autocomplete"].length && commandline.commandSession === this) this.completions.complete(true, false); }, @@ -337,7 +337,7 @@ var CommandMode = Class("CommandMode", { enter: function (stack) { commandline.commandSession = this; - if (this.command || stack.pop && commandline.command) { + if (stack.pop && commandline.command) { this.onChange(commandline.command); if (this.completions && stack.pop) this.completions.complete(true, false); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 5623eb17..cddbf583 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -61,23 +61,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { "dactyl-cleanup": function dactyl_cleanup() { let modules = dactyl.modules; - for (let name in values(Object.getOwnPropertyNames(modules).reverse())) { - let mod = Object.getOwnPropertyDescriptor(modules, name).value; - if (mod instanceof Class) { + let mods = Object.getOwnPropertyNames(modules).reverse() + .map(function (name) Object.getOwnPropertyDescriptor(modules, name).value); + + for (let mod in values(mods)) + if (mod instanceof ModuleBase || mod && mod.isLocalModule) { if ("cleanup" in mod) this.trapErrors(mod.cleanup, mod); if ("destroy" in mod) this.trapErrors(mod.destroy, mod); - if ("INIT" in mod && "cleanup" in mod.INIT) - this.trapErrors(mod.cleanup, mod, dactyl, modules, window); } - } + + for (let mod in values(mods)) + if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT) + this.trapErrors(mod.cleanup, mod, dactyl, modules, window); for (let name in values(Object.getOwnPropertyNames(modules).reverse())) try { delete modules[name]; } catch (e) {} + modules.__proto__ = {}; } }, diff --git a/common/modules/base.jsm b/common/modules/base.jsm index f147ebf1..1b1293bc 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -917,6 +917,7 @@ Module.INIT = { let module = this, objs = {}; for (let i in locals) module = objs[i] = Object.create(module); + module.isLocalModule = true; modules.jsmodules[this.constructor.className] = module; locals.reverse().forEach(function (fn, i) update(objs[i], fn.apply(module, args))) diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 45aaf449..8a4ce14f 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -156,11 +156,16 @@ var Overlay = Module("Overlay", { "completion", "config", "downloads", + "finder", + "highlight", + "io", "javascript", "overlay", "prefs", "services", "storage", + "styles", + "template", "util" ].forEach(function (name) defineModule.time("load", name, require, null, jsmodules, name)); @@ -174,17 +179,12 @@ var Overlay = Module("Overlay", { "commands", "editor", "events", - "finder", - "highlight", "hints", - "io", "mappings", "marks", "mow", "options", - "statusline", - "styles", - "template" + "statusline" ].forEach(function (name) defineModule.time("load", name, modules.load, modules, name)); config.scripts.forEach(modules.load); From 4211f6ee072b94b80e24cbcbd888b82dc858ed29 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 14:07:18 -0500 Subject: [PATCH 22/52] More groups work, including -args flag and proper serialization. --HG-- branch : groups --- common/content/autocommands.js | 7 +++++- common/content/commands.js | 8 +++++-- common/content/contexts.js | 42 ++++++++++++++++++++++++++++++---- common/content/mappings.js | 17 ++++++-------- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 76a45a41..3d1db6e2 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -146,7 +146,11 @@ var AutoCommands = Module("autocommands", { let uri = args.url ? util.newURI(args.url) : buffer.uri; event = event.toLowerCase(); - for (let hive in this.hives.iterValues()) + for (let hive in this.hives.iterValues()) { + let args = update({}, + hive.group.argsExtra(arguments[1]), + arguments[1]); + for (let autoCmd in values(hive._store)) if (autoCmd.eventName === event && autoCmd.filter(uri)) { if (!lastPattern || lastPattern !== String(autoCmd.filter)) @@ -157,6 +161,7 @@ var AutoCommands = Module("autocommands", { dactyl.trapErrors(autoCmd.command, autoCmd, args); } + } } }, { }, { diff --git a/common/content/commands.js b/common/content/commands.js index 8e577a9d..644a14a4 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -154,9 +154,12 @@ var Command = Class("Command", { throw FailedAssertion("E477: No ! allowed"); return !dactyl.trapErrors(function exec(command) { + // update({}, command.hive.group.argsExtra(args), args); + if (this.always) this.always(args, modifiers); - if (!contexts.context || !contexts.context.noExecute) + + if (!context || !context.noExecute) this.action(args, modifiers); }, this); }, @@ -569,12 +572,13 @@ var Commands = Module("commands", { string = string(tokens || {}); let lines = string.split(/\r\n|[\r\n]/); + let startLine = context.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; + context.line = startLine + i; // Process escaped new lines while (i < lines.length && /^\s*\\/.test(lines[i + 1])) diff --git a/common/content/contexts.js b/common/content/contexts.js index 97e7f9d0..1233121d 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -9,7 +9,7 @@ var Group = Class("Group", { const self = this; this.name = name; this.description = description; - this.filter = filter || function (uri) true; + this.filter = filter || Group.defaultFilter; this.persist = persist || false; this.subGroups = []; }, @@ -20,6 +20,8 @@ var Group = Class("Group", { subGroup.cleanup(); }, + argsExtra: function argsExtra() ({}), + get toStringParams() [this.name], get builtin() contexts.builtinGroups.indexOf(this) >= 0, @@ -52,6 +54,8 @@ var Group = Class("Group", { groupsProto: {}, + defaultFilter: Class.memoize(function () this.compileFilter(["*"])), + subGroupMap: {}, SubGroup: Class("SubGroup", Class.Property, { @@ -91,7 +95,7 @@ var Contexts = Module("contexts", { this.groupMap = {}; this.subGroupProto = {}; - this.system = this.addGroup("builtin", "Builtin items"); + this.builtin = this.addGroup("builtin", "Builtin items"); this.user = this.addGroup("user", "User-defined items", null, true); this.builtinGroups = [this.system, this.user]; }, @@ -295,7 +299,7 @@ var Contexts = Module("contexts", { let filter = Group.compileFilter(args["-locations"]); if (!group) group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); - else { + else if (!group.builtin) { if (args.has("-locations")) group.filter = filter; if (args.has("-description")) @@ -304,6 +308,12 @@ var Contexts = Module("contexts", { group.persist = !args["-nopersist"] } + if (!group.builtin && args.has("-args")) { + group.argsExtra = contexts.bindMacro({ literalArg: "return " + args["-args"] }, + "-javascript", util.identity); + group.args = args["-args"]; + } + if (args.context) args.context.group = group; }, @@ -316,6 +326,11 @@ var Contexts = Module("contexts", { }, keepQuotes: true, options: [ + { + names: ["-args", "-a"], + description: "JavaScript Object which augments the arguments passed to commands, mappings, and autocommands", + type: CommandOption.STRING + }, { names: ["-description", "-desc", "-d"], description: "A description of this group", @@ -332,7 +347,26 @@ var Contexts = Module("contexts", { names: ["-nopersist", "-n"], description: "Do not save this group to an auto-generated RC file" } - ] + ], + serialGroup: 20, + serialize: function () [ + { + command: this.name, + bang: true, + options: iter([v, typeof group[k] == "boolean" ? null : group[k]] + // FIXME: this map is expressed multiple times + for ([k, v] in Iterator({ + args: "-args", + description: "-description", + filter: "-locations" + })) + if (group[k])).toObject(), + arguments: [group.name], + ignoreDefaults: true + } + for (group in values(contexts.activeGroups())) + if (!group.builtin && group.persist) + ].concat([{ command: this.name, arguments: ["user"] }]) }); commands.add(["delg[roup]"], diff --git a/common/content/mappings.js b/common/content/mappings.js index 7ca3b61e..e350a863 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -107,8 +107,9 @@ var Map = Class("Map", { .map(function ([i, prop]) [prop, this[i]], arguments) .toObject(); - if (!args.context) - args.context = contexts.context; + args = update({ context: contexts.context }, + this.hive.group.argsExtra(args), + args); let self = this; function repeat() self.action(args) @@ -531,20 +532,16 @@ var Mappings = Module("mappings", { serialize: function () { return this.name != "map" ? [] : array(mappings.userHives) - .filter(function (h) !h.noPersist) + .filter(function (h) h.group.persist) .map(function (hive) [ - { - command: "mapgroup", - bang: true, - arguments: [hive.name, String(hive.filter)].slice(0, hive.name == "user" ? 1 : 2) - } - ].concat([ { command: "map", options: array([ + hive !== mappings.user && ["-group", hive.group.name], ["-modes", uniqueModes(map.modes)], ["-description", map.description], map.silent && ["-silent"]]) + .filter(util.identity) .toObject(), arguments: [map.names[0]], @@ -553,7 +550,7 @@ var Mappings = Module("mappings", { } for (map in userMappings(hive)) if (map.persist) - ])) + ]) .flatten().array; } }; From 2a99de96a0985794ba2a1977e68d6e875b42e020 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 20:18:12 -0500 Subject: [PATCH 23/52] Adjust some API names to make things more consistent and less confusing. --HG-- branch : groups --- common/content/autocommands.js | 4 ++-- common/content/contexts.js | 40 +++++++++++++++++----------------- common/content/mappings.js | 6 ++--- common/modules/styles.jsm | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 3d1db6e2..0f01779a 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -73,10 +73,10 @@ var AutoCmdHive = Class("AutoCmdHive", { */ var AutoCommands = Module("autocommands", { init: function () { - this.user = contexts.subGroup.autocmd.user; + this.user = contexts.hives.autocmd.user; }, - hives: Group.SubGroup("autocmd", AutoCmdHive), + hives: Group.Hive("autocmd", AutoCmdHive), get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd).filter(function (h) h._store.length), diff --git a/common/content/contexts.js b/common/content/contexts.js index 1233121d..2fc81bba 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -11,13 +11,13 @@ var Group = Class("Group", { this.description = description; this.filter = filter || Group.defaultFilter; this.persist = persist || false; - this.subGroups = []; + this.hives = []; }, cleanup: function cleanup() { - for (let subGroup in values(this.subGroups)) - if (subGroup.cleanup) - subGroup.cleanup(); + for (let hive in values(this.hives)) + if (hive.cleanup) + hive.cleanup(); }, argsExtra: function argsExtra() ({}), @@ -26,7 +26,7 @@ var Group = Class("Group", { get builtin() contexts.builtinGroups.indexOf(this) >= 0, - subGroups: {} + hives: {} }, { compileFilter: function (patterns) { @@ -56,9 +56,9 @@ var Group = Class("Group", { defaultFilter: Class.memoize(function () this.compileFilter(["*"])), - subGroupMap: {}, + hiveMap: {}, - SubGroup: Class("SubGroup", Class.Property, { + Hive: Class("Hive", Class.Property, { init: function init(name, constructor) { const self = this; if (this.Group) @@ -73,13 +73,13 @@ var Group = Class("Group", { this.name = name; memoize(Group.prototype, name, function () { let group = constructor(this); - this.subGroups.push(group); + this.hives.push(group); return group; }); - memoize(Group.subGroupMap, name, - function () Object.create(Object.create(contexts.subGroupProto, - { _subGroup: { value: name } }))); + memoize(Group.hiveMap, name, + function () Object.create(Object.create(contexts.hiveProto, + { _hive: { value: name } }))); memoize(Group.groupsProto, name, function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); @@ -93,7 +93,7 @@ var Contexts = Module("contexts", { init: function () { this.groupList = []; this.groupMap = {}; - this.subGroupProto = {}; + this.hiveProto = {}; this.builtin = this.addGroup("builtin", "Builtin items"); this.user = this.addGroup("user", "User-defined items", null, true); @@ -110,11 +110,11 @@ var Contexts = Module("contexts", { groups: { value: this.activeGroups() } })), - activeGroups: function (subgroup) - let (need = subgroup ? [subgroup] : Object.keys(this.subGroup)) + activeGroups: function (hive) + let (need = hive ? [hive] : Object.keys(this.hives)) this.groupList.filter(function (group) need.some(function (name) set.has(group, name))), - get subGroup() Group.subGroupMap, + get hives() Group.hiveMap, addGroup: function addGroup(name, description, filter, persist) { this.removeGroup(name); @@ -122,7 +122,7 @@ var Contexts = Module("contexts", { let group = Group(name, description, filter, persist); this.groupList.unshift(group); this.groupMap[name] = group; - this.subGroupProto.__defineGetter__(name, function () group[this._subGroup]); + this.hiveProto.__defineGetter__(name, function () group[this._hive]); return group; }, @@ -146,19 +146,19 @@ var Contexts = Module("contexts", { this.context.group = null; delete this.groupMap[name]; - delete this.subGroupProto[name]; + delete this.hiveProto[name]; delete this.groups; return group; }, - getGroup: function getGroup(name, subGroup) { + getGroup: function getGroup(name, hive) { if (name === "default") var group = this.context && this.context.context && this.context.context.GROUP; else group = array.nth(this.groupList, function (h) h.name == name, 0) || null; - if (group && subGroup) - return group[subGroup]; + if (group && hive) + return group[hive]; return group; }, diff --git a/common/content/mappings.js b/common/content/mappings.js index e350a863..c1171158 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -301,13 +301,13 @@ var MapHive = Class("MapHive", { */ var Mappings = Module("mappings", { init: function () { - this.user = contexts.subGroup.mappings.user; - this.builtin = contexts.subGroup.mappings.builtin; + this.user = contexts.hives.mappings.user; + this.builtin = contexts.hives.mappings.builtin; }, repeat: Modes.boundProperty(), - hives: Group.SubGroup("mappings", MapHive), + hives: Group.Hive("mappings", MapHive), get allHives() contexts.allGroups.mappings, diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 89a1a0e7..3c240ef6 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -600,7 +600,7 @@ var Styles = Module("Styles", { }); }, contexts: function (dactyl, modules, window) { - modules.Group.SubGroup("styles", function (group) styles.addHive(group.name)); + modules.Group.Hive("styles", function (group) styles.addHive(group.name)); }, completion: function (dactyl, modules, window) { const names = Array.slice(util.computedStyle(window.document.createElement("div"))); From 83d86f7f024c71fd1154fda2be8ad67fde8ec5a3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 22:26:25 -0500 Subject: [PATCH 24/52] Also groupify commands. Usage examples are in official plugins. --HG-- branch : groups --- common/content/autocommands.js | 2 +- common/content/commands.js | 405 ++++++++++++++++++--------------- common/content/contexts.js | 41 ++-- common/content/mappings.js | 5 +- common/modules/io.jsm | 2 +- common/modules/styles.jsm | 2 +- 6 files changed, 253 insertions(+), 204 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 0f01779a..81e38fac 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -76,7 +76,7 @@ var AutoCommands = Module("autocommands", { this.user = contexts.hives.autocmd.user; }, - hives: Group.Hive("autocmd", AutoCmdHive), + hives: Group.Hives("autocmd", AutoCmdHive), get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd).filter(function (h) h._store.length), diff --git a/common/content/commands.js b/common/content/commands.js index 644a14a4..981ea8cd 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -127,6 +127,8 @@ var Command = Class("Command", { this.options = this.options.map(CommandOption.fromArray, CommandOption); }, + get toStringParams() [this.name, this.hive.group.name], + get helpTag() ":" + this.name, get lastCommand() this._lastCommand || commandline.command, @@ -170,12 +172,8 @@ var Command = Class("Command", { * @param {string} name The candidate name. * @returns {boolean} */ - hasName: function (name) { - return this.specs.some(function (spec) { - let [, head, tail] = /([^[]+)(?:\[(.*)])?/.exec(spec); - return name.indexOf(head) == 0 && (head + (tail || "")).indexOf(name) == 0; - }); - }, + hasName: function (name) this.parsedSpecs.some( + function ([long, short]) name.indexOf(short) == 0 && long.indexOf(name) == 0), /** * A helper function to parse an argument string. @@ -399,15 +397,118 @@ var ex = { __noSuchMethod__: function (meth, args) this._run(meth).apply(this, args) }; +var CommandHive = Class("CommandHive", { + init: function init(group) { + this.group = group; + this._map = {}; + this._list = []; + }, + + get toStringParams() [this.group.name], + + get builtin() this.group.builtin, + + /** @property {Iterator(Command)} @private */ + __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), + + /** @property {string} The last executed Ex command line. */ + repeat: null, + + /** + * Adds a new command. + * + * @param {string[]} names 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. + * @param {function} action The action invoked by this command. + * @param {Object} extra An optional extra configuration hash. + * @optional + */ + add: function (names, description, action, extra, replace) { + extra = extra || {}; + if (!extra.definedAt) + extra.definedAt = Commands.getCaller(Components.stack.caller); + + extra.hive = this; + extra.parsedSpecs = Command.parseSpecs(names); + + let names = array.flatten(extra.parsedSpecs); + let name = names[0]; + + dactyl.assert(!names.some(function (name) name in commands.builtin._map), + "E182: Can't replace non-user command: " + name); + + dactyl.assert(replace || names.every(function (name) !(name in this._map), this), + "Not replacing command " + name); + + for (let name in values(names)) { + ex.__defineGetter__(name, function () this._run(name)); + if (name in this._map) + this.remove(name); + } + + let self = this; + let closure = function () self._map[name]; + + memoize(this._map, name, function () Command(names, description, action, extra)); + memoize(this._list, this._list.length, closure); + for (let alias in values(names.slice(1))) + memoize(this._map, alias, closure); + + return name; + }, + _add: function (names, description, action, extra, replace) { + extra = extra || {}; + extra.definedAt = Commands.getCaller(Components.stack.caller.caller); + return this.add.apply(this, arguments); + }, + + /** + * Returns the command with matching *name*. + * + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @param {boolean} full If true, only return a command if one of + * its names matches *name* exactly. + * @returns {Command} + */ + get: function (name, full) this._map[name] + || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) + || null, + + /** + * Remove the user-defined command with matching *name*. + * + * @param {string} name The name of the command to remove. This can be + * any of the command's names. + */ + remove: function (name) { + dactyl.assert(this.group !== contexts.default, + "Cannot delete non-user commands"); + + let cmd = this.get(name); + this._list = this._list.filter(function (c) c !== cmd); + for (let name in values(cmd.names)) + delete this._map[name]; + } +}); + /** * @instance commands */ var Commands = Module("commands", { init: function () { - this._exCommands = []; - this._exMap = {}; + this.user = contexts.hives.commands.user; + this.builtin = contexts.hives.commands.builtin; }, + hives: Group.Hives("commands", CommandHive), + + get allHives() contexts.allGroups.commands, + + get userHives() this.allHives.filter(function (h) h !== this.builtin, this), + /** * @property Indicates that no count was specified for this * command invocation. @@ -423,84 +524,17 @@ var Commands = Module("commands", { COUNT_ALL: -2, // :%... /** @property {Iterator(Command)} @private */ - __iterator__: function () { - let sorted = this._exCommands.sort(function (a, b) a.name > b.name); - return array.iterValues(sorted); - }, - iterator: function () { - let sorted = this._exCommands.sort(function (a, b) a.serialGroup - b.serialGroup || a.name > b.name); - return array.iterValues(sorted); - }, + iterator: function () iter.apply(null, this.hives) + .sort(function (a, b) a.serialGroup - b.serialGroup || a.name > b.name) + .iterValues(), /** @property {string} The last executed Ex command line. */ repeat: null, - _addCommand: function (args, replace) { - if (!args[3]) - args[3] = {}; - args[3].definedAt = Commands.getCaller(Components.stack.caller.caller); - - let names = array.flatten(Command.parseSpecs(args[0])); - args.parsedSpecs = names; - - dactyl.assert(!names.some(function (name) name in this._exMap && !this._exMap[name].user, this), - "E182: Can't replace non-user command: " + args[0][0]); - - if (!replace || !args[3].user) - dactyl.assert(!names.some(function (name) name in this._exMap, this), - "Not replacing command " + args[0]); - - for (let name in values(names)) { - ex.__defineGetter__(name, function () this._run(name)); - if (name in this._exMap) - commands.removeUserCommand(name); - } - - let name = names[0]; - let closure = function () commands._exMap[name]; - - memoize(this._exMap, name, function () Command.apply(null, args)); - memoize(this._exCommands, this._exCommands.length, closure); - - for (let alias in values(names.slice(1))) - memoize(this._exMap, alias, closure); - - return name; - }, - - /** - * Adds a new default command. - * - * @param {string[]} names 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. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @optional - */ - add: function (names, description, action, extra) { - return this._addCommand([names, description, action, extra], false); - }, - - /** - * Adds a new user-defined command. - * - * @param {string[]} names 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. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @param {boolean} replace Overwrite an existing command with the same - * canonical name. - */ - addUserCommand: function (names, description, action, extra, replace) { - extra = extra || {}; - extra.user = true; - - return this._addCommand([names, description, action, extra], replace); - }, + add: function () this.builtin._add.apply(this.builtin, arguments), + addUserCommand: deprecated("commands.user.add", { get: function addUserCommand() this.user.closure._add }), + getUserCommands: deprecated("iter(commands.user)", function getUserCommands() iter(commands.user).toArray()), + removeUserCommand: deprecated("commands.user.remove", { get: function removeUserCommand() this.user.closure.remove }), /** * Returns the specified command invocation object serialized to @@ -535,6 +569,60 @@ var Commands = Module("commands", { return res.join(" "); }, + /** + * Returns the command with matching *name*. + * + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @returns {Command} + */ + get: function (name, full) iter(this.hives).map(function ([i, hive]) hive.get(name, full)) + .nth(util.identity, 0), + + /** + * Displays a list of user-defined commands. + */ + list: function list() { + function completerToString(completer) { + if (completer) + return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom"; + return ""; + } + + if (!commands.userHives.some(function (h) h._list.length)) + dactyl.echomsg("No user-defined commands found"); + else + commandline.commandOutput( + + + + + + + + + + + { + template.map(commands.userHives, function (hive) let (i = 0) + + + template.map(hive, function (cmd) + template.map(cmd.names, function (name) + + + + + + + + + )) + + ) + } +
+ NameArgsRangeCompleteDefinition
{!i++ ? hive.group.name : ""}{cmd.bang ? "!" : " "}{cmd.name}{cmd.argCount}{cmd.count ? "0c" : ""}{completerToString(cmd.completer)}{cmd.replacementText || "function () { ... }"}
); + }, + /** * Executes an Ex command script. * @@ -603,35 +691,6 @@ var Commands = Module("commands", { }); }, - /** - * Returns the command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @returns {Command} - */ - get: function (name, full) - this._exMap[name] || !full && array.nth(this._exCommands, function (cmd) cmd.hasName(name), 0) || null, - - /** - * Returns the user-defined command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @returns {Command} - */ - getUserCommand: function (name) - array.nth(this._exCommands, function (cmd) cmd.user && cmd.hasName(name), 0) || null, - - /** - * Returns all user-defined commands. - * - * @returns {Command[]} - */ - getUserCommands: function () { - return this._exCommands.filter(function (cmd) cmd.user); - }, - /** * Returns true if a command invocation contains a URL referring to the * domain *host*. @@ -1121,21 +1180,8 @@ var Commands = Module("commands", { get complQuote() Commands.complQuote, /** @property */ - get quoteArg() Commands.quoteArg, // XXX: better somewhere else? + get quoteArg() Commands.quoteArg // XXX: better somewhere else? - /** - * Remove the user-defined command with matching *name*. - * - * @param {string} name The name of the command to remove. This can be - * any of the command's names. - */ - removeUserCommand: function (name) { - let cmd = this.get(name); - dactyl.assert(cmd.user, "E184: No such user-defined command: " + name); - this._exCommands = this._exCommands.filter(function (c) c !== cmd); - for (let name in values(cmd.names)) - delete this._exMap[name]; - } }, { /** * Returns a frame object describing the currently executing @@ -1199,7 +1245,7 @@ var Commands = Module("commands", { completion.command = function command(context) { context.title = ["Command"]; context.keys = { text: "longNames", description: "description" }; - context.completions = [k for (k in commands)]; + context.generate = function () commands.hives.map(function (h) h._list).flatten(); }; // provides completions for ex commands, including their arguments @@ -1254,8 +1300,6 @@ var Commands = Module("commands", { }, commands: function () { - let completerMap = config.completers; - // TODO: Vim allows commands to be defined without {rep} if there are {attr}s // specified - useful? commands.add(["com[mand]"], @@ -1266,7 +1310,9 @@ var Commands = Module("commands", { dactyl.assert(!cmd || cmd.split(",").every(commands.validName.closure.test), "E182: Invalid command name"); - if (args.literalArg) { + if (!args.literalArg) + commands.list(); + else { let completer = args["-complete"]; let completerFunc = null; // default to no completion for user commands @@ -1295,10 +1341,10 @@ var Commands = Module("commands", { }; } else - completerFunc = function (context) completion.closure[completerMap[completer]](context); + completerFunc = function (context) completion.closure[config.completers[completer]](context); } - let added = commands.addUserCommand(cmd.split(","), + let added = args["-group"].add(cmd.split(","), args["-description"], contexts.bindMacro(args, "-ex", function makeParams(args, modifiers) ({ @@ -1323,29 +1369,6 @@ var Commands = Module("commands", { if (!added) dactyl.echoerr("E174: Command already exists: add ! to replace it"); } - else { - let completerToString = function completerToString(completer) { - if (completer) - return [k for ([k, v] in Iterator(completerMap)) if (completer == completion.closure[v])][0] || "custom"; - return ""; - } - - // TODO: perhaps we shouldn't allow options in a list call but just ignore them for now - let cmds = commands._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd))); - - if (cmds.length > 0) - commandline.commandOutput( - template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"], - ([cmd.bang ? "!" : " ", - cmd.name, - cmd.argCount, - cmd.count ? "0c" : "", - completerToString(cmd.completer), - cmd.replacementText || "function () { ... }"] - for ([, cmd] in Iterator(cmds))))); - else - dactyl.echomsg("No user-defined commands found"); - } }, { bang: true, completer: function (context, args) { @@ -1362,22 +1385,27 @@ var Commands = Module("commands", { // TODO: "E180: invalid complete value: " + arg names: ["-complete", "-C"], description: "The argument completion function", - completer: function (context) [[k, ""] for ([k, v] in Iterator(completerMap))], + completer: function (context) [[k, ""] for ([k, v] in Iterator(config.completers))], type: CommandOption.STRING, - validator: function (arg) arg in completerMap || /^custom,/.test(arg), - }, { + validator: function (arg) arg in config.completers || /^custom,/.test(arg), + }, + { names: ["-description", "-desc", "-d"], description: "A user-visible description of the command", default: "User-defined command", type: CommandOption.STRING - }, { + }, + contexts.GroupFlag("commands"), + { names: ["-javascript", "-js", "-j"], description: "Execute the definition as JavaScript rather than Ex commands" - }, { + }, + { names: ["-literal", "-l"], description: "Process the nth ignoring any quoting or meta characters", type: CommandOption.INT - }, { + }, + { names: ["-nargs", "-a"], description: "The allowed number of arguments", completer: [["0", "No arguments are allowed (default)"], @@ -1388,31 +1416,36 @@ var Commands = Module("commands", { default: "0", type: CommandOption.STRING, validator: function (arg) /^[01*?+]$/.test(arg) - }, { + }, + { names: ["-nopersist", "-n"], description: "Do not save this command to an auto-generated RC file" } ], literal: 1, - serialize: function () [ { - command: this.name, - bang: true, - options: iter([v, typeof cmd[k] == "boolean" ? null : cmd[k]] - // FIXME: this map is expressed multiple times - for ([k, v] in Iterator({ - argCount: "-nargs", - bang: "-bang", - count: "-count", - description: "-description" - })) - if (cmd[k])).toObject(), - arguments: [cmd.name], - literalArg: cmd.action, - ignoreDefaults: true - } - for ([k, cmd] in Iterator(commands._exCommands)) - if (cmd.user && cmd.persist) - ] + + serialize: function () array(commands.userHives) + .filter(function (h) h.group.persist) + .map(function (hive) [ + { + command: this.name, + bang: true, + options: iter([v, typeof cmd[k] == "boolean" ? null : cmd[k]] + // FIXME: this map is expressed multiple times + for ([k, v] in Iterator({ + argCount: "-nargs", + bang: "-bang", + count: "-count", + description: "-description" + })) + if (cmd[k])).toObject(), + arguments: [cmd.name], + literalArg: cmd.action, + ignoreDefaults: true + } + for (cmd in hive) if (cmd.persist) + ], this) + .flatten().array }); commands.add(["comc[lear]"], @@ -1449,8 +1482,14 @@ var Commands = Module("commands", { name: ["listc[ommands]", "lc"], description: "List all Ex commands along with their short descriptions", index: "ex-cmd", - iterate: function (args) commands, + iterate: function (args) commands.iterator().map(function (cmd) ({ + __proto__: cmd, + columns: [ + cmd.hive == commands.builtin ? "" : {cmd.hive.group.name} + ] + })), format: { + headings: ["Command", "Group", "Description"], description: function (cmd) template.linkifyHelp(cmd.description + (cmd.replacementText ? ": " + cmd.action : "")), help: function (cmd) ":" + cmd.name } @@ -1471,8 +1510,8 @@ var Commands = Module("commands", { }); }, javascript: function () { - JavaScript.setCompleter([commands.get, commands.removeUserCommand], - [function () ([c.name, c.description] for (c in commands))]); + JavaScript.setCompleter([commands.user.get, commands.user.remove], + [function () ([c.name, c.description] for (c in this))]); }, mappings: function () { mappings.add(config.browserModes, diff --git a/common/content/contexts.js b/common/content/contexts.js index 2fc81bba..e3f2a0d4 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -58,7 +58,7 @@ var Group = Class("Group", { hiveMap: {}, - Hive: Class("Hive", Class.Property, { + Hives: Class("Hives", Class.Property, { init: function init(name, constructor) { const self = this; if (this.Group) @@ -116,13 +116,18 @@ var Contexts = Module("contexts", { get hives() Group.hiveMap, - addGroup: function addGroup(name, description, filter, persist) { - this.removeGroup(name); + addGroup: function addGroup(name, description, filter, persist, replace) { + if (replace) + this.removeGroup(name); - let group = Group(name, description, filter, persist); - this.groupList.unshift(group); - this.groupMap[name] = group; - this.hiveProto.__defineGetter__(name, function () group[this._hive]); + let group = this.groupMap[name]; + if (!group) { + group = Group(name, description, filter, persist); + this.groupList.unshift(group); + this.groupMap[name] = group; + this.hiveProto.__defineGetter__(name, function () group[this._hive]); + delete this.groups; + } return group; }, @@ -248,7 +253,8 @@ var Contexts = Module("contexts", { delete plugins[this.NAME]; if (plugins[this.PATH] === this) delete plugins[this.PATH]; - contexts.removeGroup(this.GROUP); + if (!this.GROUP.builtin) + contexts.removeGroup(this.GROUP); }) }); Class.replaceProperty(plugins, file.path, self); @@ -264,12 +270,15 @@ var Contexts = Module("contexts", { let path = isRuntime ? file.getRelativeDescriptor(isRuntime) : file.path; - self.GROUP = group || - contexts.addGroup((isRuntime ? "" : "script-") + - commands.nameRegexp.iterate(path.replace(/\..*/, "")) - .join("-"), - "Script group for " + file.path, - null, false); + if (!group) + group = contexts.addGroup((isRuntime ? "" : "script-") + + commands.nameRegexp.iterate(path.replace(/\..*/, "")) + .join("-"), + "Script group for " + file.path, + null, false); + + Class.replaceProperty(self, "GROUP", group); + Class.replaceProperty(self, "group", group); return plugins.contexts[file.path] = self; }, @@ -297,8 +306,8 @@ var Contexts = Module("contexts", { dactyl.assert(group || name, "No current group"); let filter = Group.compileFilter(args["-locations"]); - if (!group) - group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"]); + if (!group || args.bang) + group = contexts.addGroup(name, args["-description"], filter, !args["-nopersist"], args.bang); else if (!group.builtin) { if (args.has("-locations")) group.filter = filter; diff --git a/common/content/mappings.js b/common/content/mappings.js index c1171158..3f05549a 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -307,7 +307,7 @@ var Mappings = Module("mappings", { repeat: Modes.boundProperty(), - hives: Group.Hive("mappings", MapHive), + hives: Group.Hives("mappings", MapHive), get allHives() contexts.allGroups.mappings, @@ -668,6 +668,7 @@ var Mappings = Module("mappings", { iterate: function (args) { let mainMode = this.getMode(args); let seen = {}; + // Bloody hell. --Kris for (let mode in values([mainMode].concat(mainMode.bases))) for (let hive in mappings.hives.iterValues()) for (let map in array.iterValues(hive.getStack(mode))) @@ -677,7 +678,7 @@ var Mappings = Module("mappings", { name: name, columns: [ mode == mainMode ? "" : {mode.name}, - hive.name == "builtin" ? "" : {hive.name} + hive == mappings.builtin ? "" : {hive.group.name} ], __proto__: map }; diff --git a/common/modules/io.jsm b/common/modules/io.jsm index f83987a7..f4abbfdf 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -612,7 +612,7 @@ var IO = Module("io", { "E189: " + file.path.quote() + " exists (add ! to override)"); // TODO: Use a set/specifiable list here: - let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)]; + let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)]; lines = array.flatten(lines); lines.unshift('"' + config.version + "\n"); diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 3c240ef6..a7259259 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -600,7 +600,7 @@ var Styles = Module("Styles", { }); }, contexts: function (dactyl, modules, window) { - modules.Group.Hive("styles", function (group) styles.addHive(group.name)); + modules.Group.Hives("styles", function (group) styles.addHive(group.name)); }, completion: function (dactyl, modules, window) { const names = Array.slice(util.computedStyle(window.document.createElement("div"))); From 9189715e9c15b75490d670f8ee24ea52743bd076 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 6 Feb 2011 23:02:36 -0500 Subject: [PATCH 25/52] Also add events groups for the use of plugins. --HG-- branch : groups --- common/content/autocommands.js | 12 ++---- common/content/buffer.js | 6 +-- common/content/commands.js | 20 ++++------ common/content/contexts.js | 41 +++++++++++++++++++- common/content/dactyl.js | 4 +- common/content/events.js | 70 ++++++++++++++++++++++++++-------- common/content/hints.js | 2 +- common/content/mappings.js | 18 ++++----- common/content/marks.js | 2 +- common/content/tabs.js | 4 +- common/modules/styles.jsm | 1 + 11 files changed, 123 insertions(+), 57 deletions(-) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 81e38fac..e24d2e6e 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -17,16 +17,12 @@ update(AutoCommand.prototype, { } }); -var AutoCmdHive = Class("AutoCmdHive", { +var AutoCmdHive = Class("AutoCmdHive", Group.Hive, { init: function init(group) { - this.group = group; + init.supercall(this, group); this._store = []; }, - get toStringParams() [this.group.name], - - get builtin() this.group.builtin, - __iterator__: function () array.iterValues(this._store), /** @@ -111,7 +107,7 @@ var AutoCommands = Module("autocommands", { { template.map(this.activeHives, function (hive) - {hive.group.name} + {hive.name} + + template.map(cmds(hive), function ([event, items]) @@ -148,7 +144,7 @@ var AutoCommands = Module("autocommands", { event = event.toLowerCase(); for (let hive in this.hives.iterValues()) { let args = update({}, - hive.group.argsExtra(arguments[1]), + hive.argsExtra(arguments[1]), arguments[1]); for (let autoCmd in values(hive._store)) diff --git a/common/content/buffer.js b/common/content/buffer.js index b5e4d1ed..4b691261 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1644,9 +1644,9 @@ var Buffer = Module("buffer", { }; }, events: function () { - events.addSessionListener(config.browser, "DOMContentLoaded", buffer.closure.onDOMContentLoaded, true); - events.addSessionListener(config.browser, "load", buffer.closure.onPageLoad, true); - events.addSessionListener(config.browser, "scroll", buffer.closure._updateBufferPosition, false); + events.listen(config.browser, "DOMContentLoaded", buffer.closure.onDOMContentLoaded, true); + events.listen(config.browser, "load", buffer.closure.onPageLoad, true); + events.listen(config.browser, "scroll", buffer.closure._updateBufferPosition, false); }, mappings: function () { var myModes = config.browserModes; diff --git a/common/content/commands.js b/common/content/commands.js index 981ea8cd..86d2e670 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -127,7 +127,7 @@ var Command = Class("Command", { this.options = this.options.map(CommandOption.fromArray, CommandOption); }, - get toStringParams() [this.name, this.hive.group.name], + get toStringParams() [this.name, this.hive.name], get helpTag() ":" + this.name, @@ -155,8 +155,8 @@ var Command = Class("Command", { if (args.bang && !this.bang) throw FailedAssertion("E477: No ! allowed"); - return !dactyl.trapErrors(function exec(command) { - // update({}, command.hive.group.argsExtra(args), args); + return !dactyl.trapErrors(function exec() { + update({}, this.hive.argsExtra(args), args); if (this.always) this.always(args, modifiers); @@ -397,17 +397,13 @@ var ex = { __noSuchMethod__: function (meth, args) this._run(meth).apply(this, args) }; -var CommandHive = Class("CommandHive", { +var CommandHive = Class("CommandHive", Group.Hive, { init: function init(group) { - this.group = group; + init.supercall(this, group); this._map = {}; this._list = []; }, - get toStringParams() [this.group.name], - - get builtin() this.group.builtin, - /** @property {Iterator(Command)} @private */ __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), @@ -610,7 +606,7 @@ var Commands = Module("commands", { template.map(hive, function (cmd) template.map(cmd.names, function (name) - {!i++ ? hive.group.name : ""} + {!i++ ? hive.name : ""} {cmd.bang ? "!" : " "} {cmd.name} {cmd.argCount} @@ -1425,7 +1421,7 @@ var Commands = Module("commands", { literal: 1, serialize: function () array(commands.userHives) - .filter(function (h) h.group.persist) + .filter(function (h) h.persist) .map(function (hive) [ { command: this.name, @@ -1485,7 +1481,7 @@ var Commands = Module("commands", { iterate: function (args) commands.iterator().map(function (cmd) ({ __proto__: cmd, columns: [ - cmd.hive == commands.builtin ? "" : {cmd.hive.group.name} + cmd.hive == commands.builtin ? "" : {cmd.hive.name} ] })), format: { diff --git a/common/content/contexts.js b/common/content/contexts.js index e3f2a0d4..9e57e90f 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -16,8 +16,11 @@ var Group = Class("Group", { cleanup: function cleanup() { for (let hive in values(this.hives)) - if (hive.cleanup) - hive.cleanup(); + dactyl.trapErrors("cleanup", hive); + }, + destroy: function destroy() { + for (let hive in values(this.hives)) + dactyl.trapErrors("destroy", hive); }, argsExtra: function argsExtra() ({}), @@ -58,6 +61,32 @@ var Group = Class("Group", { hiveMap: {}, + Hive: Class("Hive", { + init: function init(group) { + this.group = group; + }, + + cleanup: function cleanup() {}, + destroy: function destroy() {}, + + get argsExtra() this.group.argsExtra, + get builtin() this.group.builtin, + + get name() this.group.name, + set name(val) this.group.name = val, + + get description() this.group.description, + set description(val) this.group.description = val, + + get filter() this.group.filter, + set filter(val) this.group.filter = val, + + get persist() this.group.persist, + set persist(val) this.group.persist = val, + + get toStringParams() [this.name] + }), + Hives: Class("Hives", Class.Property, { init: function init(name, constructor) { const self = this; @@ -100,6 +129,13 @@ var Contexts = Module("contexts", { this.builtinGroups = [this.system, this.user]; }, + destroy: function () { + for (let hive in values(this.groupList)) { + dactyl.trapErrors("cleanup", hive); + dactyl.trapErrors("destroy", hive); + } + }, + context: null, groups: Class.memoize(function () Object.create(Group.groupsProto, { @@ -145,6 +181,7 @@ var Contexts = Module("contexts", { if (group) { this.groupList.splice(this.groupList.indexOf(group), 1); group.cleanup(); + group.destroy(); } if (this.context && this.context.group === group) diff --git a/common/content/dactyl.js b/common/content/dactyl.js index cddbf583..ee2c9b3a 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1459,8 +1459,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, { events: function () { - events.addSessionListener(window, "click", dactyl.closure.onClick, true); - events.addSessionListener(window, "dactyl.execute", dactyl.closure.onExecute, true); + events.listen(window, "click", dactyl.closure.onClick, true); + events.listen(window, "dactyl.execute", dactyl.closure.onExecute, true); }, // Only general options are added here, which are valid for all Dactyl extensions options: function () { diff --git a/common/content/events.js b/common/content/events.js index 26217d4a..bebd5808 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -255,6 +255,54 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { } }); +var EventHive = Class("EventHive", Group.Hive, { + init: function init(group) { + init.supercall(this, group); + this.sessionListeners = []; + }, + + cleanup: function cleanup() { + this.unlisten(null); + }, + + /** + * Adds an event listener for this session and removes it on + * dactyl shutdown. + * + * @param {Element} target The element on which to listen. + * @param {string} event The event to listen for. + * @param {function} callback The function to call when the event is received. + * @param {boolean} capture When true, listen during the capture + * phase, otherwise during the bubbling phase. + */ + listen: function (target, event, callback, capture) { + let args = Array.slice(arguments, 0); + args[2] = this.wrapListener(callback); + args[0].addEventListener.apply(args[0], args.slice(1)); + args[0] = Cu.getWeakReference(args[0]); + this.sessionListeners.push(args); + }, + + /** + * Remove an event listener. + * + * @param {Element} target The element on which to listen. + * @param {string} event The event to listen for. + * @param {function} callback The function to call when the event is received. + * @param {boolean} capture When true, listen during the capture + * phase, otherwise during the bubbling phase. + */ + unlisten: function (target, event, callback, capture) { + this.sessionListeners = this.sessionListeners.filter(function (args) { + if (target == null || args[0].get() == target && args[1] == event && args[2] == callback && args[3] == capture) { + args[0].get().removeEventListener.apply(args[0].get(), args.slice(1)); + return true; + } + return !args[0].get(); + }); + } +}); + /** * @instance events */ @@ -283,7 +331,9 @@ var Events = Module("events", { this._macroKeys = []; this._lastMacro = ""; - this.sessionListeners = []; + EventHive.prototype.wrapListener = this.closure.wrapListener; + this.user = contexts.hives.events.user; + this.builtin = contexts.hives.events.builtin; this._macros = storage.newMap("macros", { privateData: true, store: true }); for (let [k, m] in this._macros) @@ -344,19 +394,14 @@ var Events = Module("events", { this._activeMenubar = false; for (let [event, callback] in Iterator(this.events)) - this.addSessionListener(window, event, callback, true); + this.listen(window, event, callback, true); dactyl.registerObserver("modeChange", function () { delete self.processor; }); }, - destroy: function () { - util.dump("Removing all event listeners"); - for (let args in values(this.sessionListeners)) - if (args[0].get()) - args[0].get().removeEventListener.apply(args[0].get(), args.slice(1)); - }, + hives: Group.Hives("events", EventHive), /** * Adds an event listener for this session and removes it on @@ -368,13 +413,8 @@ var Events = Module("events", { * @param {boolean} capture When true, listen during the capture * phase, otherwise during the bubbling phase. */ - addSessionListener: function (target, event, callback, capture) { - let args = Array.slice(arguments, 0); - args[2] = this.wrapListener(callback); - args[0].addEventListener.apply(args[0], args.slice(1)); - args[0] = Cu.getWeakReference(args[0]); - this.sessionListeners.push(args); - }, + get addSessionListener() this.builtin.closure.listen, + get listen() this.builtin.closure.listen, /** * Wraps an event listener to ensure that errors are reported. diff --git a/common/content/hints.js b/common/content/hints.js index 9a9a7502..7b6d6d52 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -677,7 +677,7 @@ var Hints = Module("hints", { let appContent = document.getElementById("appcontent"); if (appContent) - events.addSessionListener(appContent, "scroll", this.resizeTimer.closure.tell, false); + events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false); const Mode = Hints.Mode; Mode.defaultValue("tags", function () function () options["hinttags"]); diff --git a/common/content/mappings.js b/common/content/mappings.js index 3f05549a..9512e087 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -108,7 +108,7 @@ var Map = Class("Map", { .toObject(); args = update({ context: contexts.context }, - this.hive.group.argsExtra(args), + this.hive.argsExtra(args), args); let self = this; @@ -138,16 +138,12 @@ var Map = Class("Map", { id: 0 }); -var MapHive = Class("MapHive", { +var MapHive = Class("MapHive", Group.Hive, { init: function init(group) { - this.group = group; + init.supercall(this, group); this.stacks = {}; }, - get toStringParams() [this.group.name], - - get builtin() this.group.builtin, - /** * Iterates over all mappings present in all of the given *modes*. * @@ -423,7 +419,7 @@ var Mappings = Module("mappings", { template.map(maps(hive), function (map) template.map(map.names, function (name, i) - {!i ? hive.group.name : ""} + {!i ? hive.name : ""} {modeSign} {name} {map.rhs || map.action.toSource()} @@ -532,12 +528,12 @@ var Mappings = Module("mappings", { serialize: function () { return this.name != "map" ? [] : array(mappings.userHives) - .filter(function (h) h.group.persist) + .filter(function (h) h.persist) .map(function (hive) [ { command: "map", options: array([ - hive !== mappings.user && ["-group", hive.group.name], + hive !== mappings.user && ["-group", hive.name], ["-modes", uniqueModes(map.modes)], ["-description", map.description], map.silent && ["-silent"]]) @@ -678,7 +674,7 @@ var Mappings = Module("mappings", { name: name, columns: [ mode == mainMode ? "" : {mode.name}, - hive == mappings.builtin ? "" : {hive.group.name} + hive == mappings.builtin ? "" : {hive.name} ], __proto__: map }; diff --git a/common/content/marks.js b/common/content/marks.js index 729e23ac..b7df2c89 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -208,7 +208,7 @@ var Marks = Module("marks", { events: function () { let appContent = document.getElementById("appcontent"); if (appContent) - events.addSessionListener(appContent, "load", marks.closure._onPageLoad, true); + events.listen(appContent, "load", marks.closure._onPageLoad, true); }, mappings: function () { var myModes = config.browserModes; diff --git a/common/content/tabs.js b/common/content/tabs.js index 13ec4df4..d14d8bbc 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -874,8 +874,8 @@ var Tabs = Module("tabs", { tabs.timeout(function () { this.updateTabCount(); }); } for (let event in values(["TabMove", "TabOpen", "TabClose"])) - events.addSessionListener(tabContainer, event, callback, false); - events.addSessionListener(tabContainer, "TabSelect", tabs.closure._onTabSelect, false); + events.listen(tabContainer, event, callback, false); + events.listen(tabContainer, "TabSelect", tabs.closure._onTabSelect, false); }, mappings: function () { mappings.add([modes.NORMAL], ["g0", "g^"], diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index a7259259..aebdc528 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -93,6 +93,7 @@ var Hive = Class("Hive", { for (let sheet in values(this.sheets)) sheet.enabled = false; }, + destroy: function destroy() {}, __iterator__: function () Iterator(this.sheets), From 76aaf150fafd03ef1013beb4a62d48e387ab8a33 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 01:09:33 -0500 Subject: [PATCH 26/52] Merge default. --HG-- branch : groups --- common/content/events.js | 10 +++++++--- common/modules/base.jsm | 2 +- common/modules/javascript.jsm | 2 ++ common/modules/overlay.jsm | 18 +++++++++--------- common/tests/functional/testCommands.js | 19 ++++++++++++++++++- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/common/content/events.js b/common/content/events.js index bebd5808..71fef679 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -79,9 +79,11 @@ var ProcessorStack = Class("ProcessorStack", { Events.kill(this.events[this.events.length - 1]); if (result === Events.PASS || result === Events.ABORT) { - dbg("REFEED: " + this.events.filter(function (e) e.getPreventDefault()).map(events.closure.toString).join("")); - this.events.filter(function (e) e.getPreventDefault()) - .forEach(function (event, i) { + let list = this.events.filter(function (e) e.getPreventDefault()); + if (list.length) + events.dbg("REFEED: " + list.map(events.closure.toString).join("")); + + list.forEach(function (event, i) { let elem = event.originalTarget; if (event.originalTarget) { let doc = elem.ownerDocument || elem.document || elem; @@ -307,6 +309,8 @@ var EventHive = Class("EventHive", Group.Hive, { * @instance events */ var Events = Module("events", { + dbg: function () {}, + init: function () { const self = this; diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 1b1293bc..12f3c9be 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -1012,7 +1012,7 @@ var Timer = Class("Timer", { notify: function (timer, force) { try { - if (util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0) + if (loaded.util && util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0) return; this._timer.cancel(); diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm index d66f0323..e65f3a64 100644 --- a/common/modules/javascript.jsm +++ b/common/modules/javascript.jsm @@ -36,6 +36,8 @@ var JavaScript = Module("javascript", { }, }), + lazyInit: true, + newContext: function () this.modules.newContext(this.modules.userContext), get completers() JavaScript.completers, // For backward compatibility diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 1106ad2c..957e7773 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -291,18 +291,18 @@ var Overlay = Module("Overlay", { }); }); - function frob(name) { - // util.dump(" ======================== FROB " + name + " ======================== "); - (deferredInit[name] || []).forEach(call); - } + function frob(name) { (deferredInit[name] || []).forEach(call); } frob("init"); - defineModule.modules.forEach(function ({ constructor: { className } }) { - modules.__defineGetter__(className, function () { - delete modules[className]; + defineModule.modules.forEach(function ({ lazyInit, constructor: { className } }) { + if (!lazyInit) frob(className); - return modules[className] = modules[className]; - }); + else + modules.__defineGetter__(className, function () { + delete modules[className]; + frob(className); + return modules[className] = modules[className]; + }); }); // Module.list.forEach(load); diff --git a/common/tests/functional/testCommands.js b/common/tests/functional/testCommands.js index 62eefa5c..60fc7b8f 100644 --- a/common/tests/functional/testCommands.js +++ b/common/tests/functional/testCommands.js @@ -483,7 +483,24 @@ var tests = { sanitize: { // Skip details for now. completions: [ - "", + ["", function (context) ["all", + "cache", + "downloads", + "formdata", + "offlineapps", + "passwords", + "sessions", + "cookies", + "history", + "host", + "sitesettings", + "commandline", + "messages", + "macros", + "marks", + "options" + ].every(function (item) context.allItems.items.some(function ({ text }) item == text)) + ], "-", "-host=", "-timespan=" From 3a40882db44b1953268f1476addb3feb64df05c2 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 02:16:19 -0500 Subject: [PATCH 27/52] Fix some bugs. --HG-- branch : groups --- common/content/contexts.js | 8 ++++++-- common/content/marks.js | 10 +++++----- common/modules/io.jsm | 3 --- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 9e57e90f..2b4dace4 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -134,6 +134,10 @@ var Contexts = Module("contexts", { dactyl.trapErrors("cleanup", hive); dactyl.trapErrors("destroy", hive); } + + for (let plugin in values(plugins.contexts)) + if (plugin.onUnload) + dactyl.trapErrors("onUnload", plugin); }, context: null, @@ -162,8 +166,8 @@ var Contexts = Module("contexts", { this.groupList.unshift(group); this.groupMap[name] = group; this.hiveProto.__defineGetter__(name, function () group[this._hive]); - delete this.groups; } + delete this.groups; return group; }, @@ -309,7 +313,7 @@ var Contexts = Module("contexts", { if (!group) group = contexts.addGroup((isRuntime ? "" : "script-") + - commands.nameRegexp.iterate(path.replace(/\..*/, "")) + commands.nameRegexp.iterate(path.replace(/\.[^.]*$/, "")) .join("-"), "Script group for " + file.path, null, false); diff --git a/common/content/marks.js b/common/content/marks.js index b7df2c89..73a9805d 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -288,17 +288,17 @@ var Marks = Module("marks", { function matchhost(url) !host || util.isDomainURL(url, host); function match(marks) (k for ([k, v] in Iterator(marks)) if (timespan.contains(v.timestamp) && matchhost(v.location))); - for (let [url, local] in storage["local-marks"]) + for (let [url, local] in marks._localMarks) if (matchhost(url)) { for (let key in match(local)) delete local[key]; if (!Object.keys(local).length) - storage["local-marks"].remove(url); + marks._localMarks.remove(url); } - storage["local-marks"].changed(); + marks._localMarks.changed(); - for (let key in match(storage["url-marks"])) - storage["url-marks"].remove(key); + for (let key in match(marks._urlMarks)) + marks._urlMarks.remove(key); } }); } diff --git a/common/modules/io.jsm b/common/modules/io.jsm index f4abbfdf..57d5f8be 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -84,9 +84,6 @@ var IO = Module("io", { destroy: function destroy() { services.downloadManager.removeListener(this.downloadListener); - for (let [, plugin] in Iterator(plugins.contexts)) - if (plugin.onUnload) - plugin.onUnload(); }, /** From 5be9f3e85c5dc8a1bf210f360701299dc38c4d2e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 02:32:15 -0500 Subject: [PATCH 28/52] Fix bloody Minefield issue. --HG-- branch : groups --- common/modules/config.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 6e5645b2..4da97e04 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -31,7 +31,7 @@ var ConfigBase = Class("ConfigBase", { iter(config.dtdExtra, (["dactyl." + k, v] for ([k, v] in iter(config.dtd))), (["dactyl." + s, config[s]] for each (s in config.dtdStrings))) - .map(function ([k, v]) [""].join("")) + .map(function ([k, v]) [""].join("")) .join("\n")] }); }, From a8ef60f4d5050e54760a4e2eda92c19a5a63c0cf Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 03:02:40 -0500 Subject: [PATCH 29/52] Don't be so damned loud about errors during :sourcing now that we're no longer aping Vim. --HG-- branch : groups --- common/content/commands.js | 9 ++------- common/modules/io.jsm | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/common/content/commands.js b/common/content/commands.js index 86d2e670..9f90d21d 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -672,13 +672,8 @@ var Commands = Module("commands", { 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 + ":"); - } + if (!silent) { + e.message = context.file + ":" + context.line + ": " + e.message; dactyl.reportError(e, true); } } diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 57d5f8be..9727d66f 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -188,7 +188,7 @@ var IO = Module("io", { styles.registerSheet(uri.spec, false, true); else { context = Contexts.Context(file, params.group); - modules.commands.execute(file.read(), null, params.silent || "loud", + modules.commands.execute(file.read(), null, params.silent, null, { context: context, file: file.path, From 27f973a4d0bcd93d8cbca140666e5d3be56bd49e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 04:13:57 -0500 Subject: [PATCH 30/52] Fix bugs. Add optional bang to :loadplugins. --HG-- branch : groups --- common/content/contexts.js | 15 +++++++++++---- common/content/dactyl.js | 7 ++++--- common/content/events.js | 24 ++++++++++-------------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index 2b4dace4..c2a9806a 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -103,6 +103,7 @@ var Group = Class("Group", { memoize(Group.prototype, name, function () { let group = constructor(this); this.hives.push(group); + delete contexts.groups; return group; }); @@ -126,7 +127,7 @@ var Contexts = Module("contexts", { this.builtin = this.addGroup("builtin", "Builtin items"); this.user = this.addGroup("user", "User-defined items", null, true); - this.builtinGroups = [this.system, this.user]; + this.builtinGroups = [this.builtin, this.user]; }, destroy: function () { @@ -157,11 +158,16 @@ var Contexts = Module("contexts", { get hives() Group.hiveMap, addGroup: function addGroup(name, description, filter, persist, replace) { + let group = this.getGroup(name); + if (group) + name = group.name; + if (replace) this.removeGroup(name); - let group = this.groupMap[name]; - if (!group) { + if (!group || replace) { + dactyl.assert(name !== "default", "Illegal group name"); + group = Group(name, description, filter, persist); this.groupList.unshift(group); this.groupMap[name] = group; @@ -183,6 +189,7 @@ var Contexts = Module("contexts", { dactyl.assert(!group || !group.builtin, "Cannot remove builtin group"); if (group) { + name = group.name; this.groupList.splice(this.groupList.indexOf(group), 1); group.cleanup(); group.destroy(); @@ -201,7 +208,7 @@ var Contexts = Module("contexts", { if (name === "default") var group = this.context && this.context.context && this.context.context.GROUP; else - group = array.nth(this.groupList, function (h) h.name == name, 0) || null; + group = set.has(this.groupMap, name) && this.groupMap[name]; if (group && hive) return group[hive]; diff --git a/common/content/dactyl.js b/common/content/dactyl.js index ee2c9b3a..70488de8 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1031,7 +1031,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { get: function globalVariables() this._globalVariables }), - loadPlugins: function (args) { + loadPlugins: function (args, force) { function sourceDirectory(dir) { dactyl.assert(dir.isReadable(), "E484: Can't open file " + dir.path); @@ -1042,7 +1042,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) } dir.readDirectory(true).forEach(function (file) { - if (file.isFile() && loadplugins.getKey(file.path) && !(file.path in dactyl.pluginFiles)) { + if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) { try { io.source(file.path); dactyl.pluginFiles[file.path] = true; @@ -1756,10 +1756,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { commands.add(["loadplugins", "lpl"], "Load all plugins immediately", function (args) { - dactyl.loadPlugins(args.length ? args : null); + dactyl.loadPlugins(args.length ? args : null, args.bang); }, { argCount: "*", + bang: true, keepQuotes: true, serialGroup: 10, serialize: function () [ diff --git a/common/content/events.js b/common/content/events.js index 71fef679..bdef70e8 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -38,8 +38,6 @@ var ProcessorStack = Class("ProcessorStack", { }, execute: function execute(result, force) { - function dbg() {} - if (force && this.actions.length) this.processors.length = 0; @@ -71,15 +69,15 @@ var ProcessorStack = Class("ProcessorStack", { else if (result === undefined) result = Events.PASS; - dbg("RESULT: " + (result === Events.KILL ? "KILL" : - result === Events.PASS ? "PASS" : - result === Events.ABORT ? "ABORT" : result)); + events.dbg("RESULT: " + (result === Events.KILL ? "KILL" : + result === Events.PASS ? "PASS" : + result === Events.ABORT ? "ABORT" : result)); if (result !== Events.PASS) Events.kill(this.events[this.events.length - 1]); if (result === Events.PASS || result === Events.ABORT) { - let list = this.events.filter(function (e) e.getPreventDefault()); + let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented); if (list.length) events.dbg("REFEED: " + list.map(events.closure.toString).join("")); @@ -102,8 +100,6 @@ var ProcessorStack = Class("ProcessorStack", { }, process: function process(event) { - function dbg() {} - if (this.timer) this.timer.cancel(); @@ -115,15 +111,15 @@ var ProcessorStack = Class("ProcessorStack", { let actions = []; let processors = []; - dbg("\n\n"); - dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro); + events.dbg("\n\n"); + events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro); for (let [i, input] in Iterator(this.processors)) { let res = input.process(event); if (res !== Events.ABORT) var result = res; - dbg("RES: " + input + " " + (callable(res) ? {}.toString.call(res) : res)); + events.dbg("RES: " + input + " " + (callable(res) ? {}.toString.call(res) : res)); if (res === Events.KILL) break; @@ -139,9 +135,9 @@ var ProcessorStack = Class("ProcessorStack", { processors.push(input); } - dbg("RESULT: " + (callable(result) ? {}.toString.call(result) : result) + " " + event.getPreventDefault()); - dbg("ACTIONS: " + actions.length + " " + this.actions.length); - dbg("PROCESSORS:", processors); + events.dbg("RESULT: " + (callable(result) ? {}.toString.call(result) : result) + " " + event.getPreventDefault()); + events.dbg("ACTIONS: " + actions.length + " " + this.actions.length); + events.dbg("PROCESSORS:", processors); this._actions = actions; this.actions = actions.concat(this.actions); From 68b42e466e190c1d02847429a58032ca43ddacaa Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 13:22:24 -0500 Subject: [PATCH 31/52] Unhide COMMAND mode. --HG-- branch : groups --- common/content/modes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/content/modes.js b/common/content/modes.js index 560f616d..3b2ca4a3 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -47,8 +47,7 @@ var Modes = Module("modes", { count: false }); this.addMode("COMMAND", { - description: "The base mode for most modes which accept commands rather than input", - hidden: true + description: "The base mode for most modes which accept commands rather than input" }); this.addMode("NORMAL", { From 9cb22db8044ec1694fbf47772d58d6bc3a858f77 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 16:35:49 -0500 Subject: [PATCH 32/52] Fix horrible performance problems with multiple MOW echoes. --HG-- branch : groups --- common/content/mow.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/content/mow.js b/common/content/mow.js index 8a130fbb..5cb6709e 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -9,6 +9,8 @@ var MOW = Module("mow", { init: function () { + this._resize = Timer(20, 400, function (force) { this.resize(force) }, this); + let fontSize = util.computedStyle(document.documentElement).fontSize; styles.system.add("font-size", "dactyl://content/buffer.xhtml", "body { font-size: " + fontSize + "; } \ @@ -112,8 +114,7 @@ var MOW = Module("mow", { // that don't generate output are executed if (this.widgets.mowContainer.collapsed) { this.body.scrollTop = 0; - while (body.firstChild) - body.removeChild(body.firstChild); + body.textContent = ""; } body.appendChild(output); @@ -122,7 +123,7 @@ var MOW = Module("mow", { if (!silent) dactyl.triggerObserver("echoMultiline", data, highlightGroup, output); - this.resize(true); + this._resize.tell(true); if (options["more"] && this.isScrollable(1)) { // start the last executed command's output at the top of the screen From f3ca43cea9bda9c67a4d48531e5134fc39563a6c Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 17:17:34 -0500 Subject: [PATCH 33/52] Last commit, only better and with collateral fixes. --HG-- branch : groups --- common/content/events.js | 22 ++++++++++----- common/content/mow.js | 58 +++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/common/content/events.js b/common/content/events.js index bdef70e8..aaa81b63 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -274,11 +274,20 @@ var EventHive = Class("EventHive", Group.Hive, { * phase, otherwise during the bubbling phase. */ listen: function (target, event, callback, capture) { - let args = Array.slice(arguments, 0); - args[2] = this.wrapListener(callback); - args[0].addEventListener.apply(args[0], args.slice(1)); - args[0] = Cu.getWeakReference(args[0]); - this.sessionListeners.push(args); + if (isObject(event)) + var [self, events] = [event, event[callback]]; + else + [self, events] = [null, array.toObject([[event, callback]])]; + + for (let [event, callback] in Iterator(events)) { + let args = [Cu.getWeakReference(target), + event, + this.wrapListener(callback, self), + capture]; + + target.addEventListener.apply(target, args.slice(1)); + this.sessionListeners.push(args); + } }, /** @@ -393,8 +402,7 @@ var Events = Module("events", { } this._activeMenubar = false; - for (let [event, callback] in Iterator(this.events)) - this.listen(window, event, callback, true); + this.listen(window, this, "events"); dactyl.registerObserver("modeChange", function () { delete self.processor; diff --git a/common/content/mow.js b/common/content/mow.js index 5cb6709e..06a93d94 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -9,7 +9,30 @@ var MOW = Module("mow", { init: function () { - this._resize = Timer(20, 400, function (force) { this.resize(force) }, this); + this._resize = Timer(20, 400, function () { + if (this.visible) { + this.resize(false); + this.updateMorePrompt(); + } + }, this); + + this._timer = Timer(20, 400, function () { + util.dump("RESIZE " + this.window.document.body.textContent); + this.resize(true); + + if (options["more"] && this.isScrollable(1)) { + // start the last executed command's output at the top of the screen + let elements = this.document.getElementsByClassName("ex-command-output"); + elements[elements.length - 1].scrollIntoView(true); + } + else + this.body.scrollTop = this.body.scrollHeight; + + dactyl.focus(this.window); + this.updateMorePrompt(); + }, this); + + events.listen(window, this, "windowEvents"); let fontSize = util.computedStyle(document.documentElement).fontSize; styles.system.add("font-size", "dactyl://content/buffer.xhtml", @@ -112,7 +135,7 @@ var MOW = Module("mow", { // FIXME: need to make sure an open MOW is closed when commands // that don't generate output are executed - if (this.widgets.mowContainer.collapsed) { + if (!this.visible) { this.body.scrollTop = 0; body.textContent = ""; } @@ -123,18 +146,9 @@ var MOW = Module("mow", { if (!silent) dactyl.triggerObserver("echoMultiline", data, highlightGroup, output); - this._resize.tell(true); - - if (options["more"] && this.isScrollable(1)) { - // start the last executed command's output at the top of the screen - let elements = this.document.getElementsByClassName("ex-command-output"); - elements[elements.length - 1].scrollIntoView(true); - } - else - this.body.scrollTop = this.body.scrollHeight; - - dactyl.focus(this.window); - this.updateMorePrompt(); + this._timer.tell(); + if (!this.visible) + this._timer.flush(); }, events: { @@ -172,6 +186,12 @@ var MOW = Module("mow", { } }, + windowEvents: { + resize: function onResize(event) { + this._resize.tell(); + } + }, + contextEvents: { popupshowing: function (event) { let menu = commandline.widgets.contextMenu; @@ -208,14 +228,14 @@ var MOW = Module("mow", { * already so. */ resize: function updateOutputHeight(open, extra) { - if (!open && this.widgets.mowContainer.collapsed) + if (!(open || this.visible)) return; let doc = this.widget.contentDocument; let availableHeight = config.outputHeight; - if (!this.widgets.mowContainer.collapsed) - availableHeight += parseFloat(this.widgets.mowContainer.height); + if (this.visible) + availableHeight += parseFloat(this.widgets.mowContainer.height || 0); availableHeight -= extra || 0; doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px"; @@ -225,6 +245,7 @@ var MOW = Module("mow", { 0); doc.body.style.minWidth = ""; + this.visible = true; }, @@ -243,7 +264,7 @@ var MOW = Module("mow", { * and what they do. */ updateMorePrompt: function updateMorePrompt(force, showHelp) { - if (this.widgets.mowContainer.collapsed) + if (!this.visible) return this.widgets.message = null; let elem = this.widget.contentDocument.documentElement; @@ -256,6 +277,7 @@ var MOW = Module("mow", { }, visible: Modes.boundProperty({ + get: function get_mowVisible() !this.widgets.mowContainer.collapsed, set: function set_mowVisible(value) { this.widgets.mowContainer.collapsed = !value; From a94832ef1576c691b1703b0fc7b0ecbe3b068022 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 17:20:14 -0500 Subject: [PATCH 34/52] Kill useless dump statements. :( --HG-- branch : groups --- common/content/mow.js | 1 - 1 file changed, 1 deletion(-) diff --git a/common/content/mow.js b/common/content/mow.js index 06a93d94..24d65da3 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -17,7 +17,6 @@ var MOW = Module("mow", { }, this); this._timer = Timer(20, 400, function () { - util.dump("RESIZE " + this.window.document.body.textContent); this.resize(true); if (options["more"] && this.isScrollable(1)) { From ee03cbd2ce56b0f00f44c2a461adbd300c1ff943 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 18:34:48 -0500 Subject: [PATCH 35/52] Fix some bugs. --HG-- branch : groups --- common/content/commands.js | 7 +++++-- common/content/dactyl.js | 1 - common/content/events.js | 8 +++++--- common/content/modes.js | 2 +- common/modules/finder.jsm | 15 +++++---------- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/common/content/commands.js b/common/content/commands.js index 9f90d21d..7d67ecd0 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -156,7 +156,10 @@ var Command = Class("Command", { throw FailedAssertion("E477: No ! allowed"); return !dactyl.trapErrors(function exec() { - update({}, this.hive.argsExtra(args), args); + let extra = this.hive.argsExtra(args); + for (let k in properties(extra)) + if (!(k in args)) + Object.defineProperty(args, k, Object.getOwnPropertyDescriptor(extra, k)); if (this.always) this.always(args, modifiers); @@ -1339,7 +1342,7 @@ var Commands = Module("commands", { args["-description"], contexts.bindMacro(args, "-ex", function makeParams(args, modifiers) ({ - args: { + args: { __proto__: args, toString: function () this.string, }, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 70488de8..88156d0d 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1358,7 +1358,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { */ reportError: function reportError(error, echo) { if (error instanceof FailedAssertion || error.message === "Interrupted") { - let context = contexts.context; let prefix = context ? context.file + ":" + context.line + ": " : ""; if (error.message && error.message.indexOf(prefix) !== 0) diff --git a/common/content/events.js b/common/content/events.js index aaa81b63..46e8775a 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -55,8 +55,10 @@ var ProcessorStack = Class("ProcessorStack", { events.feedingKeys = false; } - for (var res = this.actions[0]; callable(res);) - res = res(); + for (var res = this.actions[0]; callable(res);) { + res = dactyl.trapErrors(res); + events.dbg("ACTION RES: " + res); + } result = res === Events.PASS ? Events.PASS : Events.KILL; } else if (result !== Events.KILL && !this.actions.length && @@ -402,7 +404,7 @@ var Events = Module("events", { } this._activeMenubar = false; - this.listen(window, this, "events"); + this.listen(window, this, "events", true); dactyl.registerObserver("modeChange", function () { delete self.processor; diff --git a/common/content/modes.js b/common/content/modes.js index 3b2ca4a3..b9ce2fc9 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -423,7 +423,7 @@ var Modes = Module("modes", { hidden: false, - input: false, + input: Class.memoize(function () this.bases.length && this.bases.some(function (b) b.input)), get passUnknown() this.input, diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm index 1e959f27..b5c0693e 100644 --- a/common/modules/finder.jsm +++ b/common/modules/finder.jsm @@ -165,22 +165,16 @@ var RangeFinder = Module("rangefinder", { modes: function (dactyl, modules, window) { const { modes } = modules; modes.addMode("FIND", { - extended: true, description: "Find mode, active when typing search input", bases: [modes.COMMAND_LINE], - input: true }); modes.addMode("FIND_FORWARD", { - extended: true, description: "Forward Find mode, active when typing search input", - bases: [modes.FIND], - input: true + bases: [modes.FIND] }); modes.addMode("FIND_BACKWARD", { - extended: true, description: "Backward Find mode, active when typing search input", - bases: [modes.FIND], - input: true + bases: [modes.FIND] }); }, commands: function (dactyl, modules, window) { @@ -191,7 +185,8 @@ var RangeFinder = Module("rangefinder", { { argCount: "0" }); }, commandline: function (dactyl, modules, window) { - this.CommandMode = Class("CommandFindMode", modules.CommandMode, { + const { rangefinder } = modules; + rangefinder.CommandMode = Class("CommandFindMode", modules.CommandMode, { init: function init(mode) { this.mode = mode; init.supercall(this); @@ -201,7 +196,7 @@ var RangeFinder = Module("rangefinder", { get prompt() this.mode === modules.modes.FIND_BACKWARD ? "?" : "/", - get onCancel() modules.rangefinder.closure.onCancel, + get onCancel() rangefinder.closure.onCancel, get onChange() modules.rangefinder.closure.onChange, get onSubmit() modules.rangefinder.closure.onSubmit }); From e9a18c1a5a61f68578ead11600284b9560169471 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 19:54:42 -0500 Subject: [PATCH 36/52] Bake hg version into install.rdf on XPI build. Warn users who are running in a testing branch. --HG-- branch : groups --- common/Makefile | 19 +++++++++++++++---- common/content/buffer.js | 6 ------ common/content/mow.js | 6 ++++-- common/modules/config.jsm | 30 ++++++++++++++++++++++++++++-- common/modules/overlay.jsm | 1 + 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/common/Makefile b/common/Makefile index 40012c05..c551ec66 100644 --- a/common/Makefile +++ b/common/Makefile @@ -32,7 +32,7 @@ CHROME = $(MANGLE)/ JAR = $(CHROME)$(NAME).jar XPI_BASES = $(JAR_BASES) $(TOP)/.. -XPI_FILES = bootstrap.js install.rdf TODO AUTHORS Donors NEWS LICENSE.txt +XPI_FILES = bootstrap.js TODO AUTHORS Donors NEWS LICENSE.txt XPI_DIRS = components $(MANGLE) defaults XPI_TEXTS = js jsm $(JAR_TEXTS) XPI_BINS = $(JAR_BINS) @@ -49,6 +49,9 @@ BUILD_DIR = build.$(VERSION).$(OS) AWK ?= awk B64ENCODE ?= base64 CURL ?= curl +SED := $(shell if [ "xoo" = x$$(echo foo | sed -E 's/f(o)/\1/' 2>/dev/null) ]; \ + then echo sed -E; else echo sed -r; \ + fi) .SILENT: @@ -174,11 +177,19 @@ test: xpi xpi: $(CHROME) @echo "Building XPI..." mkdir -p "$(XPI_PATH)" - + $(AWK) -v 'name=$(NAME)' -v 'suffix=$(MANGLE)' \ -f $(BASE)/process_manifest.awk \ - $(TOP)/chrome.manifest >"$(XPI_PATH)/chrome.manifest" - + "$(TOP)/chrome.manifest" >"$(XPI_PATH)/chrome.manifest" + + version="$(VERSION)"; \ + hg root >/dev/null 2>&1 && \ + case "$$version" in \ + *pre) version="$$version-hg$$(hg log -r . --template '{rev}')-$$(hg branch)";; \ + esac; \ + $(SED) -e 's/(em:version(>|="))([^"<]+)/\1'"$$version/" \ + <"$(TOP)/install.rdf" >"$(XPI_PATH)/install.rdf" + $(MAKE_JAR) "$(XPI)" "$(XPI_BASES)" "$(XPI_DIRS)" "$(XPI_TEXTS)" "$(XPI_BINS)" "$(XPI_FILES)" rm -r -- $(CHROME) @echo "Built XPI: $(XPI)" diff --git a/common/content/buffer.js b/common/content/buffer.js index 4b691261..c919b517 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -259,8 +259,6 @@ var Buffer = Module("buffer", { * @property {Object} The document loading progress listener. */ progressListener: { - dactylLoadCount: 0, - // XXX: function may later be needed to detect a canceled synchronous openURL() onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) { onStateChange.superapply(this, arguments); @@ -273,10 +271,6 @@ var Buffer = Module("buffer", { statusline.progress = 0; buffer._triggerLoadAutocmd("PageLoadPre", webProgress.DOMWindow.document); - - if (document.commandDispatcher.focusedWindow == webProgress.DOMWindow && this.dactylLoadCount++) - util.timeout(function () { modes.reset(false); }, - modes.main == modes.HINTS ? 500 : 0); } else if (flags & Ci.nsIWebProgressListener.STATE_STOP) { // Workaround for bugs 591425 and 606877, dactyl bug #81 diff --git a/common/content/mow.js b/common/content/mow.js index 24d65da3..9fdd6cee 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -80,8 +80,8 @@ var MOW = Module("mow", { widgets: Class.memoize(function () commandline.widgets), body: Class.memoize(function () this.widget.contentDocument.documentElement), - document: Class.memoize(function () this.widget.contentDocument), - window: Class.memoize(function () this.widget.contentWindow), + get document() this.widget.contentDocument, + get window() this.widget.contentWindow, /** * Display a multi-line message. @@ -90,6 +90,8 @@ var MOW = Module("mow", { * @param {string} highlightGroup */ echo: function echo(data, highlightGroup, silent) { + this.document; + util.dump(String(this.widget), String(this.window), String(this.document)); let body = this.document.body; this.widgets.message = null; diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 4da97e04..ea88bbc1 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -115,6 +115,23 @@ var ConfigBase = Class("ConfigBase", { .nth(function (l) set.has(langs, l), 0); }, + haveHg: Class.memoize(function () { + if (/pre$/.test(this.addon.version)) { + let uri = this.addon.getResourceURI("../.hg"); + if (uri instanceof Ci.nsIFileURL && + uri.QueryInterface(Ci.nsIFileURL).file.exists() && + io.pathSearch("hg")) + return ["hg", "-R", uri.file.parent.path]; + } + return null; + }), + + branch: Class.memoize(function () { + if (this.haveHg) + return io.system(this.haveHg.concat(["branch"])).output; + return (/pre-hg\d+-(.*)$/.exec(this.version) || [])[1]; + }), + /** @property {string} The Dactyl version string. */ version: Class.memoize(function () { if (/pre$/.test(this.addon.version)) { @@ -124,11 +141,11 @@ var ConfigBase = Class("ConfigBase", { io.pathSearch("hg")) { return io.system(["hg", "-R", uri.file.parent.path, "log", "-r.", - "--template=hg{rev} ({date|isodate})"]).output; + "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output; } } let version = this.addon.version; - if ("@DATE" !== "@" + "DATE@") + if ("@DATE@" !== "@" + "DATE@") version += " (created: @DATE@)"; return version; }), @@ -723,6 +740,15 @@ config.INIT = update(Object.create(config.INIT), config.INIT, { {"}"}); img = null; }; + }, + + load: function load(dactyl, modules, window) { + load.superapply(this, arguments); + + if (this.branch && this.branch !== "default" && + modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1) + dactyl.warn("You are running " + config.appName + " from a testing branch: " + this.branch + ". " + + "Please do not report errors which do not also occur in the default branch."); } }); diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 957e7773..af88c6be 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -102,6 +102,7 @@ var Overlay = Module("Overlay", { const jsmodules = { NAME: "jsmodules" }; const modules = update(create(jsmodules), { + yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [], jsmodules: jsmodules, From 4b4925fdaa694fd293023853c672b5901d9fc9c3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 20:39:13 -0500 Subject: [PATCH 37/52] Fix bug. --HG-- branch : groups --- common/content/commandline.js | 9 +++++++-- common/content/events.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/common/content/commandline.js b/common/content/commandline.js index 1af40583..2a669521 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -986,7 +986,9 @@ var CommandLine = Module("commandline", { dactyl.registerObserver("events.doneFeeding", this.closure.onDoneFeeding, true); this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) { - if (!events.feedingKeys && options["autocomplete"].length) { + if (events.feedingKeys) + this.ignoredCount++; + if (options["autocomplete"].length) { this.complete(true, false); this.itemList.visible = true; } @@ -1004,8 +1006,11 @@ var CommandLine = Module("commandline", { this.itemList.visible = false; }, + ignoredCount: 0, onDoneFeeding: function onDoneFeeding() { - this.autocompleteTimer.flush(true); + if (this.ignoredCount) + this.autocompleteTimer.flush(true); + this.ignoredCount = 0; }, UP: {}, diff --git a/common/content/events.js b/common/content/events.js index 46e8775a..c9908923 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -1105,7 +1105,7 @@ var Events = Module("events", { // Hack to deal with and so forth not dispatching input // events - if (event.originalTarget instanceof HTMLInputElement && !modes.main.passthrough) { + if (key && event.originalTarget instanceof HTMLInputElement && !modes.main.passthrough) { let elem = event.originalTarget; elem.dactylKeyPress = elem.value; util.timeout(function () { From 51a9fc8deafe7bebe3d6f1cd135be4324a0df9dc Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 7 Feb 2011 23:51:29 -0500 Subject: [PATCH 38/52] Add contexts.withContext. --HG-- branch : groups --- common/content/commandline.js | 8 ++++---- common/content/commands.js | 6 ++---- common/content/contexts.js | 8 +++++++- common/content/mow.js | 2 -- common/modules/io.jsm | 3 +-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/common/content/commandline.js b/common/content/commandline.js index 003d1b3d..f2926ace 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -424,10 +424,10 @@ var CommandExMode = Class("CommandExMode", CommandMode, { }, onSubmit: function onSubmit(command) { - io.withSavedValues(["readHeredoc"], function () { - this.readHeredoc = commandline.readHeredoc; - contexts.withSavedValues(["context"], function () { - this.context = { file: "[Command Line]", line: 1 }; + contexts.withContext({ file: "[Command Line]", line: 1 }, + function () { + io.withSavedValues(["readHeredoc"], function () { + this.readHeredoc = commandline.readHeredoc; commands.repeat = command; dactyl.execute(command); }); diff --git a/common/content/commands.js b/common/content/commands.js index 7d67ecd0..c9118a6a 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -635,10 +635,8 @@ var Commands = Module("commands", { * command string. */ execute: function (string, tokens, silent, args, context) { - contexts.withSavedValues(["context"], function () { - context = update({}, context || this.context || { file: "[Command Line]", line: 1 }); - this.context = context; - + contexts.withContext(context || this.context || { file: "[Command Line]", line: 1 }, + function (context) { io.withSavedValues(["readHeredoc"], function () { this.readHeredoc = function (end) { let res = []; diff --git a/common/content/contexts.js b/common/content/contexts.js index c2a9806a..bed6f126 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -270,7 +270,13 @@ var Contexts = Module("contexts", { get default() (contexts.context && contexts.context.group || contexts.user)[name], completer: function (context) completion.group(context) - }) + }), + + withContext: function withContext(defaults, callback, self) + this.withSavedValues(["context"], function () { + this.context = defaults && update({}, defaults); + return callback.call(self, this.context); + }) }, { Context: modules.Script = function Context(file, group, args) { function Const(val) Class.Property({ enumerable: true, value: val }); diff --git a/common/content/mow.js b/common/content/mow.js index 9fdd6cee..e526efda 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -90,8 +90,6 @@ var MOW = Module("mow", { * @param {string} highlightGroup */ echo: function echo(data, highlightGroup, silent) { - this.document; - util.dump(String(this.widget), String(this.window), String(this.document)); let body = this.document.body; this.widgets.message = null; diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 522b2832..b249f22a 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -150,8 +150,7 @@ var IO = Module("io", { params = params || {}; let time = Date.now(); - return contexts.withSavedValues(["context"], function _source() { - contexts.context = null; + return contexts.withContext(null, function () { try { var file = util.getFile(filename) || io.File(filename); From 863c10df7a0071ceb4036ef647107a1bd1eb59e9 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 8 Feb 2011 20:53:11 -0500 Subject: [PATCH 39/52] Add reference counts for globally active style hives. --HG-- branch : groups --- common/content/contexts.js | 19 ++++++++++---- common/modules/base.jsm | 6 ++++- common/modules/highlight.jsm | 3 +-- common/modules/styles.jsm | 49 ++++++++++++++++++++++++++++-------- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index bed6f126..b1494f3f 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -130,11 +130,14 @@ var Contexts = Module("contexts", { this.builtinGroups = [this.builtin, this.user]; }, - destroy: function () { - for (let hive in values(this.groupList)) { + cleanup: function () { + for (let hive in values(this.groupList)) dactyl.trapErrors("cleanup", hive); + }, + + destroy: function () { + for (let hive in values(this.groupList)) dactyl.trapErrors("destroy", hive); - } for (let plugin in values(plugins.contexts)) if (plugin.onUnload) @@ -191,8 +194,7 @@ var Contexts = Module("contexts", { if (group) { name = group.name; this.groupList.splice(this.groupList.indexOf(group), 1); - group.cleanup(); - group.destroy(); + dactyl.trapErrors("destroy", group); } if (this.context && this.context.group === group) @@ -295,18 +297,25 @@ var Contexts = Module("contexts", { } else { let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-") : file.leafName; + self = update(modules.newContext.apply(null, args || [userContext]), { NAME: Const(name.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())), + PATH: Const(file.path), + CONTEXT: Const(self), + unload: Const(function unload() { if (plugins[this.NAME] === this || plugins[this.PATH] === this) if (this.onUnload) this.onUnload(); + if (plugins[this.NAME] === this) delete plugins[this.NAME]; + if (plugins[this.PATH] === this) delete plugins[this.PATH]; + if (!this.GROUP.builtin) contexts.removeGroup(this.GROUP); }) diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 12f3c9be..614b0aa7 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -61,6 +61,8 @@ if (!Object.defineProperties) for (let [k, v] in Iterator(props)) Object.defineProperty(obj, k, v); } +if (!Object.freeze) + Object.freeze = function freeze(obj) {}; if (!Object.getOwnPropertyDescriptor) Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(obj, prop) { if (!hasOwnProperty.call(obj, prop)) @@ -101,13 +103,15 @@ if (!Object.keys) Object.keys = function keys(obj) Object.getOwnPropertyNames(obj).filter(function (k) objproto.propertyIsEnumerable.call(obj, k)); +let getGlobalForObject = Cu.getGlobalForObject || function (obj) obj.__parent__; + let use = {}; let loaded = {}; let currentModule; let global = this; function defineModule(name, params, module) { if (!module) - module = Cu.getGlobalForObject ? Cu.getGlobalForObject(params) : params.__parent__; + module = getGlobalForObject(params); module.NAME = name; module.EXPORTED_SYMBOLS = params.exports || []; diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm index 02d8e15b..dbe669d4 100644 --- a/common/modules/highlight.jsm +++ b/common/modules/highlight.jsm @@ -23,8 +23,7 @@ Highlight.liveProperty = function (name, prop) { val = Array.slice(val); else val = update({}, val); - if (Object.freeze) - Object.freeze(val); + Object.freeze(val); } this.set(name, val); diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index aebdc528..c9f59d6d 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -23,7 +23,7 @@ Sheet.liveProperty = function (name) { this.prototype.__defineSetter__(name, function (val) { if (isArray(val)) val = Array.slice(val); - if (isArray(val) && Object.freeze) + if (isArray(val)) Object.freeze(val); this[i] = val; this.enabled = this.enabled; @@ -87,13 +87,25 @@ var Hive = Class("Hive", { this.name = name; this.sheets = []; this.names = {}; + this.refs = []; + }, + + addRef: function (obj) { + this.refs.push(Cu.getWeakReference(obj)); + this.dropRef(null); + }, + dropRef: function (obj) { + this.refs = this.refs.filter(function (ref) ref.get() && ref.get() !== obj); + if (!this.refs.length) { + this.cleanup(); + styles.hives = styles.hives.filter(function (h) h !== this, this); + } }, cleanup: function cleanup() { for (let sheet in values(this.sheets)) sheet.enabled = false; }, - destroy: function destroy() {}, __iterator__: function () Iterator(this.sheets), @@ -220,7 +232,6 @@ var Hive = Class("Hive", { }, }); -try { /** * Manages named and unnamed user style sheets, which apply to both * chrome and content pages. @@ -230,7 +241,6 @@ try { var Styles = Module("Styles", { init: function () { this._id = 0; - this.hives = []; this.cleanup(); this.allSheets = {}; @@ -244,17 +254,20 @@ var Styles = Module("Styles", { cleanup: function cleanup() { for each (let hive in this.hives) - hive.cleanup(); - this.user = this.addHive("user"); - this.system = this.addHive("system"); + util.trapErrors("cleanup", hive); + this.hives = []; + this.user = this.addHive("user", this); + this.system = this.addHive("system", this); }, - addHive: function addHive(name) { + addHive: function addHive(name, ref) { let hive = array.nth(this.hives, function (h) h.name === name, 0); if (!hive) { hive = Hive(name); this.hives.push(hive); } + if (ref) + hive.addRef(ref); return hive; }, @@ -601,7 +614,23 @@ var Styles = Module("Styles", { }); }, contexts: function (dactyl, modules, window) { - modules.Group.Hives("styles", function (group) styles.addHive(group.name)); + modules.Group.Hives("styles", + Class("LocalHive", modules.Group.Hive, { + init: function init(group) { + init.superapply(this, arguments); + + this.hive = styles.addHive(group.name); + this.hive.addRef(this); + }, + + __noSuchMethod__: function __noSuchMethod__(meth, args) { + return this.hive[meth].apply(this.hive, args); + }, + + destroy: function () { + this.hive.dropRef(this); + } + })); }, completion: function (dactyl, modules, window) { const names = Array.slice(util.computedStyle(window.document.createElement("div"))); @@ -658,6 +687,6 @@ var Styles = Module("Styles", { endModule(); -} catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);} +// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);} // vim: set fdm=marker sw=4 ts=4 et ft=javascript: From 7aed800d29f1c38e75fa947fe05911a4c7605a48 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 9 Feb 2011 07:21:10 -0500 Subject: [PATCH 40/52] Fixes, mostly, most of which should really be in the default branch. --HG-- branch : groups --- common/content/buffer.js | 23 ++++++++++++++++------- common/content/dactyl.js | 6 +++--- common/content/hints.js | 18 ++++++++++-------- common/content/options.js | 7 +++++++ common/modules/highlight.jsm | 2 +- common/modules/styles.jsm | 2 +- common/modules/template.jsm | 25 ++++++++++++++++--------- common/modules/util.jsm | 1 + 8 files changed, 55 insertions(+), 29 deletions(-) diff --git a/common/content/buffer.js b/common/content/buffer.js index 1827d00c..955737ce 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -14,7 +14,7 @@ * files. * @instance buffer */ -var Buffer = Module("buffer", { +var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { init: function () { this.evaluateXPath = util.evaluateXPath; this.pageInfo = {}; @@ -153,10 +153,12 @@ var Buffer = Module("buffer", { for (let tab in values(tabs.allTabs)) if (tab.linkedBrowser.contentDocument.readyState === "complete") dactyl.initDocument(tab.linkedBrowser.contentDocument); + util.addObserver(this); }, cleanup: function () { this.cleanupProgressListener(); + this.observe.unregister(); }, getDefaultNames: function getDefaultNames(node) { @@ -199,7 +201,7 @@ var Buffer = Module("buffer", { if (!(uri || doc.location)) return; - uri = uri || util.newURI(doc.location.href); + uri = isObject(uri) ? uri : util.newURI(uri || doc.location.href); let args = { url: { toString: function () uri.spec, valueOf: function () uri }, title: doc.title @@ -222,6 +224,18 @@ var Buffer = Module("buffer", { commandline.clear(); }, + observers: { + "chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", null); }, + "content-document-global-created": function (win, uri) { + let top = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); + + if (top == window) + this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri); + } + }, + onDOMContentLoaded: function onDOMContentLoaded(event) { let doc = event.originalTarget; if (doc instanceof HTMLDocument) @@ -269,8 +283,6 @@ var Buffer = Module("buffer", { // only thrown for the current tab, not when another tab changes if (flags & Ci.nsIWebProgressListener.STATE_START) { statusline.progress = 0; - - buffer._triggerLoadAutocmd("PageLoadPre", webProgress.DOMWindow.document); } else if (flags & Ci.nsIWebProgressListener.STATE_STOP) { // Workaround for bugs 591425 and 606877, dactyl bug #81 @@ -528,11 +540,8 @@ var Buffer = Module("buffer", { let range = selection.getRangeAt(0).cloneRange(); if (range.collapsed) { let re = options.get("iskeyword").regexp; - util.dump(String.quote(range)); Editor.extendRange(range, true, re, true); - util.dump(String.quote(range)); Editor.extendRange(range, false, re, true); - util.dump(String.quote(range) + "\n\n\n"); } return util.domToString(range); }, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 9fc2f70e..8e9baae6 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1832,8 +1832,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { function () { dactyl.restart(); }); function findToolbar(name) util.evaluateXPath( - "./*[@toolbarname=" + util.escapeString(name, "'") + "]", - toolbox).snapshotItem(0); + "//*[@toolbarname=" + util.escapeString(name, "'") + "]", + document).snapshotItem(0); var toolbox = document.getElementById("navigator-toolbox"); if (toolbox) { @@ -2017,7 +2017,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { completion.toolbar = function toolbar(context) { context.title = ["Toolbar"]; context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" }; - context.completions = util.evaluateXPath("./*[@toolbarname]", toolbox); + context.completions = util.evaluateXPath("//*[@toolbarname]", document); }; completion.window = function window(context) { diff --git a/common/content/hints.js b/common/content/hints.js index 7b6d6d52..2b7a6cab 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1160,21 +1160,23 @@ var Hints = Module("hints", { }, options: function () { const DEFAULT_HINTTAGS = - util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select", - "*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @tabindex or @role='link' or @role='button']"]); - function xpath(arg) Option.quote(util.makeXPath(arg)); + function xpath(arg) util.makeXPath(arg); options.add(["extendedhinttags", "eht"], "XPath strings of hintable elements for extended hint modes", - "regexpmap", "[iI]:" + xpath(["img"]) + - ",[asOTivVWy]:" + xpath(["{a,area}[@href]", "{img,iframe}[@src]"]) + - ",[F]:" + xpath(["body", "code", "div", "html", "p", "pre", "span"]) + - ",[S]:" + xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]), + "regexpmap", { + "[iI]": xpath(["img"]), + "[asOTivVWy]": xpath(["{a,area}[@href]", "{img,iframe}[@src]"]), + "[F]": xpath(["body", "code", "div", "html", "p", "pre", "span"]), + "[S]": xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]) + }, { validator: Option.validateXPath }); options.add(["hinttags", "ht"], "XPath string of hintable elements activated by 'f' and 'F'", - "string", DEFAULT_HINTTAGS, + "string", xpath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select", + "*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " + + "@tabindex or @role='link' or @role='button']"]), { validator: Option.validateXPath }); options.add(["hintkeys", "hk"], diff --git a/common/content/options.js b/common/content/options.js index 5ac9218e..67a4e3ec 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -62,6 +62,13 @@ var Option = Class("Option", { if (arguments.length > 3) { if (this.type == "string") defaultValue = Commands.quote(defaultValue); + + if (isObject(defaultValue)) + defaultValue = iter(defaultValue).map(function (val) val.map(Option.quote).join(":")).join(","); + + if (isArray(defaultValue)) + defaultValue = defaultValue.map(Option.quote).join(","); + this.defaultValue = this.parse(defaultValue); } diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm index dbe669d4..1c53104f 100644 --- a/common/modules/highlight.jsm +++ b/common/modules/highlight.jsm @@ -81,7 +81,7 @@ update(Highlight.prototype, { get cssText() this.inheritedCSS + this.value, toString: function () "Highlight(" + this.class + ")\n\t" + - [k + ": " + String.quote(v) for ([k, v] in this)] .join("\n\t") + [k + ": " + String(v).quote() for ([k, v] in this)] .join("\n\t") }); /** diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index c9f59d6d..921e11bc 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -512,7 +512,7 @@ var Styles = Module("Styles", { { names: ["-name", "-n"], description: "The name of this stylesheet", - completer: function () [[k, v.css] for ([k, v] in Iterator(args["-group"].names))], + completer: function (context, args) [[k, v.css] for ([k, v] in Iterator(args["-group"].hive.names))], type: modules.CommandOption.STRING } ], diff --git a/common/modules/template.jsm b/common/modules/template.jsm index d3e7dcd0..be1e5ab7 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -6,7 +6,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("template", { - exports: ["Template", "template"], + exports: ["Binding", "Template", "template"], require: ["util"], use: ["services"] }, this); @@ -14,8 +14,9 @@ defineModule("template", { default xml namespace = XHTML; var Binding = Class("Binding", { - init: function (node) { + init: function (node, nodes) { this.node = node; + this.nodes = nodes; node.dactylBinding = this; Object.defineProperties(node, this.constructor.properties); @@ -32,9 +33,13 @@ var Binding = Class("Binding", { }, get collapsed() !!this.getAttribute("collapsed"), - __noSuchMethod__: function __noSuchMethod__(meth, args) { - return this.node[meth].apply(this.node, args); - } + __noSuchMethod__: Class.Property({ + configurable: true, + writeable: true, + value: function __noSuchMethod__(meth, args) { + return this.node[meth].apply(this.node, args); + } + }) }, { get bindings() { let bindingProto = Object.getPrototypeOf(Binding.prototype); @@ -66,10 +71,12 @@ var Binding = Class("Binding", { for (let obj in this.bindings) for (let prop in properties(obj)) { let desc = Object.getOwnPropertyDescriptor(obj, prop); - for (let k in values(["get", "set", "value"])) - if (typeof desc[k] === "function") - desc[k] = this.bind(desc[k]); - res[prop] = desc; + if (desc.enumerable) { + for (let k in values(["get", "set", "value"])) + if (typeof desc[k] === "function") + desc[k] = this.bind(desc[k]); + res[prop] = desc; + } } return res; }) diff --git a/common/modules/util.jsm b/common/modules/util.jsm index aa3d7d71..5cd21681 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -1083,6 +1083,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), this._loadOverlay(window, obj(window)); } }, + _loadOverlay: function _loadOverlay(window, obj) { let doc = window.document; if (!doc.dactylOverlayElements) { From d6d126a5abf542b3f0d2bdffdb56a18fbd7333c3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 9 Feb 2011 07:21:46 -0500 Subject: [PATCH 41/52] Fixes, mostly, most of which should really be in the default branch. --HG-- branch : groups --- common/content/buffer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/content/buffer.js b/common/content/buffer.js index 955737ce..5466c445 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -225,7 +225,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, observers: { - "chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", null); }, + "chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", uri); }, "content-document-global-created": function (win, uri) { let top = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem From d4f9f9909cdf0a6086da6d7f54a3e618e75ad3ce Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 9 Feb 2011 08:11:25 -0500 Subject: [PATCH 42/52] Move some things around. Fix bug in last commit (I've already forgotten which). --HG-- branch : groups --- common/content/browser.js | 266 +++++++++++++++------- common/content/buffer.js | 426 +++++++++++++---------------------- common/content/hints.js | 2 - common/content/statusline.js | 3 +- common/content/tabs.js | 4 + 5 files changed, 346 insertions(+), 355 deletions(-) diff --git a/common/content/browser.js b/common/content/browser.js index ac647fee..2fe98736 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -11,63 +11,204 @@ /** * @instance browser */ -var Browser = Module("browser", { -}, { - climbUrlPath: function (count) { - let url = buffer.documentURI.clone(); - dactyl.assert(url instanceof Ci.nsIURL); - - while (count-- && url.path != "/") - url.path = url.path.replace(/[^\/]+\/*$/, ""); - - dactyl.assert(!url.equals(buffer.documentURI)); - dactyl.open(url.spec); +var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { + init: function init() { + this.cleanupProgressListener = util.overlayObject(window.XULBrowserWindow, + this.progressListener); + util.addObserver(this); }, - incrementURL: function (count) { - let matches = buffer.uri.spec.match(/(.*?)(\d+)(\D*)$/); - dactyl.assert(matches); - let oldNum = matches[2]; + cleanup: function () { + this.cleanupProgressListener(); + this.observe.unregister(); + }, - // disallow negative numbers as trailing numbers are often proceeded by hyphens - let newNum = String(Math.max(parseInt(oldNum, 10) + count, 0)); - if (/^0/.test(oldNum)) - while (newNum.length < oldNum.length) - newNum = "0" + newNum; + observers: { + "chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", uri); }, + "content-document-global-created": function (win, uri) { + let top = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); - matches[2] = newNum; - dactyl.open(matches.slice(1).join("")); + if (top == window) + this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri); + } + }, + + _triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc, uri) { + if (!(uri || doc.location)) + return; + + uri = isObject(uri) ? uri : util.newURI(uri || doc.location.href); + let args = { + url: { toString: function () uri.spec, valueOf: function () uri }, + title: doc.title + }; + + if (dactyl.has("tabs")) { + args.tab = tabs.getContentIndex(doc) + 1; + args.doc = { + valueOf: function () doc, + toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument" + }; + } + + autocommands.trigger(name, args); + }, + + events: { + DOMContentLoaded: function onDOMContentLoaded(event) { + util.dump("DOMContentLoaded"); + let doc = event.originalTarget; + if (doc instanceof HTMLDocument) + this._triggerLoadAutocmd("DOMLoad", doc); + }, + + // TODO: see what can be moved to onDOMContentLoaded() + // event listener which is is called on each page load, even if the + // page is loaded in a background tab + load: function onLoad(event) { + util.dump("onLoad"); + let doc = event.originalTarget; + if (doc instanceof Document) + dactyl.initDocument(doc); + + if (doc instanceof HTMLDocument) { + if (doc.defaultView.frameElement) { + // document is part of a frameset + + // hacky way to get rid of "Transferring data from ..." on sites with frames + // when you click on a link inside a frameset, because asyncUpdateUI + // is not triggered there (Gecko bug?) + this.timeout(function () { statusline.updateUrl(); }, 10); + } + else { + // code which should happen for all (also background) newly loaded tabs goes here: + if (doc != config.browser.contentDocument) + dactyl.echomsg({ domains: [util.getHost(doc.location)], message: "Background tab loaded: " + (doc.title || doc.location.href) }, 3); + + this._triggerLoadAutocmd("PageLoad", doc); + } + } + } + }, + + /** + * @property {Object} The document loading progress listener. + */ + progressListener: { + // XXX: function may later be needed to detect a canceled synchronous openURL() + onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) { + onStateChange.superapply(this, arguments); + // STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also + // receive statechange events for loading images and other parts of the web page + if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) { + // This fires when the load event is initiated + // only thrown for the current tab, not when another tab changes + if (flags & Ci.nsIWebProgressListener.STATE_START) { + statusline.progress = 0; + } + else if (flags & Ci.nsIWebProgressListener.STATE_STOP) { + // Workaround for bugs 591425 and 606877, dactyl bug #81 + config.browser.mCurrentBrowser.collapsed = false; + if (!dactyl.focusedElement || dactyl.focusedElement === document.documentElement) + dactyl.focusContent(); + statusline.updateUrl(); + } + } + }), + // for notifying the user about secure web pages + onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) { + onSecurityChange.superapply(this, arguments); + if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) + statusline.security = "broken"; + else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) + statusline.security = "extended"; + else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH) + statusline.security = "secure"; + else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) + statusline.security = "insecure"; + if (webProgress && webProgress.DOMWindow) + webProgress.DOMWindow.document.dactylSecurity = statusline.security; + }), + onStatusChange: util.wrapCallback(function onStatusChange(webProgress, request, status, message) { + onStatusChange.superapply(this, arguments); + statusline.updateUrl(message); + }), + onProgressChange: util.wrapCallback(function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { + onProgressChange.superapply(this, arguments); + if (webProgress && webProgress.DOMWindow) + webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress; + statusline.progress = curTotalProgress / maxTotalProgress; + }), + // happens when the users switches tabs + onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) { + onLocationChange.superapply(this, arguments); + + delete contexts.groups; + + statusline.updateUrl(); + statusline.progress = ""; + + let win = webProgress.DOMWindow; + if (win && uri) { + statusline.progress = win.dactylProgress; + + let oldURI = win.document.dactylURI; + if (win.document.dactylLoadIdx === webProgress.loadedTransIndex + || !oldURI || uri.spec.replace(/#.*/, "") !== oldURI.replace(/#.*/, "")) + for (let frame in values(buffer.allFrames(win))) + frame.document.dactylFocusAllowed = false; + win.document.dactylURI = uri.spec; + win.document.dactylLoadIdx = webProgress.loadedTransIndex; + } + + // Workaround for bugs 591425 and 606877, dactyl bug #81 + let collapse = uri && uri.scheme === "dactyl" && webProgress.isLoadingDocument; + if (collapse) + dactyl.focus(document.documentElement); + config.browser.mCurrentBrowser.collapsed = collapse; + + util.timeout(function () { + browser._triggerLoadAutocmd("LocationChange", + (win || content).document, + uri); + }); + + // if this is not delayed we get the position of the old buffer + util.timeout(function () { + statusline.updateBufferPosition(); + statusline.updateZoomLevel(); + if (loaded.commandline) + commandline.clear(); + }, 500); + }), + // called at the very end of a page load + asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() { + asyncUpdateUI.superapply(this, arguments); + util.timeout(function () { statusline.updateUrl(); }, 100); + }), + setOverLink: util.wrapCallback(function setOverLink(link, b) { + setOverLink.superapply(this, arguments); + switch (options["showstatuslinks"]) { + case "status": + statusline.updateUrl(link ? "Link: " + link : null); + break; + case "command": + if (link) + dactyl.echo("Link: " + link, commandline.DISALLOW_MULTILINE); + else + commandline.clear(); + break; + } + }), } }, { - options: function () { - options.add(["encoding", "enc"], - "The current buffer's character encoding", - "string", "UTF-8", - { - scope: Option.SCOPE_LOCAL, - getter: function () config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset, - setter: function (val) { - if (options["encoding"] == val) - return val; - - // Stolen from browser.jar/content/browser/browser.js, more or less. - try { - config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset = val; - PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val); - getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); - } - catch (e) { dactyl.reportError(e); } - return null; - }, - completer: function (context) completion.charset(context) - }); +}, { + events: function () { + events.listen(config.browser, browser, "events", true); }, - mappings: function () { - mappings.add([modes.NORMAL], - ["y", ""], "Yank current location to the clipboard", - function () { dactyl.clipboardWrite(buffer.uri.spec, true); }); - // opening websites mappings.add([modes.NORMAL], ["o"], "Open one or more URLs", @@ -93,16 +234,6 @@ var Browser = Module("browser", { "Open one or more URLs in a new window, based on current location", function () { CommandExMode().open("winopen " + buffer.uri.spec); }); - mappings.add([modes.NORMAL], - [""], "Increment last number in URL", - function (args) { Browser.incrementURL(Math.max(args.count, 1)); }, - { count: true }); - - mappings.add([modes.NORMAL], - [""], "Decrement last number in URL", - function (args) { Browser.incrementURL(-Math.max(args.count, 1)); }, - { count: true }); - mappings.add([modes.NORMAL], ["~"], "Open home directory", function () { dactyl.open("~"); }); @@ -118,29 +249,12 @@ var Browser = Module("browser", { dactyl.open(homepages, { from: "homepage", where: dactyl.NEW_TAB }); }); - mappings.add([modes.NORMAL], ["gu"], - "Go to parent directory", - function (args) { Browser.climbUrlPath(Math.max(args.count, 1)); }, - { count: true }); - - mappings.add([modes.NORMAL], ["gU"], - "Go to the root of the website", - function () { Browser.climbUrlPath(-1); }); - mappings.add(modes.all, [""], "Redraw the screen", function () { ex.redraw(); }); }, commands: function () { - commands.add(["old-downl[oads]", "old-dl"], - "Show progress of current downloads", - function () { - dactyl.open("chrome://mozapps/content/downloads/downloads.xul", - { from: "downloads"}); - }, - { argCount: "0" }); - commands.add(["o[pen]"], "Open one or more URLs in the current tab", function (args) { dactyl.open(args[0] || "about:blank"); }, diff --git a/common/content/buffer.js b/common/content/buffer.js index 5466c445..e5d93668 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -14,7 +14,7 @@ * files. * @instance buffer */ -var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { +var Buffer = Module("buffer", { init: function () { this.evaluateXPath = util.evaluateXPath; this.pageInfo = {}; @@ -145,77 +145,6 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { let elem = event.originalTarget; buffer.viewSource([elem.getAttribute("href"), Number(elem.getAttribute("line"))]); }; - - this.cleanupProgressListener = util.overlayObject(window.XULBrowserWindow, - this.progressListener); - - if (dactyl.has("tabs")) - for (let tab in values(tabs.allTabs)) - if (tab.linkedBrowser.contentDocument.readyState === "complete") - dactyl.initDocument(tab.linkedBrowser.contentDocument); - util.addObserver(this); - }, - - cleanup: function () { - this.cleanupProgressListener(); - this.observe.unregister(); - }, - - getDefaultNames: function getDefaultNames(node) { - let url = node.href || node.src || node.documentURI; - let currExt = url.replace(/^.*?(?:\.([a-z0-9]+))?$/i, "$1").toLowerCase(); - - if (isinstance(node, [Document, HTMLImageElement])) { - let type = node.contentType || node.QueryInterface(Ci.nsIImageLoadingContent) - .getRequest(0).mimeType; - - if (type === "text/plain") - var ext = "." + (currExt || "txt"); - else - ext = "." + services.mime.getPrimaryExtension(type, currExt); - } - else if (currExt) - ext = "." + currExt; - else - ext = ""; - let re = ext ? RegExp("(\\." + currExt + ")?$", "i") : /$/; - - var names = []; - if (node.title) - names.push([node.title, "Page Name"]); - - if (node.alt) - names.push([node.alt, "Alternate Text"]); - - if (!isinstance(node, Document) && node.textContent) - names.push([node.textContent, "Link Text"]); - - names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]); - - return names.filter(function ([leaf, title]) leaf) - .map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent) - .replace(re, ext), title]); - }, - - _triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc, uri) { - if (!(uri || doc.location)) - return; - - uri = isObject(uri) ? uri : util.newURI(uri || doc.location.href); - let args = { - url: { toString: function () uri.spec, valueOf: function () uri }, - title: doc.title - }; - - if (dactyl.has("tabs")) { - args.tab = tabs.getContentIndex(doc) + 1; - args.doc = { - valueOf: function () doc, - toString: function () "tabs.getTab(" + (args.tab - 1) + ").linkedBrowser.contentDocument" - }; - } - - autocommands.trigger(name, args); }, // called when the active document is scrolled @@ -224,162 +153,6 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { commandline.clear(); }, - observers: { - "chrome-document-global-created": function (win, uri) { this.observe(win, "content-document-global-created", uri); }, - "content-document-global-created": function (win, uri) { - let top = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); - - if (top == window) - this._triggerLoadAutocmd("PageLoadPre", win.document, win.location.href != "null" ? window.location.href : uri); - } - }, - - onDOMContentLoaded: function onDOMContentLoaded(event) { - let doc = event.originalTarget; - if (doc instanceof HTMLDocument) - this._triggerLoadAutocmd("DOMLoad", doc); - }, - - // TODO: see what can be moved to onDOMContentLoaded() - // event listener which is is called on each page load, even if the - // page is loaded in a background tab - onPageLoad: function onPageLoad(event) { - let doc = event.originalTarget; - if (doc instanceof Document) - dactyl.initDocument(doc); - - if (doc instanceof HTMLDocument) { - if (doc.defaultView.frameElement) { - // document is part of a frameset - - // hacky way to get rid of "Transferring data from ..." on sites with frames - // when you click on a link inside a frameset, because asyncUpdateUI - // is not triggered there (Gecko bug?) - this.timeout(function () { statusline.updateUrl(); }, 10); - } - else { - // code which should happen for all (also background) newly loaded tabs goes here: - if (doc != config.browser.contentDocument) - dactyl.echomsg({ domains: [util.getHost(doc.location)], message: "Background tab loaded: " + (doc.title || doc.location.href) }, 3); - - this._triggerLoadAutocmd("PageLoad", doc); - } - } - }, - - /** - * @property {Object} The document loading progress listener. - */ - progressListener: { - // XXX: function may later be needed to detect a canceled synchronous openURL() - onStateChange: util.wrapCallback(function onStateChange(webProgress, request, flags, status) { - onStateChange.superapply(this, arguments); - // STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also - // receive statechange events for loading images and other parts of the web page - if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) { - // This fires when the load event is initiated - // only thrown for the current tab, not when another tab changes - if (flags & Ci.nsIWebProgressListener.STATE_START) { - statusline.progress = 0; - } - else if (flags & Ci.nsIWebProgressListener.STATE_STOP) { - // Workaround for bugs 591425 and 606877, dactyl bug #81 - config.browser.mCurrentBrowser.collapsed = false; - if (!dactyl.focusedElement || dactyl.focusedElement === document.documentElement) - dactyl.focusContent(); - statusline.updateUrl(); - } - } - }), - // for notifying the user about secure web pages - onSecurityChange: util.wrapCallback(function onSecurityChange(webProgress, request, state) { - onSecurityChange.superapply(this, arguments); - if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) - statusline.security = "broken"; - else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) - statusline.security = "extended"; - else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH) - statusline.security = "secure"; - else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) - statusline.security = "insecure"; - if (webProgress && webProgress.DOMWindow) - webProgress.DOMWindow.document.dactylSecurity = statusline.security; - }), - onStatusChange: util.wrapCallback(function onStatusChange(webProgress, request, status, message) { - onStatusChange.superapply(this, arguments); - statusline.updateUrl(message); - }), - onProgressChange: util.wrapCallback(function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) { - onProgressChange.superapply(this, arguments); - if (webProgress && webProgress.DOMWindow) - webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress; - statusline.progress = curTotalProgress / maxTotalProgress; - }), - // happens when the users switches tabs - onLocationChange: util.wrapCallback(function onLocationChange(webProgress, request, uri) { - onLocationChange.superapply(this, arguments); - - delete contexts.groups; - - statusline.updateUrl(); - statusline.progress = ""; - - let win = webProgress.DOMWindow; - if (win && uri) { - statusline.progress = win.dactylProgress; - - let oldURI = win.document.dactylURI; - if (win.document.dactylLoadIdx === webProgress.loadedTransIndex - || !oldURI || uri.spec.replace(/#.*/, "") !== oldURI.replace(/#.*/, "")) - for (let frame in values(buffer.allFrames(win))) - frame.document.dactylFocusAllowed = false; - win.document.dactylURI = uri.spec; - win.document.dactylLoadIdx = webProgress.loadedTransIndex; - } - - // Workaround for bugs 591425 and 606877, dactyl bug #81 - let collapse = uri && uri.scheme === "dactyl" && webProgress.isLoadingDocument; - if (collapse) - dactyl.focus(document.documentElement); - config.browser.mCurrentBrowser.collapsed = collapse; - - util.timeout(function () { - buffer._triggerLoadAutocmd("LocationChange", - (win || content).document, - uri); - }); - - // if this is not delayed we get the position of the old buffer - util.timeout(function () { - statusline.updateBufferPosition(); - statusline.updateZoomLevel(); - if (loaded.commandline) - commandline.clear(); - }, 500); - }), - // called at the very end of a page load - asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() { - asyncUpdateUI.superapply(this, arguments); - util.timeout(function () { statusline.updateUrl(); }, 100); - }), - setOverLink: util.wrapCallback(function setOverLink(link, b) { - setOverLink.superapply(this, arguments); - switch (options["showstatuslinks"]) { - case "status": - statusline.updateUrl(link ? "Link: " + link : null); - break; - case "command": - if (link) - dactyl.echo("Link: " + link, commandline.DISALLOW_MULTILINE); - else - commandline.clear(); - break; - } - }), - }, - /** * @property {Array} The alternative style sheets for the current * buffer. Only returns style sheets for the 'screen' media type. @@ -392,6 +165,32 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { ); }, + climbUrlPath: function (count) { + let url = buffer.documentURI.clone(); + dactyl.assert(url instanceof Ci.nsIURL); + + while (count-- && url.path != "/") + url.path = url.path.replace(/[^\/]+\/*$/, ""); + + dactyl.assert(!url.equals(buffer.documentURI)); + dactyl.open(url.spec); + }, + + incrementURL: function (count) { + let matches = buffer.uri.spec.match(/(.*?)(\d+)(\D*)$/); + dactyl.assert(matches); + let oldNum = matches[2]; + + // disallow negative numbers as trailing numbers are often proceeded by hyphens + let newNum = String(Math.max(parseInt(oldNum, 10) + count, 0)); + if (/^0/.test(oldNum)) + while (newNum.length < oldNum.length) + newNum = "0" + newNum; + + matches[2] = newNum; + dactyl.open(matches.slice(1).join("")); + }, + /** * @property {Object} A map of page info sections to their * content generating functions. @@ -775,7 +574,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { onSubmit: function (path) { let file = io.File(path); if (file.exists() && file.isDirectory()) - file.append(buffer.getDefaultNames(elem)[0][0]); + file.append(Buffer.getDefaultNames(elem)[0][0]); try { if (!file.exists()) @@ -1245,6 +1044,42 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { setZoom: deprecated("buffer.setZoom", function setZoom() buffer.setZoom.apply(buffer, arguments)), bumpZoomLevel: deprecated("buffer.bumpZoomLevel", function bumpZoomLevel() buffer.bumpZoomLevel.apply(buffer, arguments)), + getDefaultNames: function getDefaultNames(node) { + let url = node.href || node.src || node.documentURI; + let currExt = url.replace(/^.*?(?:\.([a-z0-9]+))?$/i, "$1").toLowerCase(); + + if (isinstance(node, [Document, HTMLImageElement])) { + let type = node.contentType || node.QueryInterface(Ci.nsIImageLoadingContent) + .getRequest(0).mimeType; + + if (type === "text/plain") + var ext = "." + (currExt || "txt"); + else + ext = "." + services.mime.getPrimaryExtension(type, currExt); + } + else if (currExt) + ext = "." + currExt; + else + ext = ""; + let re = ext ? RegExp("(\\." + currExt + ")?$", "i") : /$/; + + var names = []; + if (node.title) + names.push([node.title, "Page Name"]); + + if (node.alt) + names.push([node.alt, "Alternate Text"]); + + if (!isinstance(node, Document) && node.textContent) + names.push([node.textContent, "Link Text"]); + + names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]); + + return names.filter(function ([leaf, title]) leaf) + .map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent) + .replace(re, ext), title]); + }, + findScrollableWindow: deprecated("buffer.findScrollableWindow", function findScrollableWindow() buffer.findScrollableWindow.apply(buffer, arguments)), findScrollable: deprecated("buffer.findScrollable", function findScrollable() buffer.findScrollable.apply(buffer, arguments)), @@ -1498,7 +1333,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { let file = io.File(filename.replace(RegExp(File.PATH_SEP + "*$"), "")); if (filename.substr(-1) === File.PATH_SEP || file.exists() && file.isDirectory()) - file.append(buffer.getDefaultNames(doc)[0][0]); + file.append(Buffer.getDefaultNames(doc)[0][0]); dactyl.assert(args.bang || !file.exists(), "E13: File exists (add ! to override)"); @@ -1650,19 +1485,38 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { completion.savePage = function savePage(context, node) { context.fork("generated", context.filter.replace(/[^/]*$/, "").length, this, function (context) { - context.completions = buffer.getDefaultNames(node); + context.completions = Buffer.getDefaultNames(node); }); }; }, events: function () { - events.listen(config.browser, "DOMContentLoaded", buffer.closure.onDOMContentLoaded, true); - events.listen(config.browser, "load", buffer.closure.onPageLoad, true); events.listen(config.browser, "scroll", buffer.closure._updateBufferPosition, false); }, mappings: function () { - var myModes = config.browserModes; + mappings.add([modes.NORMAL], + ["y", ""], "Yank current location to the clipboard", + function () { dactyl.clipboardWrite(buffer.uri.spec, true); }); - mappings.add(myModes, [".", ""], + mappings.add([modes.NORMAL], + [""], "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", + function (args) { buffer.incrementURL(-Math.max(args.count, 1)); }, + { count: true }); + + mappings.add([modes.NORMAL], ["gu"], + "Go to parent directory", + function (args) { buffer.climbUrlPath(Math.max(args.count, 1)); }, + { count: true }); + + mappings.add([modes.NORMAL], ["gU"], + "Go to the root of the website", + function () { buffer.climbUrlPath(-1); }); + + mappings.add(modes.COMMAND, [".", ""], "Repeat the last key event", function (args) { if (mappings.repeat) { @@ -1672,54 +1526,54 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, { count: true }); - mappings.add(myModes, ["i", ""], + mappings.add(modes.COMMAND, ["i", ""], "Start caret mode", function () { modes.push(modes.CARET); }); - mappings.add(myModes, [""], + mappings.add(modes.COMMAND, [""], "Stop loading the current web page", function () { ex.stop(); }); // scrolling - mappings.add(myModes, ["j", "", "", ""], + mappings.add(modes.COMMAND, ["j", "", "", ""], "Scroll document down", function (args) { buffer.scrollVertical("lines", Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["k", "", "", ""], + mappings.add(modes.COMMAND, ["k", "", "", ""], "Scroll document up", function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, dactyl.has("mail") ? ["h", ""] : ["h", "", ""], + mappings.add(modes.COMMAND, dactyl.has("mail") ? ["h", ""] : ["h", "", ""], "Scroll document to the left", function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, dactyl.has("mail") ? ["l", ""] : ["l", "", ""], + mappings.add(modes.COMMAND, dactyl.has("mail") ? ["l", ""] : ["l", "", ""], "Scroll document to the right", function (args) { buffer.scrollHorizontal("columns", Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["0", "^", ""], + mappings.add(modes.COMMAND, ["0", "^", ""], "Scroll to the absolute left of the document", function () { buffer.scrollToPercent(0, null); }); - mappings.add(myModes, ["$", ""], + mappings.add(modes.COMMAND, ["$", ""], "Scroll to the absolute right of the document", function () { buffer.scrollToPercent(100, null); }); - mappings.add(myModes, ["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(myModes, ["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 }); - mappings.add(myModes, ["%", ""], + mappings.add(modes.COMMAND, ["%", ""], "Scroll to {count} percent of the document", function (args) { dactyl.assert(args.count > 0 && args.count <= 100); @@ -1727,59 +1581,59 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, { count: true }); - mappings.add(myModes, ["", ""], + mappings.add(modes.COMMAND, ["", ""], "Scroll window downwards in the buffer", function (args) { buffer._scrollByScrollSize(args.count, true); }, { count: true }); - mappings.add(myModes, ["", ""], + mappings.add(modes.COMMAND, ["", ""], "Scroll window upwards in the buffer", function (args) { buffer._scrollByScrollSize(args.count, false); }, { count: true }); - mappings.add(myModes, ["", "", "", ""], + mappings.add(modes.COMMAND, ["", "", "", ""], "Scroll up a full page", function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["", "", "", ""], + mappings.add(modes.COMMAND, ["", "", "", ""], "Scroll down a full page", function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["]f", ""], + mappings.add(modes.COMMAND, ["]f", ""], "Focus next frame", function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["[f", ""], + mappings.add(modes.COMMAND, ["[f", ""], "Focus previous frame", function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); }, { count: true }); - mappings.add(myModes, ["]]", ""], + mappings.add(modes.COMMAND, ["]]", ""], "Follow the link labeled 'next' or '>' if it exists", function (args) { buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true); }, { count: true }); - mappings.add(myModes, ["[[", ""], + mappings.add(modes.COMMAND, ["[[", ""], "Follow the link labeled 'prev', 'previous' or '<' if it exists", function (args) { buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true); }, { count: true }); - mappings.add(myModes, ["gf", ""], + mappings.add(modes.COMMAND, ["gf", ""], "Toggle between rendered and source view", function () { buffer.viewSource(null, false); }); - mappings.add(myModes, ["gF", ""], + mappings.add(modes.COMMAND, ["gF", ""], "View source with an external editor", function () { buffer.viewSource(null, true); }); - mappings.add(myModes, ["gi", ""], + mappings.add(modes.COMMAND, ["gi", ""], "Focus last used input field", function (args) { let elem = buffer.lastInputField; @@ -1811,7 +1665,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, { count: true }); - mappings.add(myModes, ["gP"], + mappings.add(modes.COMMAND, ["gP"], "Open (put) a URL based on the current clipboard contents in a new buffer", function () { let url = dactyl.clipboardRead(); @@ -1819,7 +1673,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true }); }); - mappings.add(myModes, ["p", "", ""], + mappings.add(modes.COMMAND, ["p", "", ""], "Open (put) a URL based on the current clipboard contents in the current buffer", function () { let url = dactyl.clipboardRead(); @@ -1827,7 +1681,7 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.open(url); }); - mappings.add(myModes, ["P", ""], + mappings.add(modes.COMMAND, ["P", ""], "Open (put) a URL based on the current clipboard contents in a new buffer", function () { let url = dactyl.clipboardRead(); @@ -1836,16 +1690,16 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }); // reloading - mappings.add(myModes, ["r", ""], + mappings.add(modes.COMMAND, ["r", ""], "Reload the current web page", function () { tabs.reload(tabs.getTab(), false); }); - mappings.add(myModes, ["R", ""], + mappings.add(modes.COMMAND, ["R", ""], "Reload while skipping the cache", function () { tabs.reload(tabs.getTab(), true); }); // yanking - mappings.add(myModes, ["Y", ""], + mappings.add(modes.COMMAND, ["Y", ""], "Copy selected text or current word", function () { let sel = buffer.currentWord; @@ -1854,66 +1708,88 @@ var Buffer = Module("buffer", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }); // zooming - mappings.add(myModes, ["zi", "+", ""], + mappings.add(modes.COMMAND, ["zi", "+", ""], "Enlarge text zoom of current web page", function (args) { buffer.zoomIn(Math.max(args.count, 1), false); }, { count: true }); - mappings.add(myModes, ["zm", ""], + mappings.add(modes.COMMAND, ["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(myModes, ["zo", "-", ""], + mappings.add(modes.COMMAND, ["zo", "-", ""], "Reduce text zoom of current web page", function (args) { buffer.zoomOut(Math.max(args.count, 1), false); }, { count: true }); - mappings.add(myModes, ["zr", ""], + mappings.add(modes.COMMAND, ["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(myModes, ["zz", ""], + mappings.add(modes.COMMAND, ["zz", ""], "Set text zoom value of current web page", function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, false); }, { count: true }); - mappings.add(myModes, ["ZI", "zI", ""], + mappings.add(modes.COMMAND, ["ZI", "zI", ""], "Enlarge full zoom of current web page", function (args) { buffer.zoomIn(Math.max(args.count, 1), true); }, { count: true }); - mappings.add(myModes, ["ZM", "zM", ""], + mappings.add(modes.COMMAND, ["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(myModes, ["ZO", "zO", ""], + mappings.add(modes.COMMAND, ["ZO", "zO", ""], "Reduce full zoom of current web page", function (args) { buffer.zoomOut(Math.max(args.count, 1), true); }, { count: true }); - mappings.add(myModes, ["ZR", "zR", ""], + mappings.add(modes.COMMAND, ["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(myModes, ["zZ", ""], + mappings.add(modes.COMMAND, ["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(myModes, ["", ""], + mappings.add(modes.COMMAND, ["", ""], "Print the current file name", function () { buffer.showPageInfo(false); }); - mappings.add(myModes, ["g", ""], + mappings.add(modes.COMMAND, ["g", ""], "Print file information", function () { buffer.showPageInfo(true); }); }, options: function () { + options.add(["encoding", "enc"], + "The current buffer's character encoding", + "string", "UTF-8", + { + scope: Option.SCOPE_LOCAL, + getter: function () config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset, + setter: function (val) { + if (options["encoding"] == val) + return val; + + // Stolen from browser.jar/content/browser/browser.js, more or less. + try { + config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset = val; + PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val); + getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE); + } + catch (e) { dactyl.reportError(e); } + return null; + }, + completer: function (context) completion.charset(context) + }); + options.add(["iskeyword", "isk"], "Regular expression defining which characters constitute word characters", "string", '[^\\s.,!?:;/"\'^$%&?()[\\]{}<>#*+|=~_-]', diff --git a/common/content/hints.js b/common/content/hints.js index 2b7a6cab..18dceb2a 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1159,8 +1159,6 @@ var Hints = Module("hints", { function ({ self }) { self.escapeNumbers = !self.escapeNumbers; }); }, options: function () { - const DEFAULT_HINTTAGS = - function xpath(arg) util.makeXPath(arg); options.add(["extendedhinttags", "eht"], "XPath strings of hintable elements for extended hint modes", diff --git a/common/content/statusline.js b/common/content/statusline.js index 7def5b4a..3ec88220 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -169,7 +169,7 @@ var StatusLine = Module("statusline", { modified += "-"; } if (modules.bookmarkcache) { - if (bookmarkcache.isBookmarked(buffer.uri)) + if (bookmarkcache.isBookmarked(url)) modified += UTF8("❤"); //modified += UTF8("♥"); } @@ -178,7 +178,6 @@ var StatusLine = Module("statusline", { url = losslessDecodeURI(url); - // make it even more Vim-like if (url == "about:blank") { if (!buffer.title) url = "[No Name]"; diff --git a/common/content/tabs.js b/common/content/tabs.js index 47a14424..1b9a2869 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -39,6 +39,10 @@ var Tabs = Module("tabs", { xul|tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#tab) !important; } ]]>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m), false, true); + + for (let { linkedBrowser: { contentDocument } } in values(this.allTabs)) + if (contentDocument.readyState === "complete") + dactyl.initDocument(contentDocument); }, cleanup: function cleanup() { From 36c382cdf4026049a35baa7cc19ee61911583192 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 9 Feb 2011 08:20:06 -0500 Subject: [PATCH 43/52] Fix bug in last commit. --HG-- branch : groups --- common/content/browser.js | 2 -- common/content/statusline.js | 28 +++++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/common/content/browser.js b/common/content/browser.js index 2fe98736..dc3afd88 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -58,7 +58,6 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), events: { DOMContentLoaded: function onDOMContentLoaded(event) { - util.dump("DOMContentLoaded"); let doc = event.originalTarget; if (doc instanceof HTMLDocument) this._triggerLoadAutocmd("DOMLoad", doc); @@ -68,7 +67,6 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), // event listener which is is called on each page load, even if the // page is loaded in a background tab load: function onLoad(event) { - util.dump("onLoad"); let doc = event.originalTarget; if (doc instanceof Document) dactyl.initDocument(doc); diff --git a/common/content/statusline.js b/common/content/statusline.js index 3ec88220..84148668 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -161,20 +161,22 @@ var StatusLine = Module("statusline", { // when session information is available, add [+] when we can go // backwards, [-] when we can go forwards let modified = ""; - if (window.getWebNavigation) { - let sh = window.getWebNavigation().sessionHistory; - if (sh && sh.index > 0) - modified += "+"; - if (sh && sh.index < sh.count - 1) - modified += "-"; + if (url === buffer.uri.spec) { + if (window.getWebNavigation) { + let sh = window.getWebNavigation().sessionHistory; + if (sh && sh.index > 0) + modified += "+"; + if (sh && sh.index < sh.count - 1) + modified += "-"; + } + if (modules.bookmarkcache) { + if (bookmarkcache.isBookmarked(url)) + modified += UTF8("❤"); + //modified += UTF8("♥"); + } + if (modules.quickmarks) + modified += quickmarks.find(url.replace(/#.*/, "")).join(""); } - if (modules.bookmarkcache) { - if (bookmarkcache.isBookmarked(url)) - modified += UTF8("❤"); - //modified += UTF8("♥"); - } - if (modules.quickmarks) - modified += quickmarks.find(url.replace(/#.*/, "")).join(""); url = losslessDecodeURI(url); From 1299fddfd30f5eb89fe7cf49a3e174310d0fca82 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 9 Feb 2011 14:58:35 -0500 Subject: [PATCH 44/52] Fix some minor bugs. --HG-- branch : groups --- common/content/contexts.js | 23 +++++++++++++++-------- common/modules/util.jsm | 12 ++++++++---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/common/content/contexts.js b/common/content/contexts.js index b1494f3f..714b45c4 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -17,6 +17,10 @@ var Group = Class("Group", { cleanup: function cleanup() { for (let hive in values(this.hives)) dactyl.trapErrors("cleanup", hive); + + this.hives = []; + for (let hive in keys(Group.hiveMap)) + delete this[hive]; }, destroy: function destroy() { for (let hive in values(this.hives)) @@ -29,8 +33,6 @@ var Group = Class("Group", { get builtin() contexts.builtinGroups.indexOf(this) >= 0, - hives: {} - }, { compileFilter: function (patterns) { @@ -165,17 +167,22 @@ var Contexts = Module("contexts", { if (group) name = group.name; - if (replace) - this.removeGroup(name); - - if (!group || replace) { - dactyl.assert(name !== "default", "Illegal group name"); - + if (!group) { group = Group(name, description, filter, persist); this.groupList.unshift(group); this.groupMap[name] = group; this.hiveProto.__defineGetter__(name, function () group[this._hive]); } + + if (replace) { + dactyl.trapErrors("cleanup", group); + if (description) + group.description = description; + if (filter) + group.filter = filter + group.persist = persist; + } + delete this.groups; return group; }, diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 5cd21681..b2d62ad4 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -770,14 +770,18 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), try { let xmlhttp = services.Xmlhttp(); xmlhttp.mozBackgroundRequest = true; - if (params.callback) { - xmlhttp.onload = function handler(event) { util.trapErrors(params.callback, params, xmlhttp, event) }; - xmlhttp.onerror = xmlhttp.onload; + + let async = params.callback || params.onload || params.onerror; + if (async) { + xmlhttp.onload = function handler(event) { util.trapErrors(params.onload || params.callback, params, xmlhttp, event) }; + xmlhttp.onerror = function handler(event) { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event) }; } if (params.mimeType) xmlhttp.overrideMimeType(params.mimeType); - xmlhttp.open(params.method || "GET", url, !!params.callback, params.user, params.pass); + xmlhttp.open(params.method || "GET", url, async, + params.user, params.pass); + xmlhttp.send(null); return xmlhttp; } From 60063a8f9110dddcca41ef30eedf1d79e67dfe69 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 10 Feb 2011 15:40:09 -0500 Subject: [PATCH 45/52] Experimentally move commands.js and options.js to modules. Fix some bugs. --HG-- branch : groups rename : common/content/commands.js => common/modules/commands.jsm rename : common/content/options.js => common/modules/options.jsm --- common/content/contexts.js | 21 +- common/content/mappings.js | 15 +- .../commands.js => modules/commands.jsm} | 602 ++++++++++-------- common/modules/completion.jsm | 10 +- common/modules/io.jsm | 7 +- .../options.js => modules/options.jsm} | 324 +++++----- common/modules/overlay.jsm | 6 +- common/modules/services.jsm | 1 + common/modules/styles.jsm | 3 + common/modules/util.jsm | 9 +- common/tests/functional/dactyl.jsm | 2 +- common/tests/functional/testCommands.js | 58 +- 12 files changed, 595 insertions(+), 463 deletions(-) rename common/{content/commands.js => modules/commands.jsm} (78%) rename common/{content/options.js => modules/options.jsm} (84%) diff --git a/common/content/contexts.js b/common/content/contexts.js index 714b45c4..caa5b8c5 100644 --- a/common/content/contexts.js +++ b/common/content/contexts.js @@ -148,6 +148,23 @@ var Contexts = Module("contexts", { context: null, + /** + * Returns a frame object describing the currently executing + * command, if applicable, otherwise returns the passed frame. + * + * @param {nsIStackFrame} frame + */ + getCaller: function getCaller(frame) { + if (this.context && this.context.file) + return { + __proto__: frame, + filename: this.context.file[0] == "[" ? this.context.file + : services.io.newFileURI(File(this.context.file)).spec, + lineNumber: this.context.line + }; + return frame; + }, + groups: Class.memoize(function () Object.create(Group.groupsProto, { groups: { value: this.activeGroups().filter(function (g) g.filter(buffer.uri)) } })), @@ -216,8 +233,8 @@ var Contexts = Module("contexts", { getGroup: function getGroup(name, hive) { if (name === "default") var group = this.context && this.context.context && this.context.context.GROUP; - else - group = set.has(this.groupMap, name) && this.groupMap[name]; + else if (set.has(this.groupMap, name)) + group = this.groupMap[name]; if (group && hive) return group[hive]; diff --git a/common/content/mappings.js b/common/content/mappings.js index 9512e087..6f1c81af 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -170,7 +170,7 @@ var MapHive = Class("MapHive", Group.Hive, { extra = extra || {}; let map = Map(modes, keys, description, action, extra); - map.definedAt = Commands.getCaller(Components.stack.caller); + map.definedAt = contexts.getCaller(Components.stack.caller); map.hive = this; if (this.name !== "builtin") @@ -341,7 +341,7 @@ var Mappings = Module("mappings", { */ add: function () { let map = this.builtin.add.apply(this.builtin, arguments); - map.definedAt = Commands.getCaller(Components.stack.caller); + map.definedAt = contexts.getCaller(Components.stack.caller); return map; }, @@ -358,7 +358,7 @@ var Mappings = Module("mappings", { */ addUserMap: function () { let map = this.user.add.apply(this.user, arguments); - map.definedAt = Commands.getCaller(Components.stack.caller); + map.definedAt = contexts.getCaller(Components.stack.caller); return map; }, @@ -454,6 +454,9 @@ var Mappings = Module("mappings", { if (!rhs) // list the mapping mappings.list(mapmodes, mappings.expandLeader(lhs), hives); else { + util.assert(args["-group"] !== mappings.builtin, + "Cannot change mappings in the builtin group"); + args["-group"].add(mapmodes, [lhs], args["-description"], contexts.bindMacro(args, "-keys", function (params) params), @@ -572,6 +575,9 @@ var Mappings = Module("mappings", { commands.add([ch + "mapc[lear]"], "Remove all mappings" + modeDescription, function (args) { + util.assert(args["-group"] !== mappings.builtin, + "Cannot change mappings in the builtin group"); + let mapmodes = array.uniq(args["-modes"].map(findMode)); mapmodes.forEach(function (mode) { args["-group"].clear(mode); @@ -593,6 +599,9 @@ var Mappings = Module("mappings", { commands.add([ch + "unm[ap]"], "Remove a mapping" + modeDescription, function (args) { + util.assert(args["-group"] !== mappings.builtin, + "Cannot change mappings in the builtin group"); + let mapmodes = array.uniq(args["-modes"].map(findMode)); let found = false; diff --git a/common/content/commands.js b/common/modules/commands.jsm similarity index 78% rename from common/content/commands.js rename to common/modules/commands.jsm index c9118a6a..c426c106 100644 --- a/common/content/commands.js +++ b/common/modules/commands.jsm @@ -6,6 +6,15 @@ // given in the LICENSE.txt file included with this file. "use strict"; +try { + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("commands", { + exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"], + use: ["config", "options", "template", "util"] +}, this); + + /** @scope modules */ /** @@ -141,6 +150,8 @@ var Command = Class("Command", { * @param {Object} modifiers Any modifiers to be passed to {@link #action}. */ execute: function (args, modifiers) { + 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 + ": " : ""; @@ -190,7 +201,7 @@ var Command = Class("Command", { * @returns {Args} * @see Commands#parseArgs */ - parseArgs: function (args, complete, extra) commands.parseArgs(args, { + parseArgs: function parseArgs(args, complete, extra) this.modules.commands.parseArgs(args, { __proto__: this, complete: complete, extra: extra @@ -265,8 +276,9 @@ var Command = Class("Command", { optionMap: Class.memoize(function () array(this.options) .map(function (opt) opt.names.map(function (name) [name, opt])) .flatten().toObject()), - newArgs: function () { + newArgs: function (base) { let res = []; + update(res, base); res.__proto__ = this.argsPrototype; return res; }, @@ -279,8 +291,6 @@ var Command = Class("Command", { command: this, - get context() contexts.context, - explicitOpts: Class.memoize(function () ({})), has: function (opt) set.has(this.explicitOpts, opt) || typeof opt === "number" && set.has(this, opt), @@ -290,13 +300,13 @@ var Command = Class("Command", { // TODO: string: Class.memoize(function () { ... }), verify: function verify() { if (this.command.argCount) { - dactyl.assert((this.length > 0 || !/^[1+]$/.test(this.command.argCount)) && - (this.literal == null || !/[1+]/.test(this.command.argCount) || /\S/.test(this.literalArg || "")), - "E471: Argument required"); + util.assert((this.length > 0 || !/^[1+]$/.test(this.command.argCount)) && + (this.literal == null || !/[1+]/.test(this.command.argCount) || /\S/.test(this.literalArg || "")), + "E471: Argument required"); - dactyl.assert((this.length == 0 || this.command.argCount !== "0") && - (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)), - "E488: Trailing characters"); + util.assert((this.length == 0 || this.command.argCount !== "0") && + (this.length <= 1 || !/^[01?]$/.test(this.command.argCount)), + "E488: Trailing characters"); } } })), @@ -352,11 +362,16 @@ var Command = Class("Command", { }); // Prototype. -var ex = { +var Ex = Module("Ex", { + Local: function Local(dactyl, modules, window) ({ + get commands() modules.commands, + get context() modules.contexts.context + }), + _args: function (cmd, args) { args = Array.slice(args); - let res = cmd.newArgs(); + let res = cmd.newArgs({ context: this.context }); if (isObject(args[0])) for (let [k, v] in Iterator(args.shift())) if (k == "!") @@ -366,8 +381,8 @@ var ex = { else { let opt = cmd.optionMap["-" + k]; let val = opt.type && opt.type.parse(v); - dactyl.assert(val != null && (typeof val !== "number" || !isNaN(val)), - "No such option: " + k); + util.assert(val != null && (typeof val !== "number" || !isNaN(val)), + "No such option: " + k); Class.replaceProperty(args, opt.names[0], val); args.explicitOpts[opt.names[0]] = val; } @@ -376,137 +391,264 @@ var ex = { return res; }, - _complete: function (cmd) + _complete: function (cmd) let (self = this) function _complete(context, func, obj, args) { - args = ex._args(cmd, args); + args = self._args(cmd, args); args.completeArg = args.length - 1; if (cmd.completer && args.length) return cmd.completer(context, args); }, _run: function (name) { - let cmd = commands.get(name); - dactyl.assert(cmd, "No such command"); + const self = this; + let cmd = this.commands.get(name); + util.assert(cmd, "No such command"); return update(function exCommand(options) { - let args = ex._args(cmd, arguments); + let args = self._args(cmd, arguments); args.verify(); return cmd.execute(args); }, { - dactylCompleter: ex._complete(cmd) + dactylCompleter: self._complete(cmd) }); }, __noSuchMethod__: function (meth, args) this._run(meth).apply(this, args) -}; - -var CommandHive = Class("CommandHive", Group.Hive, { - init: function init(group) { - init.supercall(this, group); - this._map = {}; - this._list = []; - }, - - /** @property {Iterator(Command)} @private */ - __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), - - /** @property {string} The last executed Ex command line. */ - repeat: null, - - /** - * Adds a new command. - * - * @param {string[]} names 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. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @optional - */ - add: function (names, description, action, extra, replace) { - extra = extra || {}; - if (!extra.definedAt) - extra.definedAt = Commands.getCaller(Components.stack.caller); - - extra.hive = this; - extra.parsedSpecs = Command.parseSpecs(names); - - let names = array.flatten(extra.parsedSpecs); - let name = names[0]; - - dactyl.assert(!names.some(function (name) name in commands.builtin._map), - "E182: Can't replace non-user command: " + name); - - dactyl.assert(replace || names.every(function (name) !(name in this._map), this), - "Not replacing command " + name); - - for (let name in values(names)) { - ex.__defineGetter__(name, function () this._run(name)); - if (name in this._map) - this.remove(name); - } - - let self = this; - let closure = function () self._map[name]; - - memoize(this._map, name, function () Command(names, description, action, extra)); - memoize(this._list, this._list.length, closure); - for (let alias in values(names.slice(1))) - memoize(this._map, alias, closure); - - return name; - }, - _add: function (names, description, action, extra, replace) { - extra = extra || {}; - extra.definedAt = Commands.getCaller(Components.stack.caller.caller); - return this.add.apply(this, arguments); - }, - - /** - * Returns the command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @param {boolean} full If true, only return a command if one of - * its names matches *name* exactly. - * @returns {Command} - */ - get: function (name, full) this._map[name] - || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) - || null, - - /** - * Remove the user-defined command with matching *name*. - * - * @param {string} name The name of the command to remove. This can be - * any of the command's names. - */ - remove: function (name) { - dactyl.assert(this.group !== contexts.default, - "Cannot delete non-user commands"); - - let cmd = this.get(name); - this._list = this._list.filter(function (c) c !== cmd); - for (let name in values(cmd.names)) - delete this._map[name]; - } }); /** * @instance commands */ var Commands = Module("commands", { - init: function () { - this.user = contexts.hives.commands.user; - this.builtin = contexts.hives.commands.builtin; - }, + lazyInit: true, - hives: Group.Hives("commands", CommandHive), + Local: function Local(dactyl, modules, window) let ({ Group, contexts } = modules) ({ + init: function () { + this.Command = Class("Command", Command, { modules: modules }); + this.user = contexts.hives.commands.user; + this.builtin = contexts.hives.commands.builtin; + }, - get allHives() contexts.allGroups.commands, + get context() contexts.context, - get userHives() this.allHives.filter(function (h) h !== this.builtin, this), + get readHeredoc() modules.io.readHeredoc, + + hives: Group.Hives("commands", Class("CommandHive", Group.Hive, { + init: function init(group) { + init.supercall(this, group); + this._map = {}; + this._list = []; + }, + + /** @property {Iterator(Command)} @private */ + __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), + + /** @property {string} The last executed Ex command line. */ + repeat: null, + + /** + * Adds a new command. + * + * @param {string[]} names 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. + * @param {function} action The action invoked by this command. + * @param {Object} extra An optional extra configuration hash. + * @optional + */ + add: function (names, description, action, extra, replace) { + const { commands } = modules; + + extra = extra || {}; + if (!extra.definedAt) + extra.definedAt = contexts.getCaller(Components.stack.caller); + + extra.hive = this; + extra.parsedSpecs = Command.parseSpecs(names); + + let names = array.flatten(extra.parsedSpecs); + let name = names[0]; + + util.assert(!names.some(function (name) name in commands.builtin._map), + "E182: Can't replace non-user command: " + name); + + util.assert(replace || names.every(function (name) !(name in this._map), this), + "Not replacing command " + name); + + for (let name in values(names)) { + ex.__defineGetter__(name, function () this._run(name)); + if (name in this._map) + this.remove(name); + } + + let self = this; + let closure = function () self._map[name]; + + memoize(this._map, name, function () commands.Command(names, description, action, extra)); + memoize(this._list, this._list.length, closure); + for (let alias in values(names.slice(1))) + memoize(this._map, alias, closure); + + return name; + }, + + _add: function (names, description, action, extra, replace) { + extra = extra || {}; + extra.definedAt = contexts.getCaller(Components.stack.caller.caller); + return this.add.apply(this, arguments); + }, + + /** + * Clear all commands. + * @returns {Command} + */ + clear: function clear() { + util.assert(this.group !== contexts.default, + "Cannot delete non-user commands"); + this._map = {}; + this._list = []; + }, + + /** + * Returns the command with matching *name*. + * + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @param {boolean} full If true, only return a command if one of + * its names matches *name* exactly. + * @returns {Command} + */ + get: function get(name, full) this._map[name] + || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) + || null, + + /** + * Remove the user-defined command with matching *name*. + * + * @param {string} name The name of the command to remove. This can be + * any of the command's names. + */ + remove: function remove(name) { + util.assert(this.group !== contexts.default, + "Cannot delete non-user commands"); + + let cmd = this.get(name); + this._list = this._list.filter(function (c) c !== cmd); + for (let name in values(cmd.names)) + delete this._map[name]; + } + })), + + get allHives() contexts.allGroups.commands, + + get userHives() this.allHives.filter(function (h) h !== this.builtin, this), + + /** + * Executes an Ex command script. + * + * @param {string} string A string containing the commands to execute. + * @param {object} tokens An optional object containing tokens to be + * interpolated into the command string. + * @param {object} args Optional arguments object to be passed to + * command actions. + * @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, context) { + contexts.withContext(context || this.context || { file: "[Command Line]", line: 1 }, + function (context) { + modules.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]); + } + util.assert(false, "Unexpected end of file waiting for " + end); + }; + + 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]/); + let startLine = context.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 = startLine + 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) { + e.message = context.file + ":" + context.line + ": " + e.message; + dactyl.reportError(e, true); + } + } + } + }); + }); + }, + + /** + * Displays a list of user-defined commands. + */ + list: function list() { + function completerToString(completer) { + if (completer) + return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom"; + return ""; + } + + if (!this.userHives.some(function (h) h._list.length)) + dactyl.echomsg("No user-defined commands found"); + else + modules.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 () { ... }"}
); + } + }), /** * @property Indicates that no count was specified for this @@ -532,7 +674,7 @@ var Commands = Module("commands", { add: function () this.builtin._add.apply(this.builtin, arguments), addUserCommand: deprecated("commands.user.add", { get: function addUserCommand() this.user.closure._add }), - getUserCommands: deprecated("iter(commands.user)", function getUserCommands() iter(commands.user).toArray()), + getUserCommands: deprecated("iter(commands.user)", function getUserCommands() iter(this.user).toArray()), removeUserCommand: deprecated("commands.user.remove", { get: function removeUserCommand() this.user.closure.remove }), /** @@ -578,111 +720,6 @@ var Commands = Module("commands", { get: function (name, full) iter(this.hives).map(function ([i, hive]) hive.get(name, full)) .nth(util.identity, 0), - /** - * Displays a list of user-defined commands. - */ - list: function list() { - function completerToString(completer) { - if (completer) - return [k for ([k, v] in Iterator(config.completers)) if (completer == completion.closure[v])][0] || "custom"; - return ""; - } - - if (!commands.userHives.some(function (h) h._list.length)) - dactyl.echomsg("No user-defined commands found"); - else - commandline.commandOutput( - - - - - - - - - - - { - template.map(commands.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 () { ... }"}
); - }, - - /** - * Executes an Ex command script. - * - * @param {string} string A string containing the commands to execute. - * @param {object} tokens An optional object containing tokens to be - * interpolated into the command string. - * @param {object} args Optional arguments object to be passed to - * command actions. - * @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, context) { - contexts.withContext(context || this.context || { file: "[Command Line]", line: 1 }, - function (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); - }; - - 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]/); - let startLine = context.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 = startLine + 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) { - e.message = context.file + ":" + context.line + ": " + e.message; - dactyl.reportError(e, true); - } - } - } - }); - }); - }, - /** * Returns true if a command invocation contains a URL referring to the * domain *host*. @@ -698,7 +735,7 @@ var Commands = Module("commands", { return true; } catch (e) { - dactyl.reportError(e); + util.reportError(e); } return false; }, @@ -764,7 +801,9 @@ var Commands = Module("commands", { * Args object. * @returns {Args} */ - parseArgs: function (str, params) { + parseArgs: function parseArgs(str, params) { + const self = this; + function getNextArg(str, _keepQuotes) { if (arguments.length < 2) _keepQuotes = keepQuotes; @@ -774,7 +813,7 @@ var Commands = Module("commands", { let count = arg.length + 2; if (complete) return [count, "", ""]; - return [count, io.readHeredoc(arg), ""]; + return [count, self.readHeredoc(arg), ""]; } let [count, arg, quote] = Commands.parseArg(str, null, _keepQuotes); @@ -795,7 +834,7 @@ var Commands = Module("commands", { if (!argCount) argCount = "*"; - var args = (params.newArgs || Array).call(params); // parsed options + var args = params.newArgs ? params.newArgs() : []; args.string = str; // for access to the unparsed string // FIXME! @@ -832,7 +871,7 @@ var Commands = Module("commands", { if (complete) complete.message = error; else - dactyl.assert(false, error); + util.assert(false, error); }; outer: @@ -873,7 +912,7 @@ var Commands = Module("commands", { if (sep == "=" || /\s/.test(sep) && opt.type != CommandOption.NOARG) { [count, quoted, quote, error] = getNextArg(sub.substr(optname.length + 1), true); arg = Option.dequote(quoted); - dactyl.assert(!error, error); + 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) @@ -974,7 +1013,7 @@ var Commands = Module("commands", { // if not an option, treat this token as an argument let [count, arg, quote, error] = getNextArg(sub); - dactyl.assert(!error, error); + util.assert(!error, error); if (complete) { args.quote = Commands.complQuote[quote] || Commands.complQuote[""]; @@ -1127,7 +1166,7 @@ var Commands = Module("commands", { parseCommands: function (str, complete) { do { let [count, cmd, bang, args, len] = commands.parseCommand(str); - let command = commands.get(cmd || ""); + let command = this.get(cmd || ""); if (command == null) { yield [null, { commandString: str }]; @@ -1142,7 +1181,8 @@ var Commands = Module("commands", { if (!complete || /(\w|^)[!\s]/.test(str)) args = command.parseArgs(args, context, { count: count, bang: bang }); else - args = commands.parseArgs(args, { extra: { count: count, bang: bang } }); + args = this.parseArgs(args, { extra: { count: count, bang: bang } }); + args.context = this.context; args.commandName = cmd; args.commandString = str.substr(0, len) + args.string; str = args.trailing; @@ -1175,23 +1215,6 @@ var Commands = Module("commands", { get quoteArg() Commands.quoteArg // XXX: better somewhere else? }, { - /** - * Returns a frame object describing the currently executing - * command, if applicable, otherwise returns the passed frame. - * - * @param {nsIStackFrame} frame - */ - getCaller: function (frame) { - if (contexts.context) - return { - __proto__: frame, - filename: contexts.context.file[0] == "[" ? contexts.context.file : - services.io.newFileURI(File(contexts.context.file)).spec, - lineNumber: contexts.context.line - }; - return frame; - }, - // returns [count, parsed_argument] parseArg: function parseArg(str, sep, keepQuotes) { let arg = ""; @@ -1233,15 +1256,19 @@ var Commands = Module("commands", { /[\s"'\\]|^$|^-/.test(str) ? "'" : ""](str) }, { - completion: function () { + completion: function initCompletion(dactyl, modules, window) { + const { completion } = modules; + completion.command = function command(context) { context.title = ["Command"]; context.keys = { text: "longNames", description: "description" }; - context.generate = function () commands.hives.map(function (h) h._list).flatten(); + context.generate = function () modules.commands.hives.map(function (h) h._list).flatten(); }; // provides completions for ex commands, including their arguments completion.ex = function ex(context) { + const { commands } = modules; + // if there is no space between the command name and the cursor // then get completions of the command name for (var [command, args] in commands.parseCommands(context.filter, context)) @@ -1280,18 +1307,20 @@ var Commands = Module("commands", { } } catch (e) { - dactyl.reportError(e); + util.reportError(e); } }; - completion.userCommand = function userCommand(context) { + completion.userCommand = function userCommand(context, group) { context.title = ["User Command", "Definition"]; context.keys = { text: "name", description: "replacementText" }; - context.completions = commands.getUserCommands(); + context.completions = group || modules.commands.user; }; }, - commands: function () { + commands: function (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]"], @@ -1299,12 +1328,15 @@ var Commands = Module("commands", { function (args) { let cmd = args[0]; - dactyl.assert(!cmd || cmd.split(",").every(commands.validName.closure.test), - "E182: Invalid command name"); + util.assert(!cmd || cmd.split(",").every(commands.validName.closure.test), + "E182: Invalid command name"); if (!args.literalArg) commands.list(); else { + util.assert(args["-group"] !== commands.builtin, + "Cannot change commands in the builtin group"); + let completer = args["-complete"]; let completerFunc = null; // default to no completion for user commands @@ -1333,7 +1365,7 @@ var Commands = Module("commands", { }; } else - completerFunc = function (context) completion.closure[config.completers[completer]](context); + completerFunc = function (context) modules.completion.closure[config.completers[completer]](context); } let added = args["-group"].add(cmd.split(","), @@ -1364,8 +1396,9 @@ var Commands = Module("commands", { }, { bang: true, completer: function (context, args) { + const { completion } = modules; if (args.completeArg == 0) - completion.userCommand(context); + completion.userCommand(context, args["-group"]); else args["-javascript"] ? completion.javascript(context) : completion.ex(context); }, @@ -1442,17 +1475,20 @@ var Commands = Module("commands", { commands.add(["comc[lear]"], "Delete all user-defined commands", - function () { - commands.getUserCommands().forEach(function (cmd) { commands.removeUserCommand(cmd.name); }); + function (args) { + args["-group"].clear(); }, - { argCount: "0" }); + { + argCount: "0", + options: [contexts.GroupFlag("commands")] + }); commands.add(["comp[letions]"], "List the completion results for a given command substring", - function (args) { completion.listCompleter("ex", args[0]); }, + function (args) { modules.completion.listCompleter("ex", args[0]); }, { argCount: "1", - completer: function (context, args) completion.ex(context), + completer: function (context, args) modules.completion.ex(context), literal: 0 }); @@ -1461,13 +1497,14 @@ var Commands = Module("commands", { function (args) { let name = args[0]; - if (commands.get(name)) - commands.removeUserCommand(name); + if (args["-group"].get(name)) + args["-group"].remove(name); else dactyl.echoerr("E184: No such user-defined command: " + name); }, { argCount: "1", - completer: function (context) completion.userCommand(context) + completer: function (context, args) modules.completion.userCommand(context, args["-group"]), + options: [contexts.GroupFlag("commands")] }); dactyl.addUsageCommand({ @@ -1491,22 +1528,29 @@ var Commands = Module("commands", { "Yank the output of the given command to the clipboard", function (args) { let cmd = /^:/.test(args[0]) ? args[0] : ":echo " + args[0]; - let res = commandline.withOutputToString(commands.execute, commands, cmd); + + let res = modules.commandline.withOutputToString(commands.execute, commands, cmd); + dactyl.clipboardWrite(res); + let lines = res.split("\n").length; dactyl.echomsg("Yanked " + lines + " line" + (lines == 1 ? "" : "s")); }, { - completer: function (context) completion[/^:/.test(context.filter) ? "ex" : "javascript"](context), + completer: function (context) modules.completion[/^:/.test(context.filter) ? "ex" : "javascript"](context), literal: 0 }); }, - javascript: function () { + javascript: function (dactyl, modules, window) { + const { JavaScript, commands } = modules; + JavaScript.setCompleter([commands.user.get, commands.user.remove], - [function () ([c.name, c.description] for (c in this))]); + [function () [[c.name, c.description] for (c in this)]]); }, - mappings: function () { - mappings.add(config.browserModes, + mappings: function (dactyl, modules, window) { + const { commands, mappings, modes } = modules; + + mappings.add([modes.COMMAND], ["@:"], "Repeat the last Ex command", function (args) { if (commands.repeat) { @@ -1554,4 +1598,8 @@ var Commands = Module("commands", { }; })(); -// vim: set fdm=marker sw=4 ts=4 et: +endModule(); + +} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } + +// vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index 9b177586..3374ec2d 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -42,9 +42,11 @@ var CompletionContext = Class("CompletionContext", { let parent = editor; name = parent.name + "/" + name; - this.autoComplete = this.options.get("autocomplete").getKey(name); - this.sortResults = this.options.get("wildsort").getKey(name); - this.wildcase = this.options.get("wildcase").getKey(name); + if (this.options) { + this.autoComplete = this.options.get("autocomplete").getKey(name); + this.sortResults = this.options.get("wildsort").getKey(name); + this.wildcase = this.options.get("wildcase").getKey(name); + } this.contexts = parent.contexts; if (name in this.contexts) @@ -448,7 +450,7 @@ var CompletionContext = Class("CompletionContext", { let self = this; delete this._substrings; - if (!this.forceAnchored) + if (!this.forceAnchored && this.options) this.anchored = this.options.get("wildanchor").getKey(this.name, this.anchored); // Item matchers diff --git a/common/modules/io.jsm b/common/modules/io.jsm index ee1f8dbe..bb52400c 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -748,16 +748,17 @@ unlet s:cpo_save return lines.map(function (l) l.join("")).join("\n").replace(/\s+\n/gm, "\n"); } + const { commands, options } = modules; file.write(template({ name: config.name, autocommands: wrap("syn keyword " + config.name + "AutoEvent ", keys(config.autocommands)), commands: wrap("syn keyword " + config.name + "Command ", - array(c.specs for (c in commands)).flatten()), + array(c.specs for (c in commands.iterator())).flatten()), options: wrap("syn keyword " + config.name + "Option ", - array(o.names for (o in modules.options) if (o.type != "boolean")).flatten()), + array(o.names for (o in options) if (o.type != "boolean")).flatten()), toggleoptions: wrap("let s:toggleOptions = [", - array(o.realNames for (o in modules.options) if (o.type == "boolean")) + array(o.realNames for (o in options) if (o.type == "boolean")) .flatten().map(String.quote), ", ") + "]" })); diff --git a/common/content/options.js b/common/modules/options.jsm similarity index 84% rename from common/content/options.js rename to common/modules/options.jsm index 67a4e3ec..a6d37d1e 100644 --- a/common/content/options.js +++ b/common/modules/options.jsm @@ -6,6 +6,16 @@ // given in the LICENSE.txt file included with this file. "use strict"; +try { + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("options", { + exports: ["Option", "Options", "ValueError", "options"], + require: ["storage"], + use: ["commands", "completion", "prefs", "services", "template", "util"] +}, this); + + /** @scope modules */ let ValueError = Class("ValueError", ErrorBase); @@ -83,7 +93,7 @@ var Option = Class("Option", { get helpTag() "'" + this.name + "'", initValue: function () { - dactyl.trapErrors(function () this.value = this.value, this); + util.trapErrors(function () this.value = this.value, this); }, get isDefault() this.stringValue === this.stringDefaultValue, @@ -127,13 +137,15 @@ var Option = Class("Option", { let values; - if (dactyl.has("tabs") && (scope & Option.SCOPE_LOCAL)) + /* + if (config.has("tabs") && (scope & Option.SCOPE_LOCAL)) values = tabs.options[this.name]; + */ if ((scope & Option.SCOPE_GLOBAL) && (values == undefined)) values = this.globalValue; if (this.getter) - return dactyl.trapErrors(this.getter, this, values); + return util.trapErrors(this.getter, this, values); return values; }, @@ -151,19 +163,21 @@ var Option = Class("Option", { return; if (this.setter) - newValues = dactyl.trapErrors(this.setter, this, newValues); + newValues = this.modules.dactyl.trapErrors(this.setter, this, newValues); if (newValues === undefined) return; - if (dactyl.has("tabs") && (scope & Option.SCOPE_LOCAL)) + /* + if (config.has("tabs") && (scope & Option.SCOPE_LOCAL)) tabs.options[this.name] = newValues; + */ if ((scope & Option.SCOPE_GLOBAL) && !skipGlobal) this.globalValue = newValues; this.hasChanged = true; this.setFrom = null; - dactyl.triggerObserver("options." + this.name, newValues); + // dactyl.triggerObserver("options." + this.name, newValues); }, getValues: deprecated("Option#get", "get"), @@ -239,7 +253,7 @@ var Option = Class("Option", { } catch (e) { if (!(e instanceof ValueError)) - dactyl.reportError(e); + util.reportError(e); return this.invalidArgument(str || this.stringify(values), operator) + ": " + e.message; } @@ -480,6 +494,7 @@ var Option = Class("Option", { Option._splitAt = 0; return arg; }, + splitList: function (value, keepQuotes) { let res = []; Option._splitAt = 0; @@ -495,6 +510,7 @@ var Option = Class("Option", { } return res; }, + quote: function quote(str, re) Commands.quoteArg[/[\s|"'\\,]|^$/.test(str) || re && re.test && re.test(str) ? (/[\b\f\n\r\t]/.test(str) ? '"' : "'") @@ -514,8 +530,8 @@ var Option = Class("Option", { values = values[(values.indexOf(String(this.value)) + 1) % values.length]; let value = parseInt(values); - dactyl.assert(Number(values) % 1 == 0, - "E521: Number required after =: " + this.name + "=" + values); + util.assert(Number(values) % 1 == 0, + "E521: Number required after =: " + this.name + "=" + values); switch (operator) { case "+": @@ -627,7 +643,7 @@ var Option = Class("Option", { }, validateXPath: function (values) { - let evaluator = XPathEvaluator(); + let evaluator = services.XPathEvaluator(); return this.testValues(values, function (value) evaluator.createExpression(value, util.evaluateXPath.resolver)); } @@ -637,73 +653,124 @@ var Option = Class("Option", { * @instance options */ var Options = Module("options", { - init: function () { - this.needInit = []; - this._options = []; - this._optionMap = {}; + Local: function (dactyl, modules, window) let ({ contexts } = modules) ({ + init: function init() { + const self = this; + this.needInit = []; + this._options = []; + this._optionMap = {}; + this.Option = Class("Option", Option, { modules: modules }); - storage.newMap("options", { store: false }); - storage.addObserver("options", function optionObserver(key, event, option) { - // Trigger any setters. - let opt = options.get(option); - if (event == "change" && opt) - opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true); - }, window); - }, + storage.newMap("options", { store: false }); + storage.addObserver("options", function optionObserver(key, event, option) { + // Trigger any setters. + let opt = self.get(option); + if (event == "change" && opt) + opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true); + }, window); + }, - cleanup: function cleanup() { - for (let opt in this) - if (opt.cleanupValue != null) - opt.value = opt.parse(opt.cleanupValue); - }, + dactyl: dactyl, + + /** + * Lists all options in *scope* or only those with changed values if + * *onlyNonDefault* is specified. + * + * @param {function(Option)} filter Limit the list + * @param {number} scope Only list options in this scope (see + * {@link Option#scope}). + */ + list: function (filter, scope) { + if (!scope) + scope = Option.SCOPE_BOTH; + + function opts(opt) { + for (let opt in Iterator(options)) { + let option = { + __proto__: opt, + isDefault: opt.isDefault, + default: opt.stringDefaultValue, + pre: "\u00a0\u00a0", // Unicode nonbreaking space. + value: <> + }; + + if (filter && !filter(opt)) + continue; + if (!(opt.scope & scope)) + continue; + + if (opt.type == "boolean") { + if (!opt.value) + option.pre = "no"; + option.default = (opt.defaultValue ? "" : "no") + opt.name; + } + else if (isArray(opt.value)) + option.value = <>={template.map(opt.value, function (v) template.highlight(String(v)), <>, )}; + else + option.value = <>={template.highlight(opt.stringValue)}; + yield option; + } + }; + + modules.commandline.commandOutput(template.options("Options", opts(), options["verbose"] > 0)); + }, + + cleanup: function cleanup() { + for (let opt in this) + if (opt.cleanupValue != null) + opt.value = opt.parse(opt.cleanupValue); + }, + + /** + * Adds a new option. + * + * @param {string[]} names All names for the option. + * @param {string} description A description of the option. + * @param {string} type The option type (see {@link Option#type}). + * @param {value} defaultValue The option's default value. + * @param {Object} extra An optional extra configuration hash (see + * {@link Map#extraInfo}). + * @optional + */ + add: function (names, description, type, defaultValue, extraInfo) { + const self = this; + + if (!extraInfo) + extraInfo = {}; + + extraInfo.definedAt = contexts.getCaller(Components.stack.caller); + + let name = names[0]; + if (name in this._optionMap) { + this.dactyl.log("Warning: " + name.quote() + " already exists: replacing existing option.", 1); + this.remove(name); + } + + let closure = function () self._optionMap[name]; + + memoize(this._optionMap, name, function () self.Option(names, description, type, defaultValue, extraInfo)); + for (let alias in values(names.slice(1))) + memoize(this._optionMap, alias, closure); + + if (extraInfo.setter && (!extraInfo.scope || extraInfo.scope & Option.SCOPE_GLOBAL)) + if (this.dactyl.initialized) + closure().initValue(); + else + memoize(this.needInit, this.needInit.length, closure); + + this._floptions = (this._floptions || []).concat(name); + memoize(this._options, this._options.length, closure); + + // quickly access options with options["wildmode"]: + this.__defineGetter__(name, function () this._optionMap[name].value); + this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; }); + } + }), /** @property {Iterator(Option)} @private */ __iterator__: function () values(this._options.sort(function (a, b) String.localeCompare(a.name, b.name))), - /** - * Adds a new option. - * - * @param {string[]} names All names for the option. - * @param {string} description A description of the option. - * @param {string} type The option type (see {@link Option#type}). - * @param {value} defaultValue The option's default value. - * @param {Object} extra An optional extra configuration hash (see - * {@link Map#extraInfo}). - * @optional - */ - add: function (names, description, type, defaultValue, extraInfo) { - if (!extraInfo) - extraInfo = {}; - - extraInfo.definedAt = Commands.getCaller(Components.stack.caller); - - let name = names[0]; - if (name in this._optionMap) { - dactyl.log("Warning: " + name.quote() + " already exists: replacing existing option.", 1); - this.remove(name); - } - - let closure = function () options._optionMap[name]; - - memoize(this._optionMap, name, function () Option(names, description, type, defaultValue, extraInfo)); - for (let alias in values(names.slice(1))) - memoize(this._optionMap, alias, closure); - - if (extraInfo.setter && (!extraInfo.scope || extraInfo.scope & Option.SCOPE_GLOBAL)) - if (dactyl.initialized) - closure().initValue(); - else - memoize(this.needInit, this.needInit.length, closure); - - this._floptions = (this._floptions || []).concat(name); - memoize(this._options, this._options.length, closure); - - // quickly access options with options["wildmode"]: - this.__defineGetter__(name, function () this._optionMap[name].value); - this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; }); - }, - allPrefs: deprecated("prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)), getPref: deprecated("prefs.get", function getPref() prefs.get.apply(prefs, arguments)), invertPref: deprecated("prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)), @@ -734,49 +801,6 @@ var Options = Module("options", { return null; }, - /** - * Lists all options in *scope* or only those with changed values if - * *onlyNonDefault* is specified. - * - * @param {function(Option)} filter Limit the list - * @param {number} scope Only list options in this scope (see - * {@link Option#scope}). - */ - list: function (filter, scope) { - if (!scope) - scope = Option.SCOPE_BOTH; - - function opts(opt) { - for (let opt in Iterator(options)) { - let option = { - __proto__: opt, - isDefault: opt.isDefault, - default: opt.stringDefaultValue, - pre: "\u00a0\u00a0", // Unicode nonbreaking space. - value: <> - }; - - if (filter && !filter(opt)) - continue; - if (!(opt.scope & scope)) - continue; - - if (opt.type == "boolean") { - if (!opt.value) - option.pre = "no"; - option.default = (opt.defaultValue ? "" : "no") + opt.name; - } - else if (isArray(opt.value)) - option.value = <>={template.map(opt.value, function (v) template.highlight(String(v)), <>, )}; - else - option.value = <>={template.highlight(opt.stringValue)}; - yield option; - } - }; - - commandline.commandOutput(template.options("Options", opts(), options["verbose"] > 0)); - }, - /** * Parses a :set command's argument string. * @@ -801,7 +825,7 @@ var Options = Module("options", { } if (matches) { - ret.option = options.get(ret.name, ret.scope); + ret.option = this.get(ret.name, ret.scope); if (!ret.option && (ret.option = options.get(prefix + ret.name, ret.scope))) { ret.name = prefix + ret.name; prefix = ""; @@ -849,7 +873,9 @@ var Options = Module("options", { get store() storage.options }, { }, { - commands: function () { + commands: function initCommands(dactyl, modules, window) { + const { commands, contexts, options } = modules; + let args = { getMode: function (args) findMode(args["-mode"]), iterate: function (args) { @@ -916,7 +942,7 @@ var Options = Module("options", { } if (name == "all" && reset) - commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ", + modules.commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ", function (resp) { if (resp == "yes") for (let pref in values(prefs.getNames())) @@ -946,22 +972,21 @@ var Options = Module("options", { prefs.set(name, value); } else - commandline.commandOutput(prefs.list(onlyNonDefault, name)); + modules.commandline.commandOutput(prefs.list(onlyNonDefault, name)); return; } - let opt = options.parseOpt(arg, modifiers); - dactyl.assert(opt, "Error parsing :set command: " + arg); + let opt = modules.options.parseOpt(arg, modifiers); + util.assert(opt, "Error parsing :set command: " + arg); let option = opt.option; - dactyl.assert(option != null || opt.all, - "E518: Unknown option: " + opt.name); + util.assert(option != null || opt.all, "E518: Unknown option: " + opt.name); // reset a variable to its default value if (opt.reset) { flushList(); if (opt.all) { - for (let option in options) + for (let option in modules.options) option.reset(); } else { @@ -975,7 +1000,7 @@ var Options = Module("options", { else { flushList(); if (opt.option.type === "boolean") { - dactyl.assert(!opt.valueGiven, "E474: Invalid argument: " + arg); + util.assert(!opt.valueGiven, "E474: Invalid argument: " + arg); opt.values = !opt.unsetBoolean; } else if (/^(string|number)$/.test(opt.option.type) && opt.invert) @@ -989,7 +1014,7 @@ var Options = Module("options", { } if (res) dactyl.echoerr(res); - option.setFrom = Commands.getCaller(null); + option.setFrom = contexts.getCaller(null); } } flushList(); @@ -1014,7 +1039,7 @@ var Options = Module("options", { return completion.preference(context); } - let opt = options.parseOpt(filter, modifiers); + let opt = modules.options.parseOpt(filter, modifiers); let prefix = opt.prefix; context.highlight(); @@ -1055,7 +1080,7 @@ var Options = Module("options", { } let optcontext = context.fork("values"); - completion.optionValue(optcontext, opt.name, opt.operator); + modules.completion.optionValue(optcontext, opt.name, opt.operator); // Fill in the current values if we're removing if (opt.operator == "-" && isArray(opt.values)) { @@ -1065,7 +1090,7 @@ var Options = Module("options", { context.maxItems = optcontext.maxItems; context.filters.push(function (i) !set.has(have, i.text)); - completion.optionValue(context, opt.name, opt.operator, null, + modules.completion.optionValue(context, opt.name, opt.operator, null, function (context) { context.generate = function () option.value.map(function (o) [o, ""]); }); @@ -1108,10 +1133,10 @@ var Options = Module("options", { let [, scope, name, op, expr] = matches; let fullName = (scope || "") + name; - dactyl.assert(scope == "g:" || scope == null, - "E461: Illegal variable name: " + scope + name); - dactyl.assert(set.has(globalVariables, name) || (expr && !op), - "E121: Undefined variable: " + fullName); + util.assert(scope == "g:" || scope == null, + "E461: Illegal variable name: " + scope + name); + util.assert(set.has(globalVariables, name) || (expr && !op), + "E121: Undefined variable: " + fullName); if (!expr) dactyl.echo(fullName + "\t\t" + fmt(globalVariables[name])); @@ -1120,7 +1145,7 @@ var Options = Module("options", { var newValue = dactyl.userEval(expr); } catch (e) {} - dactyl.assert(newValue !== undefined, + util.assert(newValue !== undefined, "E15: Invalid expression: " + expr); let value = newValue; @@ -1167,7 +1192,7 @@ var Options = Module("options", { literalArg: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name : opt.name + "=" + opt.stringValue] } - for (opt in options) + for (opt in modules.options) if (!opt.getter && !opt.isDefault && (opt.scope & Option.SCOPE_GLOBAL)) ] } @@ -1182,18 +1207,18 @@ var Options = Module("options", { completer: setCompleter, domains: function (args) array.flatten(args.map(function (spec) { try { - let opt = options.parseOpt(spec); + let opt = modules.options.parseOpt(spec); if (opt.option && opt.option.domains) return opt.option.domains(opt.values); } catch (e) { - dactyl.reportError(e); + util.reportError(e); } return []; })), keepQuotes: true, privateData: function (args) args.some(function (spec) { - let opt = options.parseOpt(spec); + let opt = modules.options.parseOpt(spec); return opt.option && opt.option.privateData && (!callable(opt.option.privateData) || opt.option.privateData(opt.values)); @@ -1223,12 +1248,14 @@ var Options = Module("options", { deprecated: "the options system" }); }, - completion: function () { + completion: function initCompletion(dactyl, modules, window) { + const { completion } = modules; + completion.option = function option(context, scope, prefix) { context.title = ["Option"]; context.keys = { text: "names", description: "description" }; context.anchored = false; - context.completions = options; + context.completions = modules.options; if (prefix == "inv") context.keys.text = function (opt) opt.type == "boolean" || isArray(opt.value) ? opt.names.map(function (n) "inv" + n) @@ -1238,7 +1265,7 @@ var Options = Module("options", { }; completion.optionValue = function (context, name, op, curValue, completer) { - let opt = options.get(name); + let opt = modules.options.get(name); completer = completer || opt.completer; if (!completer || !opt) return; @@ -1301,15 +1328,18 @@ var Options = Module("options", { context.completions = res; }; }, - javascript: function () { + javascript: function initJavascript(dactyl, modules, window) { + const { options, JavaScript } = modules; JavaScript.setCompleter(options.get, [function () ([o.name, o.description] for (o in options))]); }, - sanitizer: function () { + sanitizer: function initSanitizer(dactyl, modules, window) { + const { sanitizer } = modules; + sanitizer.addItem("options", { description: "Options containing hostname data", action: function (timespan, host) { if (host) - for (let opt in values(options._options)) + for (let opt in values(modules.options._options)) if (timespan.contains(opt.lastSet * 1000) && opt.domains) try { opt.value = opt.filterDomain(host, opt.value); @@ -1319,12 +1349,12 @@ var Options = Module("options", { } }, privateEnter: function () { - for (let opt in values(options._options)) + for (let opt in values(modules.options._options)) if (opt.privateData && (!callable(opt.privateData) || opt.privateData(opt.value))) opt.oldValue = opt.value; }, privateLeave: function () { - for (let opt in values(options._options)) + for (let opt in values(modules.options._options)) if (opt.oldValue != null) { opt.value = opt.oldValue; opt.oldValue = null; @@ -1334,4 +1364,8 @@ var Options = Module("options", { } }); -// vim: set fdm=marker sw=4 ts=4 et: +endModule(); + +} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } + +// vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index af88c6be..c878c724 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -154,6 +154,7 @@ var Overlay = Module("Overlay", { defineModule.time("load", null, function _load() { ["addons", "base", + "commands", "completion", "config", "downloads", @@ -161,6 +162,7 @@ var Overlay = Module("Overlay", { "highlight", "io", "javascript", + "options", "overlay", "prefs", "services", @@ -177,14 +179,12 @@ var Overlay = Module("Overlay", { "abbreviations", "autocommands", "buffer", - "commands", "editor", "events", "hints", "mappings", "marks", "mow", - "options", "statusline" ].forEach(function (name) defineModule.time("load", name, modules.load, modules, name)); @@ -325,6 +325,8 @@ var Overlay = Module("Overlay", { } }); +endModule(); + } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } // vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/common/modules/services.jsm b/common/modules/services.jsm index 6c38529b..7b31a422 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -84,6 +84,7 @@ var Services = Module("Services", { this.addClass("Timer", "@mozilla.org/timer;1", Ci.nsITimer, "initWithCallback"); this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1",Ci.nsIAsyncStreamCopier, "init"); this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest); + this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", Ci.nsIDOMXPathEvaluator); this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", Ci.nsIZipReader, "open"); this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter); }, diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 921e11bc..245feccd 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -623,6 +623,9 @@ var Styles = Module("Styles", { this.hive.addRef(this); }, + get names() this.hive.names, + get sheets() this.hive.sheets, + __noSuchMethod__: function __noSuchMethod__(meth, args) { return this.hive[meth].apply(this.hive, args); }, diff --git a/common/modules/util.jsm b/common/modules/util.jsm index b2d62ad4..18f7e83a 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: ["config", "highlight", "storage", "template"] + use: ["commands", "config", "highlight", "storage", "template"] }, this); var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); @@ -22,13 +22,6 @@ var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); default xml namespace = XHTML; -memoize(this, "Commands", function () { - // FIXME - let obj = { Module: Class }; - JSMLoader.loadSubScript("resource://dactyl-content/commands.js", obj); - return obj.Commands; -}); - var FailedAssertion = Class("FailedAssertion", ErrorBase); var Point = Struct("x", "y"); diff --git a/common/tests/functional/dactyl.jsm b/common/tests/functional/dactyl.jsm index b32b7313..f7ce56ab 100644 --- a/common/tests/functional/dactyl.jsm +++ b/common/tests/functional/dactyl.jsm @@ -64,7 +64,7 @@ function Controller(controller) { } this.errors = []; this._countError = function countError(message, highlight) { - if (/\bErrorMsg\b/.test(highlight)) + if (/\b(Error|Warning)Msg\b/.test(highlight)) self.errors.push(String(message)); } this.modules.dactyl.registerObserver("beep", this._countBeep); diff --git a/common/tests/functional/testCommands.js b/common/tests/functional/testCommands.js index 618b688f..1d439861 100644 --- a/common/tests/functional/testCommands.js +++ b/common/tests/functional/testCommands.js @@ -122,7 +122,17 @@ var tests = { singleOutput: ["", "foobar"], noOutput: ["foo bar", "-js bar baz"], multiOutput: [""], - error: ["foo bar", "-js bar baz"] + error: [ + "foo bar", + "-js bar baz", + "-group=builtin baz quux", + "! -group=builtin baz quux", + ], + completions: [ + ["", hasItems], + ["-group=", hasItems], + ["-group=user ", hasItems] + ] }, comclear: { noOutput: [""] @@ -140,6 +150,11 @@ var tests = { delcommand: [ { init: ["comclear", "command foo bar"], + completions: [ + ["", hasItems], + ["-group=", hasItems], + ["-group=user ", hasItems] + ], noOutput: ["foo"] }, { @@ -152,7 +167,6 @@ var tests = { noOutput: ["x"], completions: ["", "x"] }, - delmapgroup: {}, // Skip for now get delmarks() this.delmacros, get delqmarks() this.delmacros, delstyle: { @@ -233,6 +247,25 @@ var tests = { finish: { noOutput: [""] }, forward: { noOutput: [""] }, frameonly: { noOutput: [""] }, + delgroup: { + error: ["builtin"], + completions: [""] + }, + group: { + multiOutput: [""], + noOutput: [ + "foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'", + "! foo -d='foo group' -nopersist -l 'bar.com','http://bar/*','http://bar','^http:'", + "foo", + "user" + ], + error: ["builtin"], + completions: [ + "", + "foo " + ], + cleanup: ["delmapgroup foo"] + }, hardcopy: {}, // Skip for now help: { noOutput: ["", "intro"], @@ -318,7 +351,8 @@ var tests = { ], error: [ "-mode=some-nonexistent-mode ", - "-gtroup=some-nonexistent-group " + "-group=some-nonexistent-group ", + "-group=builtin " ], completions: [ ["", hasItems], @@ -333,25 +367,13 @@ var tests = { }, mapclear: { noOutput: [""], - completions: [""] - }, - mapgroup: { - multiOutput: [""], - noOutput: [ - "foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'", - "! foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'", - "foo", - "user" - ], error: [ - "some-nonexistent-group", - "foo -d='foo group' -nopersist 'bar.com,http://bar/*,http://bar,^http:'" + "-group=builtin" ], completions: [ "", - "foo " - ], - cleanup: ["delmapgroup foo"] + "-group=" + ] }, mark: { error: ["", "#", "xy"], From 89020be0ee72f1f6fd00441b941c6155d5dc9fc7 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 10 Feb 2011 16:23:38 -0500 Subject: [PATCH 46/52] Fix a race. --HG-- branch : groups --- common/content/editor.js | 4 ++++ common/content/modes.js | 4 +++- common/content/mow.js | 27 +++++++++++++++------------ common/modules/base.jsm | 17 ++++++++--------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/common/content/editor.js b/common/content/editor.js index a8938b02..1e5a62b3 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -36,6 +36,8 @@ var Editor = Module("editor", { let text = dactyl.clipboardRead(clipboard); if (!text) return; + if (isinstance(elem, [HTMLInputElement, XULTextBoxElement])) + text = text.replace(/\n+/g, ""); // This is a hacky fix - but it works. // in the bottom of a long textarea bounces up @@ -46,7 +48,9 @@ var Editor = Module("editor", { let end = elem.selectionEnd; let value = elem.value.substring(0, start) + text + elem.value.substring(end); elem.value = value; + Editor.getEditor(elem).rootElement.firstChild.textContent = value; + elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length); elem.selectionEnd = elem.selectionStart; diff --git a/common/content/modes.js b/common/content/modes.js index b9ce2fc9..64334921 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -245,6 +245,8 @@ var Modes = Module("modes", { getCharModes: function (chr) (this.modeChars[chr] || []).slice(), + have: function have(mode) this._modeStack.some(function (m) isinstance(m.main, mode)), + matchModes: function (obj) this._modes.filter(function (mode) Object.keys(obj) .every(function (k) obj[k] == (mode[k] || false))), @@ -397,7 +399,7 @@ var Modes = Module("modes", { }, isinstance: function (obj) - this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj, + this === obj || this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj, allBases: Class.memoize(function () { let seen = {}, res = [], queue = this.bases; diff --git a/common/content/mow.js b/common/content/mow.js index e526efda..5b42c5dd 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -10,25 +10,28 @@ var MOW = Module("mow", { init: function () { this._resize = Timer(20, 400, function () { - if (this.visible) { + if (this.visible) this.resize(false); + + if (this.visible && isinstance(modes.main, modes.OUTPUT_MULTILINE)) this.updateMorePrompt(); - } }, this); this._timer = Timer(20, 400, function () { - this.resize(true); + if (modes.have(modes.OUTPUT_MULTILINE)) { + this.resize(true); - if (options["more"] && this.isScrollable(1)) { - // start the last executed command's output at the top of the screen - let elements = this.document.getElementsByClassName("ex-command-output"); - elements[elements.length - 1].scrollIntoView(true); + if (options["more"] && this.isScrollable(1)) { + // start the last executed command's output at the top of the screen + let elements = this.document.getElementsByClassName("ex-command-output"); + elements[elements.length - 1].scrollIntoView(true); + } + else + this.body.scrollTop = this.body.scrollHeight; + + dactyl.focus(this.window); + this.updateMorePrompt(); } - else - this.body.scrollTop = this.body.scrollHeight; - - dactyl.focus(this.window); - this.updateMorePrompt(); }, this); events.listen(window, this, "windowEvents"); diff --git a/common/modules/base.jsm b/common/modules/base.jsm index d91b41dd..ea894b8c 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -440,25 +440,24 @@ function isinstance(object, interfaces) { if (object == null) return false; - interfaces = Array.concat(interfaces); - for (var i = 0; i < interfaces.length; i++) { - if (typeof interfaces[i] === "string") { - if (objproto.toString.call(object) === "[object " + interfaces[i] + "]") + return Array.concat(interfaces).some(function isinstance_some(iface) { + if (typeof iface === "string") { + if (objproto.toString.call(object) === "[object " + iface + "]") return true; } else if (typeof object === "object" && "isinstance" in object && object.isinstance !== isinstance) { - if (object.isinstance(interfaces[i])) + if (object.isinstance(iface)) return true; } else { - if (object instanceof interfaces[i]) + if (object instanceof iface) return true; var type = isinstance_types[typeof object]; - if (type && isSubclass(interfaces[i], type)) + if (type && isSubclass(iface, type)) return true; } - } - return false; + return false; + }); } /** From 56a28ec0faa3fc5dc218fece57fed1c6d6463d5e Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 10 Feb 2011 17:05:38 -0500 Subject: [PATCH 47/52] Add commands.get completer. Add more JS completer tests. --HG-- branch : groups --- common/content/mappings.js | 10 ++-------- common/modules/commands.jsm | 4 +++- common/tests/functional/testCommands.js | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/common/content/mappings.js b/common/content/mappings.js index 6f1c81af..86ff72ca 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -746,16 +746,10 @@ var Mappings = Module("mappings", { }; }, javascript: function () { - JavaScript.setCompleter(mappings.get, + JavaScript.setCompleter([mappings.get, mappings.builtin.get], [ null, - function (context, obj, args) { - let mode = args[0]; - return array.flatten([ - [[name, map.description] for ([i, name] in Iterator(map.names))] - for (map in mappings.iterate(mode)) - ]); - } + function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))] ]); }, options: function () { diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index c426c106..a7bfde9c 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -1545,7 +1545,9 @@ var Commands = Module("commands", { const { JavaScript, commands } = modules; JavaScript.setCompleter([commands.user.get, commands.user.remove], - [function () [[c.name, c.description] for (c in this)]]); + [function () [[c.names, c.description] for (c in this)]]); + JavaScript.setCompleter([commands.get], + [function () [[c.names, c.description] for (c in this.iterator())]]); }, mappings: function (dactyl, modules, window) { const { commands, mappings, modes } = modules; diff --git a/common/tests/functional/testCommands.js b/common/tests/functional/testCommands.js index 1d439861..e11a3cb3 100644 --- a/common/tests/functional/testCommands.js +++ b/common/tests/functional/testCommands.js @@ -312,7 +312,21 @@ var tests = { ["window", hasItems], ["window.", hasItems], ["window['", hasItems], - ["commands.get('", hasItems] + ["File('", hasItems], + ["File.expandPath('", hasItems], + "autocommands.user.get('", + ["commands.get('", hasItems], + ["commands.builtin.get('", hasItems], + ["highlight.get('", hasItems], + ["highlight.highlightNode(null, '", hasItems], + ["mappings.get(modes.NORMAL, '", hasItems], + // ["mappings.builtin.get(modes.NORMAL, '", hasItems], + ["options.get('", hasItems], + ["prefs.get('", hasItems], + ["prefs.defaults.get('", hasItems], + ["localPrefs.get('", hasItems], + ["localPrefs.defaults.get('", hasItems], + ["styles.system.get('", hasItems], ] }, jumps: { From 30e29714247c58dc011bbf69a31cef9450fb8f9d Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 10 Feb 2011 21:36:03 -0500 Subject: [PATCH 48/52] Experimentally move contexts.js to contexts.jsm. --HG-- branch : groups rename : common/content/contexts.js => common/modules/contexts.jsm --- common/content/autocommands.js | 12 +- common/content/events.js | 16 +- common/content/mappings.js | 39 +- common/modules/base.jsm | 3 +- common/modules/commands.jsm | 252 ++++++----- .../contexts.js => modules/contexts.jsm} | 416 ++++++++++-------- common/modules/io.jsm | 6 +- common/modules/overlay.jsm | 4 +- common/modules/styles.jsm | 10 +- common/modules/util.jsm | 5 + 10 files changed, 419 insertions(+), 344 deletions(-) rename common/{content/contexts.js => modules/contexts.jsm} (71%) diff --git a/common/content/autocommands.js b/common/content/autocommands.js index e24d2e6e..c5357fe1 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -17,7 +17,7 @@ update(AutoCommand.prototype, { } }); -var AutoCmdHive = Class("AutoCmdHive", Group.Hive, { +var AutoCmdHive = Class("AutoCmdHive", Contexts.Hive, { init: function init(group) { init.supercall(this, group); this._store = []; @@ -69,12 +69,14 @@ var AutoCmdHive = Class("AutoCmdHive", Group.Hive, { */ var AutoCommands = Module("autocommands", { init: function () { - this.user = contexts.hives.autocmd.user; + update(this, { + hives: contexts.Hives("autocmd", AutoCmdHive), + user: contexts.hives.autocmd.user + }); }, - hives: Group.Hives("autocmd", AutoCmdHive), - - get activeHives() contexts.activeGroups("autocmd").map(function (h) h.autocmd).filter(function (h) h._store.length), + get activeHives() contexts.initializedGroups("autocmd") + .filter(function (h) h._store.length), add: deprecated("autocommand.user.add", { get: function add() autocommands.user.closure.add }), get: deprecated("autocommand.user.get", { get: function get() autocommands.user.closure.get }), diff --git a/common/content/events.js b/common/content/events.js index de6a65aa..dbbb0f34 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -255,7 +255,7 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { } }); -var EventHive = Class("EventHive", Group.Hive, { +var EventHive = Class("EventHive", Contexts.Hive, { init: function init(group) { init.supercall(this, group); this.sessionListeners = []; @@ -321,6 +321,14 @@ var Events = Module("events", { init: function () { const self = this; + update(this, { + hives: contexts.Hives("events", EventHive), + user: contexts.hives.events.user, + builtin: contexts.hives.events.builtin + }); + + EventHive.prototype.wrapListener = this.closure.wrapListener; + XML.ignoreWhitespace = true; util.overlayWindow(window, { append: @@ -342,10 +350,6 @@ var Events = Module("events", { this._macroKeys = []; this._lastMacro = ""; - EventHive.prototype.wrapListener = this.closure.wrapListener; - this.user = contexts.hives.events.user; - this.builtin = contexts.hives.events.builtin; - this._macros = storage.newMap("macros", { privateData: true, store: true }); for (let [k, m] in this._macros) if (isString(m)) @@ -411,8 +415,6 @@ var Events = Module("events", { }); }, - hives: Group.Hives("events", EventHive), - /** * Adds an event listener for this session and removes it on * dactyl shutdown. diff --git a/common/content/mappings.js b/common/content/mappings.js index 86ff72ca..5ab10f3c 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -138,7 +138,7 @@ var Map = Class("Map", { id: 0 }); -var MapHive = Class("MapHive", Group.Hive, { +var MapHive = Class("MapHive", Contexts.Hive, { init: function init(group) { init.supercall(this, group); this.stacks = {}; @@ -297,14 +297,10 @@ var MapHive = Class("MapHive", Group.Hive, { */ var Mappings = Module("mappings", { init: function () { - this.user = contexts.hives.mappings.user; - this.builtin = contexts.hives.mappings.builtin; }, repeat: Modes.boundProperty(), - hives: Group.Hives("mappings", MapHive), - get allHives() contexts.allGroups.mappings, get userHives() this.allHives.filter(function (h) h !== this.builtin, this), @@ -436,7 +432,14 @@ var Mappings = Module("mappings", { } }, { }, { - commands: function () { + contexts: function initContexts(dactyl, modules, window) { + update(Mappings.prototype, { + hives: contexts.Hives("mappings", MapHive), + user: contexts.hives.mappings.user, + builtin: contexts.hives.mappings.builtin + }); + }, + commands: function initCommands(dactyl, modules, window) { function addMapCommands(ch, mapmodes, modeDescription) { function map(args, noremap) { let mapmodes = array.uniq(args["-modes"].map(findMode)); @@ -454,7 +457,7 @@ var Mappings = Module("mappings", { if (!rhs) // list the mapping mappings.list(mapmodes, mappings.expandLeader(lhs), hives); else { - util.assert(args["-group"] !== mappings.builtin, + util.assert(args["-group"].modifiable, "Cannot change mappings in the builtin group"); args["-group"].add(mapmodes, [lhs], @@ -575,7 +578,7 @@ var Mappings = Module("mappings", { commands.add([ch + "mapc[lear]"], "Remove all mappings" + modeDescription, function (args) { - util.assert(args["-group"] !== mappings.builtin, + util.assert(args["-group"].modifiable, "Cannot change mappings in the builtin group"); let mapmodes = array.uniq(args["-modes"].map(findMode)); @@ -599,7 +602,7 @@ var Mappings = Module("mappings", { commands.add([ch + "unm[ap]"], "Remove a mapping" + modeDescription, function (args) { - util.assert(args["-group"] !== mappings.builtin, + util.assert(args["-group"].modifiable, "Cannot change mappings in the builtin group"); let mapmodes = array.uniq(args["-modes"].map(findMode)); @@ -668,6 +671,12 @@ var Mappings = Module("mappings", { addMapCommands("", [modes.NORMAL, modes.VISUAL], ""); + for (let mode in modes.mainModes) + if (mode.char && !commands.get(mode.char + "map", true)) + addMapCommands(mode.char, + [m.mask for (m in modes.mainModes) if (m.char == mode.char)], + [mode.name.toLowerCase()]); + let args = { getMode: function (args) findMode(args["-mode"]), iterate: function (args) { @@ -729,14 +738,8 @@ var Mappings = Module("mappings", { options: [] }); }); - - for (let mode in modes.mainModes) - if (mode.char && !commands.get(mode.char + "map", true)) - addMapCommands(mode.char, - [m.mask for (m in modes.mainModes) if (m.char == mode.char)], - [mode.name.toLowerCase()]); }, - completion: function () { + completion: function initCompletion(dactyl, modules, window) { completion.userMapping = function userMapping(context, modes, hive) { // FIXME: have we decided on a 'standard' way to handle this clash? --djk hive = hive || mappings.user; @@ -745,14 +748,14 @@ var Mappings = Module("mappings", { context.completions = hive.iterate(modes); }; }, - javascript: function () { + javascript: function initJavascript(dactyl, modules, window) { JavaScript.setCompleter([mappings.get, mappings.builtin.get], [ null, function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))] ]); }, - options: function () { + options: function initOptions(dactyl, modules, window) { options.add(["mapleader", "ml"], "Define the replacement keys for the pseudo-key", "string", "\\", { diff --git a/common/modules/base.jsm b/common/modules/base.jsm index ea894b8c..56d86a3b 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -981,7 +981,8 @@ let StructBase = Class("StructBase", Array, { } }, { fromArray: function (ary) { - ary.__proto__ = this.prototype; + if (!(ary instanceof this)) + ary.__proto__ = this.prototype; return ary; }, diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index a7bfde9c..4f63fb64 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -11,12 +11,10 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("commands", { exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"], + require: ["contexts"], use: ["config", "options", "template", "util"] }, this); - -/** @scope modules */ - /** * A structure representing the options available for a command. * @@ -273,20 +271,20 @@ var Command = Class("Command", { * @see Commands@parseArguments */ options: [], + optionMap: Class.memoize(function () array(this.options) .map(function (opt) opt.names.map(function (name) [name, opt])) .flatten().toObject()), + newArgs: function (base) { let res = []; update(res, base); res.__proto__ = this.argsPrototype; return res; }, - argsPrototype: Class.memoize(function () update([], - array(this.options).filter(function (opt) opt.default !== undefined) - .map(function (opt) [opt.names[0], Class.Property(Object.getOwnPropertyDescriptor(opt, "default"))]) - .toObject(), - { + + argsPrototype: Class.memoize(function () { + let res = update([], { __iterator__: function () array.iterItems(this), command: this, @@ -298,6 +296,7 @@ var Command = Class("Command", { get literalArg() this.command.literal != null && this[this.command.literal] || "", // TODO: string: Class.memoize(function () { ... }), + verify: function verify() { if (this.command.argCount) { util.assert((this.length > 0 || !/^[1+]$/.test(this.command.argCount)) && @@ -309,7 +308,18 @@ var Command = Class("Command", { "E488: Trailing characters"); } } - })), + }); + + this.options.forEach(function (opt) { + if (opt.default !== undefined) + Object.defineProperty(res, opt.names[0], + Object.getOwnPropertyDescriptor(opt, "default") || + { configurable: true, enumerable: true, get: function () opt.default }); + }); + + return res; + }), + /** * @property {boolean|function(args)} When true, invocations of this * command may contain private data which should be purged from @@ -416,6 +426,113 @@ var Ex = Module("Ex", { __noSuchMethod__: function (meth, args) this._run(meth).apply(this, args) }); +var CommandHive = Class("CommandHive", Contexts.Hive, { + init: function init(group) { + init.supercall(this, group); + this._map = {}; + this._list = []; + }, + + /** @property {Iterator(Command)} @private */ + __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), + + /** @property {string} The last executed Ex command line. */ + repeat: null, + + /** + * Adds a new command. + * + * @param {string[]} names 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. + * @param {function} action The action invoked by this command. + * @param {Object} extra An optional extra configuration hash. + * @optional + */ + add: function (names, description, action, extra, replace) { + const { commands, contexts } = this.modules; + + extra = extra || {}; + if (!extra.definedAt) + extra.definedAt = contexts.getCaller(Components.stack.caller); + + extra.hive = this; + extra.parsedSpecs = Command.parseSpecs(names); + + let names = array.flatten(extra.parsedSpecs); + let name = names[0]; + + util.assert(!names.some(function (name) name in commands.builtin._map), + "E182: Can't replace non-user command: " + name); + + util.assert(replace || names.every(function (name) !(name in this._map), this), + "Not replacing command " + name); + + for (let name in values(names)) { + ex.__defineGetter__(name, function () this._run(name)); + if (name in this._map) + this.remove(name); + } + + let self = this; + let closure = function () self._map[name]; + + memoize(this._map, name, function () commands.Command(names, description, action, extra)); + memoize(this._list, this._list.length, closure); + for (let alias in values(names.slice(1))) + memoize(this._map, alias, closure); + + return name; + }, + + _add: function (names, description, action, extra, replace) { + const { contexts } = this.modules; + + extra = extra || {}; + extra.definedAt = contexts.getCaller(Components.stack.caller.caller); + return this.add.apply(this, arguments); + }, + + /** + * Clear all commands. + * @returns {Command} + */ + clear: function clear() { + util.assert(this.group.modifiable, "Cannot delete non-user commands"); + this._map = {}; + this._list = []; + }, + + /** + * Returns the command with matching *name*. + * + * @param {string} name The name of the command to return. This can be + * any of the command's names. + * @param {boolean} full If true, only return a command if one of + * its names matches *name* exactly. + * @returns {Command} + */ + get: function get(name, full) this._map[name] + || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) + || null, + + /** + * Remove the user-defined command with matching *name*. + * + * @param {string} name The name of the command to remove. This can be + * any of the command's names. + */ + remove: function remove(name) { + util.assert(this.group.modifiable, "Cannot delete non-user commands"); + + let cmd = this.get(name); + this._list = this._list.filter(function (c) c !== cmd); + for (let name in values(cmd.names)) + delete this._map[name]; + } +}); + /** * @instance commands */ @@ -425,121 +542,17 @@ var Commands = Module("commands", { Local: function Local(dactyl, modules, window) let ({ Group, contexts } = modules) ({ init: function () { this.Command = Class("Command", Command, { modules: modules }); - this.user = contexts.hives.commands.user; - this.builtin = contexts.hives.commands.builtin; + update(this, { + hives: contexts.Hives("commands", Class("CommandHive", CommandHive, { modules: modules })), + user: contexts.hives.commands.user, + builtin: contexts.hives.commands.builtin + }); }, get context() contexts.context, get readHeredoc() modules.io.readHeredoc, - hives: Group.Hives("commands", Class("CommandHive", Group.Hive, { - init: function init(group) { - init.supercall(this, group); - this._map = {}; - this._list = []; - }, - - /** @property {Iterator(Command)} @private */ - __iterator__: function () array.iterValues(this._list.sort(function (a, b) a.name > b.name)), - - /** @property {string} The last executed Ex command line. */ - repeat: null, - - /** - * Adds a new command. - * - * @param {string[]} names 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. - * @param {function} action The action invoked by this command. - * @param {Object} extra An optional extra configuration hash. - * @optional - */ - add: function (names, description, action, extra, replace) { - const { commands } = modules; - - extra = extra || {}; - if (!extra.definedAt) - extra.definedAt = contexts.getCaller(Components.stack.caller); - - extra.hive = this; - extra.parsedSpecs = Command.parseSpecs(names); - - let names = array.flatten(extra.parsedSpecs); - let name = names[0]; - - util.assert(!names.some(function (name) name in commands.builtin._map), - "E182: Can't replace non-user command: " + name); - - util.assert(replace || names.every(function (name) !(name in this._map), this), - "Not replacing command " + name); - - for (let name in values(names)) { - ex.__defineGetter__(name, function () this._run(name)); - if (name in this._map) - this.remove(name); - } - - let self = this; - let closure = function () self._map[name]; - - memoize(this._map, name, function () commands.Command(names, description, action, extra)); - memoize(this._list, this._list.length, closure); - for (let alias in values(names.slice(1))) - memoize(this._map, alias, closure); - - return name; - }, - - _add: function (names, description, action, extra, replace) { - extra = extra || {}; - extra.definedAt = contexts.getCaller(Components.stack.caller.caller); - return this.add.apply(this, arguments); - }, - - /** - * Clear all commands. - * @returns {Command} - */ - clear: function clear() { - util.assert(this.group !== contexts.default, - "Cannot delete non-user commands"); - this._map = {}; - this._list = []; - }, - - /** - * Returns the command with matching *name*. - * - * @param {string} name The name of the command to return. This can be - * any of the command's names. - * @param {boolean} full If true, only return a command if one of - * its names matches *name* exactly. - * @returns {Command} - */ - get: function get(name, full) this._map[name] - || !full && array.nth(this._list, function (cmd) cmd.hasName(name), 0) - || null, - - /** - * Remove the user-defined command with matching *name*. - * - * @param {string} name The name of the command to remove. This can be - * any of the command's names. - */ - remove: function remove(name) { - util.assert(this.group !== contexts.default, - "Cannot delete non-user commands"); - - let cmd = this.get(name); - this._list = this._list.filter(function (c) c !== cmd); - for (let name in values(cmd.names)) - delete this._map[name]; - } - })), - get allHives() contexts.allGroups.commands, get userHives() this.allHives.filter(function (h) h !== this.builtin, this), @@ -689,7 +702,8 @@ var Commands = Module("commands", { let defaults = {}; if (args.ignoreDefaults) - defaults = array(this.options).map(function (opt) [opt.names[0], opt.default]).toObject(); + defaults = array(this.options).map(function (opt) [opt.names[0], opt.default]) + .toObject(); for (let [opt, val] in Iterator(args.options || {})) { if (val != null && defaults[opt] === val) diff --git a/common/content/contexts.js b/common/modules/contexts.jsm similarity index 71% rename from common/content/contexts.js rename to common/modules/contexts.jsm index caa5b8c5..98c130fa 100644 --- a/common/content/contexts.js +++ b/common/modules/contexts.jsm @@ -4,34 +4,45 @@ // given in the LICENSE.txt file included with this file. "use strict"; +try { + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("contexts", { + exports: ["Contexts", "Group", "contexts"], + use: ["commands", "options", "services", "storage", "styles", "util"] +}, this); + var Group = Class("Group", { init: function init(name, description, filter, persist) { const self = this; + this.name = name; this.description = description; - this.filter = filter || Group.defaultFilter; + this.filter = filter || this.constructor.defaultFilter; this.persist = persist || false; this.hives = []; }, + modifiable: true, + cleanup: function cleanup() { for (let hive in values(this.hives)) - dactyl.trapErrors("cleanup", hive); + util.trapErrors("cleanup", hive); this.hives = []; - for (let hive in keys(Group.hiveMap)) + for (let hive in keys(this.hiveMap)) delete this[hive]; }, destroy: function destroy() { for (let hive in values(this.hives)) - dactyl.trapErrors("destroy", hive); + util.trapErrors("destroy", hive); }, argsExtra: function argsExtra() ({}), get toStringParams() [this.name], - get builtin() contexts.builtinGroups.indexOf(this) >= 0, + get builtin() this.modules.contexts.builtinGroups.indexOf(this) >= 0, }, { compileFilter: function (patterns) { @@ -44,7 +55,10 @@ var Group = Class("Group", { return update(siteFilter, { toString: function () this.filters.join(","), - toXML: function () template.map(this.filters, function (f) {f}, <>,), + toXML: function (modules) let (uri = modules && modules.buffer.uri) + template.map(this.filters, + function (f) {f}, + <>,), filters: patterns.map(function (pattern) { let [, res, filter] = /^(!?)(.*)/.exec(pattern); @@ -57,93 +71,170 @@ var Group = Class("Group", { }); }, - groupsProto: {}, - - defaultFilter: Class.memoize(function () this.compileFilter(["*"])), - - hiveMap: {}, - - Hive: Class("Hive", { - init: function init(group) { - this.group = group; - }, - - cleanup: function cleanup() {}, - destroy: function destroy() {}, - - get argsExtra() this.group.argsExtra, - get builtin() this.group.builtin, - - get name() this.group.name, - set name(val) this.group.name = val, - - get description() this.group.description, - set description(val) this.group.description = val, - - get filter() this.group.filter, - set filter(val) this.group.filter = val, - - get persist() this.group.persist, - set persist(val) this.group.persist = val, - - get toStringParams() [this.name] - }), - - Hives: Class("Hives", 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, name, function () { - let group = constructor(this); - this.hives.push(group); - delete contexts.groups; - return group; - }); - - memoize(Group.hiveMap, name, - function () Object.create(Object.create(contexts.hiveProto, - { _hive: { value: name } }))); - - memoize(Group.groupsProto, name, - function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); - } - }) + defaultFilter: Class.memoize(function () this.compileFilter(["*"])) }); -plugins.contexts = {}; - var Contexts = Module("contexts", { - init: function () { - this.groupList = []; - this.groupMap = {}; - this.hiveProto = {}; + Local: function Local(dactyl, modules, window) ({ + init: function () { + const contexts = this; + this.modules = modules; - this.builtin = this.addGroup("builtin", "Builtin items"); - this.user = this.addGroup("user", "User-defined items", null, true); - this.builtinGroups = [this.builtin, this.user]; + this.groupList = []; + this.groupMap = {}; + this.groupsProto = {}; + this.hives = {}; + this.hiveProto = {}; + + this.user = this.addGroup("user", "User-defined items", null, true); + this.builtin = this.addGroup("builtin", "Builtin items"); + this.builtinGroups = [this.builtin, this.user]; + this.builtin.modifiable = false; + + this.GroupFlag = Class("GroupFlag", CommandOption, { + init: function (name) { + this.name = name; + + this.type = ArgType("group", function (group) { + return isString(group) ? contexts.getGroup(group, name) + : group[name]; + }); + }, + + get toStringParams() [this.name], + + names: ["-group", "-g"], + + description: "Group to which to add", + + get default() (contexts.context && contexts.context.group || contexts.user)[this.name], + + completer: function (context) modules.completion.group(context) + }); + }, + + cleanup: function () { + for (let hive in values(this.groupList)) + util.trapErrors("cleanup", hive); + }, + + destroy: function () { + for (let hive in values(this.groupList)) + util.trapErrors("destroy", hive); + + for (let plugin in values(plugins.contexts)) + if (plugin.onUnload) + util.trapErrors("onUnload", plugin); + }, + + Group: Class("Group", Group, { modules: modules, get hiveMap() modules.contexts.hives }), + + Hives: Class("Hives", Class.Property, { + init: function init(name, constructor) { + const { contexts } = modules; + const self = this; + + if (this.Hive) + return { + enumerable: true, + + get: function () array(contexts.groups[self.name]) + }; + + + this.Hive = constructor; + this.name = name; + memoize(contexts.Group.prototype, name, function () { + let group = constructor(this); + this.hives.push(group); + delete contexts.groups; + return group; + }); + + memoize(contexts.hives, name, + function () Object.create(Object.create(contexts.hiveProto, + { _hive: { value: name } }))); + + memoize(contexts.groupsProto, name, + function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]); + }, + + get toStringParams() [this.name, this.Hive] + }) + }), + + Context: function Context(file, group, args) { + const { contexts, io, newContext, plugins, userContext } = this.modules; + + function Const(val) Class.Property({ enumerable: true, value: val }); + + let isPlugin = array.nth(io.getRuntimeDirectories("plugins"), + function (dir) dir.contains(file, true), + 0); + let isRuntime = array.nth(io.getRuntimeDirectories(""), + function (dir) dir.contains(file, true), + 0); + + let self = set.has(plugins, file.path) && plugins[file.path]; + if (self) { + if (set.has(self, "onUnload")) + self.onUnload(); + } + else { + let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-") + : file.leafName; + + self = update(newContext.apply(null, args || [userContext]), { + NAME: Const(name.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())), + + PATH: Const(file.path), + + CONTEXT: Const(self), + + unload: Const(function unload() { + if (plugins[this.NAME] === this || plugins[this.PATH] === this) + if (this.onUnload) + this.onUnload(); + + if (plugins[this.NAME] === this) + delete plugins[this.NAME]; + + if (plugins[this.PATH] === this) + delete plugins[this.PATH]; + + if (!this.GROUP.builtin) + contexts.removeGroup(this.GROUP); + }) + }); + Class.replaceProperty(plugins, file.path, self); + + // This belongs elsewhere + if (isPlugin && args) + Object.defineProperty(plugins, self.NAME, { + configurable: true, + enumerable: true, + value: self + }); + } + + let path = isRuntime ? file.getRelativeDescriptor(isRuntime) : file.path; + let name = isRuntime ? path.replace(/^(plugin|color)s([\\\/])/, "$1$2") : "script-" + path; + + if (!group) + group = this.addGroup(commands.nameRegexp + .iterate(name.replace(/\.[^.]*$/, "")) + .join("-"), + "Script group for " + file.path, + null, false); + + Class.replaceProperty(self, "GROUP", group); + Class.replaceProperty(self, "group", group); + + return plugins.contexts[file.path] = self; }, - cleanup: function () { - for (let hive in values(this.groupList)) - dactyl.trapErrors("cleanup", hive); - }, - - destroy: function () { - for (let hive in values(this.groupList)) - dactyl.trapErrors("destroy", hive); - - for (let plugin in values(plugins.contexts)) - if (plugin.onUnload) - dactyl.trapErrors("onUnload", plugin); + Script: function Script(file, group) { + return this.Context(file, group, [this.modules.plugins, true]); }, context: null, @@ -165,34 +256,34 @@ var Contexts = Module("contexts", { return frame; }, - groups: Class.memoize(function () Object.create(Group.groupsProto, { - groups: { value: this.activeGroups().filter(function (g) g.filter(buffer.uri)) } + groups: Class.memoize(function () Object.create(this.groupsProto, { + groups: { value: this.activeGroups() }, })), - allGroups: Class.memoize(function () Object.create(Group.groupsProto, { - groups: { value: this.activeGroups() } + allGroups: Class.memoize(function () Object.create(this.groupsProto, { + groups: { value: this.initializedGroups() } })), - activeGroups: function (hive) + activeGroups: function (hive) this.initializedGroups().filter(function (g) g.filter(this), this.modules.buffer.uri), + + initializedGroups: function (hive) let (need = hive ? [hive] : Object.keys(this.hives)) this.groupList.filter(function (group) need.some(function (name) set.has(group, name))), - get hives() Group.hiveMap, - addGroup: function addGroup(name, description, filter, persist, replace) { let group = this.getGroup(name); if (group) name = group.name; if (!group) { - group = Group(name, description, filter, persist); + group = this.Group(name, description, filter, persist); this.groupList.unshift(group); this.groupMap[name] = group; this.hiveProto.__defineGetter__(name, function () group[this._hive]); } if (replace) { - dactyl.trapErrors("cleanup", group); + util.trapErrors("cleanup", group); if (description) group.description = description; if (filter) @@ -213,12 +304,12 @@ var Contexts = Module("contexts", { let group = this.getGroup(name); - dactyl.assert(!group || !group.builtin, "Cannot remove builtin group"); + util.assert(!group || !group.builtin, "Cannot remove builtin group"); if (group) { name = group.name; this.groupList.splice(this.groupList.indexOf(group), 1); - dactyl.trapErrors("destroy", group); + util.trapErrors("destroy", group); } if (this.context && this.context.group === group) @@ -242,6 +333,8 @@ var Contexts = Module("contexts", { }, bindMacro: function (args, default_, params) { + const { dactyl, events } = this.modules; + let process = util.identity; if (callable(params)) @@ -286,111 +379,59 @@ var Contexts = Module("contexts", { return action; }, - GroupFlag: function (name) ({ - names: ["-group", "-g"], - - description: "Group to which to add", - - type: ArgType("group", function (group) isString(group) ? contexts.getGroup(group, name) : group[name]), - - get default() (contexts.context && contexts.context.group || contexts.user)[name], - - completer: function (context) completion.group(context) - }), - withContext: function withContext(defaults, callback, self) this.withSavedValues(["context"], function () { this.context = defaults && update({}, defaults); return callback.call(self, this.context); }) }, { - Context: modules.Script = function Context(file, group, args) { - function Const(val) Class.Property({ enumerable: true, value: val }); + Hive: Class("Hive", { + init: function init(group) { + this.group = group; + }, - let isPlugin = array.nth(io.getRuntimeDirectories("plugins"), - function (dir) dir.contains(file, true), - 0); - let isRuntime = array.nth(io.getRuntimeDirectories(""), - function (dir) dir.contains(file, true), - 0); + cleanup: function cleanup() {}, + destroy: function destroy() {}, - let self = set.has(plugins, file.path) && plugins[file.path]; - if (self) { - if (set.has(self, "onUnload")) - self.onUnload(); - } - else { - let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-") : file.leafName; + get modifiable() this.group.modifiable, - self = update(modules.newContext.apply(null, args || [userContext]), { - NAME: Const(name.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())), + get argsExtra() this.group.argsExtra, + get builtin() this.group.builtin, - PATH: Const(file.path), + get name() this.group.name, + set name(val) this.group.name = val, - CONTEXT: Const(self), + get description() this.group.description, + set description(val) this.group.description = val, - unload: Const(function unload() { - if (plugins[this.NAME] === this || plugins[this.PATH] === this) - if (this.onUnload) - this.onUnload(); + get filter() this.group.filter, + set filter(val) this.group.filter = val, - if (plugins[this.NAME] === this) - delete plugins[this.NAME]; + get persist() this.group.persist, + set persist(val) this.group.persist = val, - if (plugins[this.PATH] === this) - delete plugins[this.PATH]; - - if (!this.GROUP.builtin) - contexts.removeGroup(this.GROUP); - }) - }); - Class.replaceProperty(plugins, file.path, self); - - // This belongs elsewhere - if (isPlugin && args) - Object.defineProperty(plugins, self.NAME, { - configurable: true, - enumerable: true, - value: self - }); - } - - let path = isRuntime ? file.getRelativeDescriptor(isRuntime) : file.path; - - if (!group) - group = contexts.addGroup((isRuntime ? "" : "script-") + - commands.nameRegexp.iterate(path.replace(/\.[^.]*$/, "")) - .join("-"), - "Script group for " + file.path, - null, false); - - Class.replaceProperty(self, "GROUP", group); - Class.replaceProperty(self, "group", group); - - return plugins.contexts[file.path] = self; - }, - Script: function Script(file, group) { - return this.Context(file, group, [plugins, true]); - } + get toStringParams() [this.name] + }) }, { - commands: function initCommands() { + commands: function initCommands(dactyl, modules, window) { + const { commands, contexts } = modules; commands.add(["gr[oup]"], "Create or select a group", function (args) { if (args.length > 0) { var name = Option.dequote(args[0]); - dactyl.assert(name !== "builtin", "Cannot modify builtin group"); - dactyl.assert(commands.validName.test(name), "Invalid group name"); + util.assert(name !== "builtin", "Cannot modify builtin group"); + util.assert(commands.validName.test(name), "Invalid group name"); var group = contexts.getGroup(name); } else if (args.bang) var group = args.context && args.context.group; else - return void completion.listCompleter("group", "", null, null); + return void modules.completion.listCompleter("group", "", null, null); - dactyl.assert(group || name, "No current group"); + util.assert(group || name, "No current group"); let filter = Group.compileFilter(args["-locations"]); if (!group || args.bang) @@ -418,7 +459,7 @@ var Contexts = Module("contexts", { bang: true, completer: function (context, args) { if (args.length == 1) - completion.group(context); + modules.completion.group(context); }, keepQuotes: true, options: [ @@ -460,7 +501,7 @@ var Contexts = Module("contexts", { arguments: [group.name], ignoreDefaults: true } - for (group in values(contexts.activeGroups())) + for (group in values(contexts.initializedGroups())) if (!group.builtin && group.persist) ].concat([{ command: this.name, arguments: ["user"] }]) }); @@ -468,13 +509,13 @@ var Contexts = Module("contexts", { commands.add(["delg[roup]"], "Delete a group", function (args) { - dactyl.assert(contexts.getGroup(args[0]), "No such group: " + args[0]); + util.assert(contexts.getGroup(args[0]), "No such group: " + args[0]); contexts.removeGroup(args[0]); }, { argCount: "1", completer: function (context, args) { - completion.group(context); + modules.completion.group(context); context.filters.push(function ({ item }) !item.builtin); } }); @@ -483,7 +524,7 @@ var Contexts = Module("contexts", { commands.add(["fini[sh]"], "Stop sourcing a script file", function (args) { - dactyl.assert(args.context, "E168: :finish used outside of a sourced file"); + util.assert(args.context, "E168: :finish used outside of a sourced file"); args.context.finished = true; }, { argCount: "0" }); @@ -545,16 +586,18 @@ var Contexts = Module("contexts", { argCount: "0" }); }, - completion: function initCompletion() { + completion: function initCompletion(dactyl, modules, window) { + const { completion, contexts } = modules; + completion.group = function group(context, active) { context.title = ["Group"]; - let uri = buffer.uri; + let uri = modules.buffer.uri; context.keys = { active: function (group) group.filter(uri), text: "name", - description: function (g) <>{g.filter.toXML ? g.filter.toXML() + <>  : ""}{g.description || ""} + description: function (g) <>{g.filter.toXML ? g.filter.toXML(modules) + <>  : ""}{g.description || ""} }; - context.completions = (active === undefined ? contexts.groupList : contexts.activeGroups(active)) + context.completions = (active === undefined ? contexts.groupList : contexts.initializedGroups(active)) .slice(0, -1); iter({ Active: true, Inactive: false }).forEach(function ([name, active]) { @@ -567,3 +610,8 @@ var Contexts = Module("contexts", { } }); +endModule(); + +} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } + +// vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/common/modules/io.jsm b/common/modules/io.jsm index bb52400c..585b8e63 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -145,7 +145,7 @@ var IO = Module("io", { * silent: Whether errors should not be reported. */ source: function source(filename, params) { - const { Contexts, contexts } = modules; + const { contexts } = modules; defineModule.loadLog.push("sourcing " + filename); params = params || {}; @@ -167,7 +167,7 @@ var IO = Module("io", { // handle pure JavaScript files specially if (/\.js$/.test(filename)) { try { - var context = Contexts.Script(file, params.group); + var context = contexts.Script(file, params.group); dactyl.loadScript(uri.spec, context); dactyl.helpInitialized = false; } @@ -186,7 +186,7 @@ var IO = Module("io", { else if (/\.css$/.test(filename)) styles.registerSheet(uri.spec, false, true); else { - context = Contexts.Context(file, params.group); + context = contexts.Context(file, params.group); modules.commands.execute(file.read(), null, params.silent, null, { context: context, diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index c878c724..fe8ea1fd 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -157,6 +157,7 @@ var Overlay = Module("Overlay", { "commands", "completion", "config", + "contexts", "downloads", "finder", "highlight", @@ -172,8 +173,7 @@ var Overlay = Module("Overlay", { "util" ].forEach(function (name) defineModule.time("load", name, require, null, jsmodules, name)); - ["contexts", - "dactyl", + ["dactyl", "modes", "commandline", "abbreviations", diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 245feccd..aee8f4c0 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -8,13 +8,13 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("styles", { exports: ["Style", "Styles", "styles"], require: ["services", "util"], - use: ["template"] + use: ["contexts", "template"] }, this); function cssUri(css) "chrome-data:text/css," + encodeURI(css); var namespace = "@namespace html " + XHTML.uri.quote() + ";\n" + - "@namespace xul " + XUL.uri.quote() + ";\n" + - "@namespace dactyl " + NS.uri.quote() + ";\n"; + "@namespace xul " + XUL.uri.quote() + ";\n" + + "@namespace dactyl " + NS.uri.quote() + ";\n"; var Sheet = Struct("name", "id", "sites", "css", "hive", "agent"); Sheet.liveProperty = function (name) { @@ -614,8 +614,8 @@ var Styles = Module("Styles", { }); }, contexts: function (dactyl, modules, window) { - modules.Group.Hives("styles", - Class("LocalHive", modules.Group.Hive, { + modules.contexts.Hives("styles", + Class("LocalHive", Contexts.Hive, { init: function init(group) { init.superapply(this, arguments); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 18f7e83a..de87279a 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -1405,6 +1405,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } catch (e) { dump(e + "\n"); } } + + // ctypes.open("libc.so.6").declare("kill", ctypes.default_abi, ctypes.void_t, ctypes.int, ctypes.int)( + // ctypes.open("libc.so.6").declare("getpid", ctypes.default_abi, ctypes.int)(), 2) }, /** @@ -1596,6 +1599,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ trapErrors: function trapErrors(func, self) { try { + if (isString(func)) + func = self[func]; return func.apply(self || this, Array.slice(arguments, 2)); } catch (e) { From 43a27346bdbf318c0eefb7e02e87abe65377a076 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 10 Feb 2011 23:45:39 -0500 Subject: [PATCH 49/52] Fix bugs yay. --HG-- branch : groups --- common/content/browser.js | 4 ++++ common/content/mappings.js | 8 ++++---- common/modules/base.jsm | 2 +- common/modules/contexts.jsm | 13 ++++++++----- common/modules/overlay.jsm | 4 +++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/common/content/browser.js b/common/content/browser.js index dc3afd88..3bda8cc3 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -105,6 +105,10 @@ var Browser = Module("browser", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), // only thrown for the current tab, not when another tab changes if (flags & Ci.nsIWebProgressListener.STATE_START) { statusline.progress = 0; + while (document.commandDispatcher.focusedWindow == webProgress.DOMWindow + && modes.have(modes.INPUT)) + modes.pop(); + } else if (flags & Ci.nsIWebProgressListener.STATE_STOP) { // Workaround for bugs 591425 and 606877, dactyl bug #81 diff --git a/common/content/mappings.js b/common/content/mappings.js index 5ab10f3c..35978537 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -387,7 +387,7 @@ var Mappings = Module("mappings", { * @param {string} filter The filter string to match. */ list: function (modes, filter, hives) { - hives = hives || mappings.userHives; + hives = (hives || mappings.userHives).filter(function (h) modes.some(function (m) h.getStack(m).length)); let modeSign = ""; modes.filter(function (m) m.char).forEach(function (m) { modeSign += m.char; }); @@ -410,12 +410,12 @@ var Mappings = Module("mappings", { { - template.map(hives, function (hive) + template.map(hives, function (hive) let (i = 0) + template.map(maps(hive), function (map) - template.map(map.names, function (name, i) + template.map(map.names, function (name) - {!i ? hive.name : ""} + {!i++ ? hive.name : ""} {modeSign} {name} {map.rhs || map.action.toSource()} diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 56d86a3b..698c5505 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -1017,7 +1017,7 @@ var Timer = Class("Timer", { notify: function (timer, force) { try { - if (loaded.util && util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0) + if (!loaded || loaded.util && util.rehashing || typeof util === "undefined" || !force && this.doneAt == 0) return; this._timer.cancel(); diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index 98c130fa..75f271e5 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -80,14 +80,16 @@ var Contexts = Module("contexts", { const contexts = this; this.modules = modules; + modules.plugins.contexts = {}; + this.groupList = []; this.groupMap = {}; this.groupsProto = {}; this.hives = {}; this.hiveProto = {}; - this.user = this.addGroup("user", "User-defined items", null, true); this.builtin = this.addGroup("builtin", "Builtin items"); + this.user = this.addGroup("user", "User-defined items", null, true); this.builtinGroups = [this.builtin, this.user]; this.builtin.modifiable = false; @@ -122,8 +124,8 @@ var Contexts = Module("contexts", { for (let hive in values(this.groupList)) util.trapErrors("destroy", hive); - for (let plugin in values(plugins.contexts)) - if (plugin.onUnload) + for (let [name, plugin] in iter(this.modules.plugins.contexts)) + if (plugin && "onUnload" in plugin) util.trapErrors("onUnload", plugin); }, @@ -360,8 +362,9 @@ var Contexts = Module("contexts", { action.macro = util.compileMacro(rhs, true); break; case "-ex": - action = function action() commands.execute(action.macro, makeParams(this, arguments), - false, null, action.context); + action = function action() this.modules.commands + .execute(action.macro, makeParams(this, arguments), + false, null, action.context); action.macro = util.compileMacro(rhs, true); action.context = this.context && update({}, this.context); break; diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index fe8ea1fd..6bf393c0 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -296,8 +296,10 @@ var Overlay = Module("Overlay", { frob("init"); defineModule.modules.forEach(function ({ lazyInit, constructor: { className } }) { - if (!lazyInit) + if (!lazyInit) { frob(className); + modules[className] = modules[className]; + } else modules.__defineGetter__(className, function () { delete modules[className]; From d06250eeefe03cfe518a2587f2753eb99460c36f Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 11 Feb 2011 03:44:23 -0500 Subject: [PATCH 50/52] Add some rough docs for :group and the various -group flags. --HG-- branch : groups --- common/content/dactyl.js | 4 +- common/locale/en-US/autocommands.xml | 6 ++ common/locale/en-US/map.xml | 22 ++++- common/locale/en-US/repeat.xml | 119 +++++++++++++++++++++++++++ common/locale/en-US/starting.xml | 2 +- common/locale/en-US/styling.xml | 14 +++- common/modules/contexts.jsm | 2 +- 7 files changed, 158 insertions(+), 11 deletions(-) diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 8e9baae6..461e5a8d 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -678,7 +678,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help]; fileMap["versions"] = function () { - let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec).responseText; + let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec, + { mimeType: "text/plain;charset=UTF-8" }) + .responseText; let re = util.regexp( \s*) diff --git a/common/locale/en-US/autocommands.xml b/common/locale/en-US/autocommands.xml index a67e9a13..f326c104 100644 --- a/common/locale/en-US/autocommands.xml +++ b/common/locale/en-US/autocommands.xml @@ -41,6 +41,12 @@ interpreted as an Ex command.

+

+ If the -group=group flag is given, add this autocmd + to the named group. Any filters for group apply in + addition to pat. +

+ This behavior differs from Vim's implementation in that pat is a regular expression rather than a glob. diff --git a/common/locale/en-US/map.xml b/common/locale/en-US/map.xml index e4b6256f..96a325bb 100644 --- a/common/locale/en-US/map.xml +++ b/common/locale/en-US/map.xml @@ -119,6 +119,7 @@
-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)
-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)
@@ -198,7 +199,10 @@ :tmap :cmap -

List all mappings for the applicable mode(s).

+

+ List all mappings for the applicable mode(s). Mappings are + partitioned into groups. +

@@ -561,9 +565,12 @@ - :command cmd + :command cmd -

List all user-defined commands that start with cmd.

+

+ List all user-defined commands that start with cmd. Commands + are partitioned into groups. +

@@ -598,11 +605,18 @@ options when the command is defined.

+

Grouping

+ +

+ The -group flag (short name: -g) can be used to + assign this command to a specific group. +

+

Argument handling

By default, user commands accept no arguments. This can be changed by specifying - the -nargs option. + the -nargs option.

The valid values are:

diff --git a/common/locale/en-US/repeat.xml b/common/locale/en-US/repeat.xml index 56aab737..a713fa6d 100644 --- a/common/locale/en-US/repeat.xml +++ b/common/locale/en-US/repeat.xml @@ -133,6 +133,99 @@ +

Groups

+ +

+ In order to facilitate script writing, especially scripts which only + apply to certain web sites, many types of commands and mappings can + be assigned to a named group. In addition to helping identify the + source of such mappings in listings, and aiding in the cleanup of + scripts, these groups can be configured to apply only to certain web + sites. +

+ + + :gr :group + :group! group + +

List all active groups.

+
+
+ + + :group! group + +

+ Select, create, or modify a group. After invocation, + group becomes the default group for all further commands + issued in the current script. If ! is given the group is + cleared of all mappings, commands, and any other entries bound to + it. +

+ +

The following group names have special meanings:

+ +
+
builtin
The default group for builtin items. Can not be modified in any way by scripts.
+
default
The default group for this script.
+
user
The default group for the command line and &dactyl.name;rc.
+
+ +

The following arguments are available:

+ +
+
-args=javascript
JavaScript Object which augments the arguments passed to commands, mappings, and autocommands (short name: -a)
+
-description
A description of this group (short names: -desc, -d)
+
-locations=filters
The URLs for which this group should be active. See site-filters (short names: -locs, -loc, -l)
+
-nopersist
Do not save this group to an auto-generated RC file (short name: -n)
+
+
+
+ + +

Site Filters

+ +

+ Many &dactyl.appName; commands accept filters so that they may be applied + only to specific sites. Most of these commands accept filters in any of the + following formats: +

+ +
+
domain
+
+ Any filter which is a valid domain name will match any site on that + domain or any sub-domain thereof. These filters may contain any letter + of the Roman alphabet, Arabic numerals, hyphens, and full stops. + Non-Latin domain names must be punycode encoded. +
+ +
URL prefix
+
+ Any URL beginning with a valid protocol name and ending with a + * is treated as a URL prefix. It will match any URL which + begins with the given filter sans the trailing asterisk. +
+ +
Full URL
+
+ Any URL beginning with a valid protocol name and not ending with an + asterisk is treated as a full URL match. It will match any page which + has a URL identical to the filter. +
+ +
Regular expression
+
+ Any filter which does not fall into one of the above categories is + treated as a case-sensitive regular expression. +
+
+ +

+ In most cases, any of the above may be prefixed with a ! character + to invert the sense of the match. +

+

Using scripts

@@ -157,6 +250,32 @@ for more information.

+

Script Contexts

+ +

+ Each script executes in its own JavaScript context. This means that + any global variable or function, including those defined by + :javascript and the -javascript flag of + :map, :command, and :autocmd, + is directly available only within the current script. Outside of the + current script, they can only be accessed as properties of the + script's global object, which is stored in the plugins + global under the script's full path. +

+ +

Script Groups

+ +

+ In addition to its own JavaScript context, each script is executed + with its own default group into which + its styles, mappings, commands, and autocommands are placed. This + means that commands such as :comclear! can be issued + without fear of trampling other user-defined mappings. The command + :group! default can be issued to clear all such items at + once, and should be placed at the head of most scripts to prevent + the accumulation of stale items when the script is re-sourced. +

+

Cascading Stylesheets

diff --git a/common/locale/en-US/starting.xml b/common/locale/en-US/starting.xml index cd9ca6c9..2a6d33d1 100644 --- a/common/locale/en-US/starting.xml +++ b/common/locale/en-US/starting.xml @@ -95,7 +95,7 @@ Windows only. If this file exists, its contents are executed and $MY_&dactyl.idName;RC set to its path. -

  • +
  • ~/.&dactyl.name;rc If this file exists, its contents are executed. diff --git a/common/locale/en-US/styling.xml b/common/locale/en-US/styling.xml index efa47be0..6ac53f5a 100644 --- a/common/locale/en-US/styling.xml +++ b/common/locale/en-US/styling.xml @@ -149,10 +149,11 @@ :style! -name=name -append filter css

    - Add CSS styles to the browser or to web pages. filter is a comma-separated - list of URLs to match. URLs ending with * are matched as prefixes, URLs not - containing any : or / characters are matched as domains. css is a full - CSS rule set (e.g., body { color: blue; }). + 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; }).

    The following options are available:

    @@ -166,6 +167,11 @@ applies to contents user interface widgets as well as normal elements. (short name -A) +
    -group=group
    +
    The group to which to add this style. Please note that + this grouping is for semantic and cleanup purposes only. No + additional site filtering is applied.
    +
    -name=name
    If provided, any existing style with the same name is overridden, and the style may later be deleted using diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index 75f271e5..183bead1 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -9,7 +9,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("contexts", { exports: ["Contexts", "Group", "contexts"], - use: ["commands", "options", "services", "storage", "styles", "util"] + use: ["commands", "options", "services", "storage", "styles", "template", "util"] }, this); var Group = Class("Group", { From caedeceadd89d5b23e245f4f0d599c49e3639f05 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 11 Feb 2011 04:15:23 -0500 Subject: [PATCH 51/52] Add NEWS entry about :groups. --HG-- branch : groups --- common/content/dactyl.js | 6 +++--- common/content/tabs.js | 8 +++++--- common/modules/util.jsm | 8 ++++++-- pentadactyl/NEWS | 12 ++++++++++-- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 461e5a8d..b89b4d9c 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -728,8 +728,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } list = null; - if (level == 0 && /^.*:\n$/.test()) - var elem =

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

    ; + if (level == 0 && /^.*:\n$/.test(match.par)) + res +=

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

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

    { @@ -759,7 +759,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { unescape(encodeURI( // UTF-8 handling hack. -

    {config.appName} Versions

    +

    {config.appName} Versions

    {rec(NEWS, 0)} diff --git a/common/content/tabs.js b/common/content/tabs.js index 1b9a2869..c69aead5 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -40,9 +40,11 @@ var Tabs = Module("tabs", { ]]>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m), false, true); - for (let { linkedBrowser: { contentDocument } } in values(this.allTabs)) - if (contentDocument.readyState === "complete") - dactyl.initDocument(contentDocument); + this.timeout(function () { + for (let { linkedBrowser: { contentDocument } } in values(this.allTabs)) + if (contentDocument.readyState === "complete") + dactyl.initDocument(contentDocument); + }); }, cleanup: function cleanup() { diff --git a/common/modules/util.jsm b/common/modules/util.jsm index de87279a..8022e7e8 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -125,7 +125,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), obj.observers[target].call(obj, subject, data); } catch (e) { - util.reportError(e); + if (typeof util === "undefined") + dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl: ")); + else + util.reportError(e); } }); @@ -1039,7 +1042,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), JSMLoader.cleanup(); - services.observer.addObserver(this, "dactyl-rehash", true); + if (!this.rehashing) + services.observer.addObserver(this, "dactyl-rehash", true); }, "dactyl-rehash": function () { services.observer.removeObserver(this, "dactyl-rehash"); diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index c813a3af..4c6f2f5b 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -8,6 +8,16 @@ - Only visible tabs are considered in tab numbering, gt/gn/gN, etc. [b1] * Improved startup time by a factor of 7. [b1] + * Further improved startup time. [b6] + * Added site-local and script-local groups: + - Added the :group command to define and select groups. + - Added the -group flag to :autocmd, :command, :map, :style, + and friends. + - Mappings and commands can now be bound to groups which + execute only for certain websites. + - Autocommands, commands, mappings, and styles are now + automatically added to per-script groups so that most traces + of a script can be easily purged. * Significant completion speed improvements, especially for JavaScript. [b1] * Greatly improved private mode support and :sanitize command. @@ -76,8 +86,6 @@ and linking to source code locations). [b4] - :downloads now opens a download list in the multi-line output buffer. [b6] - - Added :mapgroup command and -group flag to :map, :unmap, and - :mapclear. [b6] - Added -arg flag to :map. [b6] - :extensions has been replaced with a more powerful :addons. - Added -literal flag to :command. [b6] From 46ffd3004f8239487064a24dfa9f5a4d18afead3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 11 Feb 2011 04:17:32 -0500 Subject: [PATCH 52/52] Add missing version tag. --HG-- branch : groups --- pentadactyl/NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index 4c6f2f5b..224502fc 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -9,7 +9,7 @@ gt/gn/gN, etc. [b1] * Improved startup time by a factor of 7. [b1] * Further improved startup time. [b6] - * Added site-local and script-local groups: + * Added site-local and script-local groups: [b6] - Added the :group command to define and select groups. - Added the -group flag to :autocmd, :command, :map, :style, and friends.