mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-19 06:10:18 +01:00
2043 lines
76 KiB
JavaScript
2043 lines
76 KiB
JavaScript
// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
|
||
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
|
||
// Copyright (c) 2008-2015 Kris Maglione <maglione.k@gmail.com>
|
||
//
|
||
// This work is licensed for reuse under an MIT license. Details are
|
||
// given in the LICENSE.txt file included with this file.
|
||
"use strict";
|
||
|
||
/** @scope modules */
|
||
|
||
var EVAL_ERROR = "__dactyl_eval_error";
|
||
var EVAL_RESULT = "__dactyl_eval_result";
|
||
var EVAL_STRING = "__dactyl_eval_string";
|
||
|
||
var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||
init: function () {
|
||
window.dactyl = this;
|
||
// cheap attempt at compatibility
|
||
let prop = {
|
||
get: deprecated("dactyl", function liberator() { return dactyl; }),
|
||
configurable: true
|
||
};
|
||
Object.defineProperty(window, "liberator", prop);
|
||
Object.defineProperty(modules, "liberator", prop);
|
||
this.commands = {};
|
||
this.indices = {};
|
||
this.modules = modules;
|
||
this._observers = {};
|
||
util.addObserver(this);
|
||
|
||
this.commands["dactyl.restart"] = event => { dactyl.restart(); };
|
||
|
||
styles.registerSheet("resource://dactyl-skin/dactyl.css");
|
||
|
||
this.cleanups = [];
|
||
this.cleanups.push(overlay.overlayObject(window, {
|
||
focusAndSelectUrlBar: function focusAndSelectUrlBar() {
|
||
switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
|
||
case "laissez-faire":
|
||
if (!Events.isHidden(window.gURLBar, true))
|
||
return focusAndSelectUrlBar.superapply(this, arguments);
|
||
default:
|
||
// Evil. Ignore.
|
||
}
|
||
}
|
||
}));
|
||
},
|
||
|
||
cleanup: function () {
|
||
for (let cleanup of this.cleanups)
|
||
cleanup.call(this);
|
||
|
||
delete window.dactyl;
|
||
delete window.liberator;
|
||
|
||
// Prevents box ordering bugs after our stylesheet is removed.
|
||
styles.system.add("cleanup-sheet", config.styleableChrome, literal(function () /*
|
||
#TabsToolbar tab { display: none; }
|
||
*/$));
|
||
styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
|
||
DOM('#TabsToolbar tab', document).style.display;
|
||
},
|
||
|
||
destroy: function () {
|
||
this.observe.unregister();
|
||
autocommands.trigger("LeavePre", {});
|
||
dactyl.triggerObserver("shutdown", null);
|
||
util.dump("All dactyl modules destroyed\n");
|
||
autocommands.trigger("Leave", {});
|
||
},
|
||
|
||
// initially hide all GUI elements, they are later restored unless the user
|
||
// has :set go= or something similar in his config
|
||
hideGUI: function () {
|
||
let guioptions = config.guioptions;
|
||
for (let option of guioptions) {
|
||
guioptions[option].forEach(function (elem) {
|
||
try {
|
||
document.getElementById(elem).collapsed = true;
|
||
}
|
||
catch (e) {}
|
||
});
|
||
}
|
||
},
|
||
|
||
observers: {
|
||
"dactyl-cleanup": function dactyl_cleanup(subject, reason) {
|
||
let modules = dactyl.modules;
|
||
|
||
for (let mod of modules.moduleList.reverse()) {
|
||
mod.stale = true;
|
||
if ("cleanup" in mod)
|
||
this.trapErrors("cleanup", mod, reason);
|
||
if ("destroy" in mod)
|
||
this.trapErrors("destroy", mod, reason);
|
||
}
|
||
|
||
modules.moduleManager.initDependencies("cleanup");
|
||
|
||
for (let name of Object.getOwnPropertyNames(modules).reverse())
|
||
try {
|
||
delete modules[name];
|
||
}
|
||
catch (e) {}
|
||
modules.__proto__ = {};
|
||
}
|
||
},
|
||
|
||
signals: {
|
||
"io.source": function ioSource(context, file, modTime) {
|
||
if (contexts.getDocs(context))
|
||
help.flush("help/plugins.xml", modTime);
|
||
}
|
||
},
|
||
|
||
profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
|
||
|
||
/**
|
||
* @property {Modes.Mode} The current main mode.
|
||
* @see modes#mainModes
|
||
*/
|
||
mode: deprecated("modes.main", {
|
||
get: function mode() modes.main,
|
||
set: function mode(val) modes.main = val
|
||
}),
|
||
|
||
getMenuItems: function getMenuItems(targetPath) {
|
||
function addChildren(node, parent) {
|
||
DOM(node).createContents();
|
||
|
||
if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
|
||
DOM(node).popupshowing({ bubbles: false });
|
||
|
||
for (let item of node.childNodes) {
|
||
if (item.childNodes.length == 0 && item.localName == "menuitem"
|
||
&& !item.hidden
|
||
&& !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
|
||
item.dactylPath = parent + item.getAttribute("label");
|
||
if (!targetPath || targetPath.startsWith(item.dactylPath))
|
||
items.push(item);
|
||
}
|
||
else {
|
||
let path = parent;
|
||
if (item.localName == "menu")
|
||
path += item.getAttribute("label") + ".";
|
||
if (!targetPath || targetPath.startsWith(path))
|
||
addChildren(item, path);
|
||
}
|
||
}
|
||
}
|
||
|
||
let items = [];
|
||
addChildren(document.getElementById(config.guioptions["m"][1]), "");
|
||
return items;
|
||
},
|
||
|
||
get menuItems() { return this.getMenuItems(); },
|
||
|
||
// Global constants
|
||
CURRENT_TAB: "here",
|
||
NEW_TAB: "tab",
|
||
NEW_BACKGROUND_TAB: "background-tab",
|
||
NEW_WINDOW: "window",
|
||
|
||
forceBackground: null,
|
||
forcePrivate: null,
|
||
forceTarget: null,
|
||
|
||
get forceOpen() {
|
||
return { background: this.forceBackground,
|
||
target: this.forceTarget };
|
||
},
|
||
set forceOpen(val) {
|
||
for (let [k, v] of iter({ background: "forceBackground", target: "forceTarget" }))
|
||
if (k in val)
|
||
this[v] = val[k];
|
||
},
|
||
|
||
version: deprecated("config.version", { get: function version() config.version }),
|
||
|
||
/**
|
||
* @property {Object} The map of command-line options. These are
|
||
* specified in the argument to the host application's -{config.name}
|
||
* option. E.g. $ firefox -pentadactyl '+u=/tmp/rcfile ++noplugin'
|
||
* Supported options:
|
||
* +u RCFILE Use RCFILE instead of .pentadactylrc.
|
||
* ++noplugin Don't load plugins.
|
||
* These two can be specified multiple times:
|
||
* ++cmd CMD Execute an Ex command before initialization.
|
||
* +c CMD Execute an Ex command after initialization.
|
||
*/
|
||
commandLineOptions: {
|
||
/** @property Whether plugin loading should be prevented. */
|
||
noPlugins: false,
|
||
/** @property An RC file to use rather than the default. */
|
||
rcFile: null,
|
||
/** @property An Ex command to run before any initialization is performed. */
|
||
preCommands: null,
|
||
/** @property An Ex command to run after all initialization has been performed. */
|
||
postCommands: null
|
||
},
|
||
|
||
registerObserver: function registerObserver(type, callback, weak) {
|
||
if (!(type in this._observers))
|
||
this._observers[type] = [];
|
||
this._observers[type].push(weak ? util.weakReference(callback) : { get: function () callback });
|
||
},
|
||
|
||
registerObservers: function registerObservers(obj, prop) {
|
||
for (let [signal, func] of iter(obj[prop || "signals"]))
|
||
this.registerObserver(signal, func.bind(obj), false);
|
||
},
|
||
|
||
unregisterObserver: function unregisterObserver(type, callback) {
|
||
if (type in this._observers)
|
||
this._observers[type] = this._observers[type].filter(c => c.get() != callback);
|
||
},
|
||
|
||
applyTriggerObserver: function triggerObserver(type, args) {
|
||
if (type in this._observers)
|
||
this._observers[type] = this._observers[type]
|
||
.filter(callback => {
|
||
callback = callback.get();
|
||
if (callback) {
|
||
util.trapErrors(() => callback.apply(null, args));
|
||
return true;
|
||
}
|
||
});
|
||
},
|
||
|
||
triggerObserver: function triggerObserver(type, ...args) {
|
||
return this.applyTriggerObserver(type, args);
|
||
},
|
||
|
||
addUsageCommand: function (params) {
|
||
function keys(item) {
|
||
return (item.names || [item.name]).concat(item.description, item.columns || []);
|
||
}
|
||
|
||
let name = commands.add(params.name, params.description,
|
||
function (args) {
|
||
let results = Ary(params.iterate(args))
|
||
.sort((a, b) => String.localeCompare(a.name, b.name));
|
||
|
||
let filters = args.map(arg => {
|
||
let re = util.regexp.escape(arg);
|
||
return util.regexp("\\b" + re + "\\b|(?:^|[()\\s])" + re + "(?:$|[()\\s])", "i");
|
||
});
|
||
|
||
if (filters.length)
|
||
results = results.filter(item => filters.every(re => keys(item).some(re.bound.test)));
|
||
|
||
commandline.commandOutput(
|
||
template.usage(results, params.format));
|
||
}, {
|
||
argCount: "*",
|
||
completer: function (context, args) {
|
||
context.keys.text = identity;
|
||
context.keys.description = function () {
|
||
return seen[this.text] + /*L*/" matching items";
|
||
};
|
||
context.ignoreCase = true;
|
||
let seen = {};
|
||
context.completions = Ary(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
|
||
for (item of params.iterate(args)))
|
||
.flatten()
|
||
.map(function (k) {
|
||
seen[k] = (seen[k] || 0) + 1;
|
||
return k;
|
||
}).uniq();
|
||
},
|
||
options: params.options || []
|
||
});
|
||
|
||
if (params.index)
|
||
this.indices[params.index] = function* () {
|
||
let results = Ary((params.iterateIndex || params.iterate).call(params, commands.get(name).newArgs()))
|
||
.array.sort((a, b) => String.localeCompare(a.name, b.name));
|
||
|
||
for (let obj of results) {
|
||
let res = dactyl.generateHelp(obj, null, null, true);
|
||
if (!hasOwnProperty(help.tags, obj.helpTag))
|
||
res[0][1].tag = obj.helpTag;
|
||
|
||
yield res;
|
||
}
|
||
};
|
||
},
|
||
|
||
/**
|
||
* Triggers the application bell to notify the user of an error. The
|
||
* bell may be either audible or visual depending on the value of the
|
||
* 'visualbell' option.
|
||
*/
|
||
beep: function () {
|
||
this.triggerObserver("beep");
|
||
if (options["visualbell"]) {
|
||
let elems = {
|
||
bell: document.getElementById("dactyl-bell"),
|
||
strut: document.getElementById("dactyl-bell-strut")
|
||
};
|
||
if (!elems.bell)
|
||
overlay.overlayWindow(window, {
|
||
objects: elems,
|
||
prepend: [
|
||
["window", { id: document.documentElement.id, xmlns: "xul" },
|
||
["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell", key: "bell" }]]],
|
||
append: [
|
||
["window", { id: document.documentElement.id, xmlns: "xul" },
|
||
["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell-strut", key: "strut" }]]]
|
||
}, elems);
|
||
|
||
elems.bell.style.height = window.innerHeight + "px";
|
||
elems.strut.style.marginBottom = -window.innerHeight + "px";
|
||
elems.strut.style.display = elems.bell.style.display = "";
|
||
|
||
util.timeout(function () { elems.strut.style.display = elems.bell.style.display = "none"; }, 20);
|
||
}
|
||
else {
|
||
let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
|
||
soundService.beep();
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Reads a string from the system clipboard.
|
||
*
|
||
* This is same as Firefox's readFromClipboard function, but is needed for
|
||
* apps like Thunderbird which do not provide it.
|
||
*
|
||
* @param {string} which Which clipboard to write to. Either
|
||
* "global" or "selection". If not provided, both clipboards are
|
||
* updated.
|
||
* @optional
|
||
* @returns {string}
|
||
*/
|
||
clipboardRead: function clipboardRead(which) {
|
||
try {
|
||
const { clipboard } = services;
|
||
|
||
let transferable = services.Transferable();
|
||
transferable.addDataFlavor("text/unicode");
|
||
|
||
let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
|
||
"kGlobalClipboard" : "kSelectionClipboard"];
|
||
clipboard.getData(transferable, source);
|
||
|
||
let str = {}, len = {};
|
||
transferable.getTransferData("text/unicode", str, len);
|
||
|
||
if (str)
|
||
return str.value.QueryInterface(Ci.nsISupportsString)
|
||
.data.substr(0, len.value / 2);
|
||
}
|
||
catch (e) {}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* Copies a string to the system clipboard. If *verbose* is specified the
|
||
* copied string is also echoed to the command line.
|
||
*
|
||
* @param {string} str The string to write.
|
||
* @param {boolean} verbose If true, the user is notified of the copied data.
|
||
* @param {string} which Which clipboard to write to. Either
|
||
* "global" or "selection". If not provided, both clipboards are
|
||
* updated.
|
||
* @optional
|
||
*/
|
||
clipboardWrite: function clipboardWrite(str, verbose, which) {
|
||
if (which == null || which == "selection" && !services.clipboard.supportsSelectionClipboard())
|
||
services.clipboardHelper.copyString(str);
|
||
else
|
||
services.clipboardHelper.copyStringToClipboard(str,
|
||
services.clipboard["k" + util.capitalize(which) + "Clipboard"]);
|
||
|
||
if (verbose) {
|
||
let message = { message: _("dactyl.yank", str) };
|
||
try {
|
||
message.domains = [util.newURI(str).host];
|
||
}
|
||
catch (e) {};
|
||
dactyl.echomsg(message);
|
||
}
|
||
},
|
||
|
||
dump: deprecated("util.dump",
|
||
{ get: function dump() util.bound.dump }),
|
||
dumpStack: deprecated("util.dumpStack",
|
||
{ get: function dumpStack() util.bound.dumpStack }),
|
||
|
||
/**
|
||
* Outputs a plain message to the command line.
|
||
*
|
||
* @param {string} str The message to output.
|
||
* @param {number} flags These control the multi-line message behavior.
|
||
* See {@link CommandLine#echo}.
|
||
*/
|
||
echo: function echo(str, flags) {
|
||
commandline.echo(str, commandline.HL_NORMAL, flags);
|
||
},
|
||
|
||
/**
|
||
* Outputs an error message to the command line.
|
||
*
|
||
* @param {string} str The message to output.
|
||
* @param {number} flags These control the multi-line message behavior.
|
||
* See {@link CommandLine#echo}.
|
||
*/
|
||
echoerr: function echoerr(str, flags) {
|
||
flags |= commandline.APPEND_TO_MESSAGES;
|
||
|
||
if (isinstance(str, ["DOMException", "Error", "Exception", ErrorBase])
|
||
|| isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
|
||
dactyl.reportError(str);
|
||
|
||
if (isObject(str) && "echoerr" in str)
|
||
str = str.echoerr;
|
||
else if (isinstance(str, ["Error", FailedAssertion]) && str.fileName)
|
||
str = [str.fileName.replace(/^.* -> /, ""), ": ", str.lineNumber, ": ", str].join("");
|
||
|
||
if (options["errorbells"])
|
||
dactyl.beep();
|
||
|
||
commandline.echo(str, commandline.HL_ERRORMSG, flags);
|
||
},
|
||
|
||
/**
|
||
* Outputs a warning message to the command line.
|
||
*
|
||
* @param {string} str The message to output.
|
||
* @param {number} flags These control the multi-line message behavior.
|
||
* See {@link CommandLine#echo}.
|
||
*/
|
||
warn: function warn(str, flags) {
|
||
commandline.echo(str, "WarningMsg", flags | commandline.APPEND_TO_MESSAGES);
|
||
},
|
||
|
||
// TODO: add proper level constants
|
||
/**
|
||
* Outputs an information message to the command line.
|
||
*
|
||
* @param {string} str The message to output.
|
||
* @param {number} verbosity The messages log level (0 - 15). Only
|
||
* messages with verbosity less than or equal to the value of the
|
||
* *verbosity* option will be output.
|
||
* @param {number} flags These control the multi-line message behavior.
|
||
* See {@link CommandLine#echo}.
|
||
*/
|
||
echomsg: function echomsg(str, verbosity, flags) {
|
||
if (verbosity == null)
|
||
verbosity = 0; // verbosity level is exclusionary
|
||
|
||
if (options["verbose"] >= verbosity)
|
||
commandline.echo(str, commandline.HL_INFOMSG,
|
||
flags | commandline.APPEND_TO_MESSAGES);
|
||
},
|
||
|
||
/**
|
||
* Loads and executes the script referenced by *uri* in the scope of the
|
||
* *context* object.
|
||
*
|
||
* @param {string} uri The URI of the script to load. Should be a local
|
||
* chrome:, file:, or resource: URL.
|
||
* @param {Object} context The context object into which the script
|
||
* should be loaded.
|
||
*/
|
||
loadScript: function loadScript(uri, context) {
|
||
let prefix = "literal:" + uri + ":";
|
||
cache.flush(s => s.startsWith(prefix));
|
||
delete literal.files[uri];
|
||
JSMLoader.loadSubScript(uri, context, File.defaultEncoding);
|
||
},
|
||
|
||
userEval: function userEval(str, context, fileName, lineNumber) {
|
||
let ctxt,
|
||
info = contexts.context;
|
||
|
||
if (fileName == null)
|
||
if (info)
|
||
({ file: fileName, line: lineNumber, context: ctxt } = info);
|
||
|
||
if (fileName && fileName[0] == "[")
|
||
fileName = "dactyl://command-line/";
|
||
else if (!context)
|
||
context = ctxt || userContext;
|
||
|
||
if (!context)
|
||
context = userContext || ctxt;
|
||
|
||
if (isinstance(context, ["Sandbox"]))
|
||
return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
|
||
|
||
if (services.has("dactyl") && services.dactyl.evalInContext)
|
||
return services.dactyl.evalInContext(str, context, fileName, lineNumber);
|
||
|
||
try {
|
||
context[EVAL_ERROR] = null;
|
||
context[EVAL_STRING] = str;
|
||
context[EVAL_RESULT] = null;
|
||
|
||
this.loadScript("resource://dactyl-content/eval.js", context);
|
||
if (context[EVAL_ERROR]) {
|
||
try {
|
||
context[EVAL_ERROR].fileName = info.file;
|
||
context[EVAL_ERROR].lineNumber += info.line;
|
||
}
|
||
catch (e) {}
|
||
throw context[EVAL_ERROR];
|
||
}
|
||
return context[EVAL_RESULT];
|
||
}
|
||
finally {
|
||
delete context[EVAL_ERROR];
|
||
delete context[EVAL_RESULT];
|
||
delete context[EVAL_STRING];
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Acts like the Function builtin, but the code executes in the
|
||
* userContext global.
|
||
*/
|
||
userFunc: function userFunc(...args) {
|
||
return this.userEval(
|
||
"(function userFunction(" + args.slice(0, -1).join(", ") + ")" +
|
||
" { " + args.pop() + " })");
|
||
},
|
||
|
||
/**
|
||
* Execute an Ex command string. E.g. ":zoom 300".
|
||
*
|
||
* @param {string} str The command to execute.
|
||
* @param {Object} modifiers Any modifiers to be passed to
|
||
* {@link Command#action}.
|
||
* @param {boolean} silent Whether the command should be echoed on the
|
||
* command line.
|
||
*/
|
||
execute: function execute(str, modifiers={}, silent=false) {
|
||
// skip comments and blank lines
|
||
if (/^\s*("|$)/.test(str))
|
||
return;
|
||
|
||
if (!silent)
|
||
commands.lastCommand = str.replace(/^\s*:\s*/, "");
|
||
let res = true;
|
||
for (let [command, args] of commands.parseCommands(str.replace(/^'(.*)'$/, "$1"))) {
|
||
if (command === null)
|
||
throw FailedAssertion(_("dactyl.notCommand", config.appName, args.commandString));
|
||
|
||
res = res && command.execute(args, modifiers);
|
||
}
|
||
return res;
|
||
},
|
||
|
||
focus: function focus(elem, flags) {
|
||
DOM(elem).focus(flags);
|
||
},
|
||
|
||
/**
|
||
* Focuses the content window.
|
||
*
|
||
* @param {boolean} clearFocusedElement Remove focus from any focused
|
||
* element.
|
||
*/
|
||
focusContent: function focusContent(clearFocusedElement) {
|
||
if (window != services.focus.activeWindow)
|
||
return;
|
||
|
||
let win = document.commandDispatcher.focusedWindow;
|
||
let elem = config.mainWidget || content;
|
||
|
||
// TODO: make more generic
|
||
try {
|
||
if (this.has("mail") && !config.isComposeWindow) {
|
||
let i = gDBView.selection.currentIndex;
|
||
if (i == -1 && gDBView.rowCount >= 0)
|
||
i = 0;
|
||
gDBView.selection.select(i);
|
||
}
|
||
else {
|
||
let frame = buffer.focusedFrame;
|
||
if (frame && frame.top == content && !Editor.getEditor(frame))
|
||
elem = frame;
|
||
}
|
||
}
|
||
catch (e) {}
|
||
|
||
if (clearFocusedElement) {
|
||
if (dactyl.focusedElement)
|
||
dactyl.focusedElement.blur();
|
||
if (win && Editor.getEditor(win)) {
|
||
this.withSavedValues(["ignoreFocus"], function _focusContent() {
|
||
this.ignoreFocus = true;
|
||
if (win.frameElement)
|
||
win.frameElement.blur();
|
||
// Grr.
|
||
if (content.document.activeElement instanceof Ci.nsIDOMHTMLIFrameElement)
|
||
content.document.activeElement.blur();
|
||
});
|
||
}
|
||
}
|
||
|
||
if (elem instanceof Window && Editor.getEditor(elem))
|
||
elem = window;
|
||
|
||
if (elem && elem != dactyl.focusedElement)
|
||
dactyl.focus(elem);
|
||
},
|
||
|
||
/** @property {Element} The currently focused element. */
|
||
get focusedElement() {
|
||
return services.focus.getFocusedElementForWindow(window, true, {});
|
||
},
|
||
set focusedElement(elem) { dactyl.focus(elem); },
|
||
|
||
/**
|
||
* Returns whether this Dactyl extension supports *feature*.
|
||
*
|
||
* @param {string} feature The feature name.
|
||
* @returns {boolean}
|
||
*/
|
||
has: function has(feature) config.has(feature),
|
||
|
||
/**
|
||
* @private
|
||
*/
|
||
initDocument: function initDocument(doc) {
|
||
try {
|
||
if (doc.location.protocol === "dactyl:") {
|
||
dactyl.initHelp();
|
||
config.styleHelp();
|
||
}
|
||
}
|
||
catch (e) {
|
||
util.reportError(e);
|
||
}
|
||
},
|
||
|
||
help: deprecated("help.help", { get: function help() modules.help.bound.help }),
|
||
findHelp: deprecated("help.findHelp", { get: function findHelp() help.bound.findHelp }),
|
||
|
||
/**
|
||
* @private
|
||
* Initialize the help system.
|
||
*/
|
||
initHelp: function initHelp() {
|
||
if ("noscriptOverlay" in window)
|
||
window.noscriptOverlay.safeAllow("dactyl:", true, false);
|
||
|
||
help.initialize();
|
||
},
|
||
|
||
/**
|
||
* Generates a help entry and returns it as a string.
|
||
*
|
||
* @param {Command|Map|Option} obj A dactyl *Command*, *Map* or *Option*
|
||
* object
|
||
* @param {XMLList} extraHelp Extra help text beyond the description.
|
||
* @returns {string}
|
||
*/
|
||
generateHelp: function generateHelp(obj, extraHelp, str, specOnly) {
|
||
let link, tag, spec;
|
||
link = tag = spec = identity;
|
||
let args = null;
|
||
|
||
if (obj instanceof Command) {
|
||
link = cmd => ["ex", {}, cmd];
|
||
args = obj.parseArgs("", CompletionContext(str || ""));
|
||
tag = cmd => DOM.DOMString(":" + cmd);
|
||
spec = cmd => [
|
||
obj.count ? ["oa", {}, "count"] : [],
|
||
cmd,
|
||
obj.bang ? ["oa", {}, "!"] : []
|
||
];
|
||
}
|
||
else if (obj instanceof Map) {
|
||
spec = map => (obj.count ? [["oa", {}, "count"], map]
|
||
: DOM.DOMString(map));
|
||
tag = map => {
|
||
let c = obj.modes[0].char;
|
||
return [c ? c + "_" : "",
|
||
map];
|
||
};
|
||
|
||
link = map => {
|
||
let [, mode, name, extra] = /^(?:(.)_)?(?:<([^>]+)>)?(.*)$/.exec(map);
|
||
let k = ["k", {}, extra];
|
||
if (name)
|
||
k[1].name = name;
|
||
if (mode)
|
||
k[1].mode = mode;
|
||
return k;
|
||
};
|
||
}
|
||
else if (obj instanceof Option) {
|
||
spec = () => template.map(obj.names, tag, " ");
|
||
tag = name => DOM.DOMString("'" + name + "'");
|
||
link = (opt, name) => ["o", {}, name];
|
||
args = { value: "", values: [] };
|
||
}
|
||
|
||
let res = [
|
||
["dt", {}, link(obj.helpTag || tag(obj.name), obj.name)],
|
||
["dd", {},
|
||
template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)]];
|
||
if (specOnly)
|
||
return res;
|
||
|
||
let description = ["description", {},
|
||
obj.description ? ["p", {}, template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)] : "",
|
||
extraHelp ? extraHelp : "",
|
||
!(extraHelp || obj.description) ? ["p", {}, /*L*/ "Sorry, no help available."] : ""];
|
||
|
||
let name = (obj.specs || obj.names)[0];
|
||
res.push(
|
||
["item", {},
|
||
["tags", {}, template.map(obj.names.slice().reverse(),
|
||
tag,
|
||
" ").join("")],
|
||
["spec", {}, spec(template.highlightRegexp(tag(name),
|
||
/\[(.*?)\]/g,
|
||
(m, n0) => ["oa", {}, n0]),
|
||
name)],
|
||
!obj.type ? "" : [
|
||
["type", {}, obj.type],
|
||
["default", {}, obj.stringDefaultValue]],
|
||
description]);
|
||
|
||
function add(ary) {
|
||
description.push(
|
||
["dl", {}, template.map(ary,
|
||
([a, b]) => [["dt", {}, a], " ",
|
||
["dd", {}, b]])]);
|
||
}
|
||
|
||
if (obj.completer && false)
|
||
add(completion._runCompleter(obj.bound.completer, "", null, args).items
|
||
.map(i => [i.text, i.description]));
|
||
|
||
if (obj.options && obj.options.some(o => o.description) && false)
|
||
add(obj.options.filter(o => o.description)
|
||
.map(o => [
|
||
o.names[0],
|
||
[o.description,
|
||
o.names.length == 1 ? "" :
|
||
["", " (short name: ",
|
||
template.map(o.names.slice(1),
|
||
n => ["em", {}, n],
|
||
", "),
|
||
")"]]
|
||
]));
|
||
|
||
return DOM.toPrettyXML(res, true, null, { "": String(NS) });
|
||
},
|
||
|
||
/**
|
||
* The map of global variables.
|
||
*
|
||
* These are set and accessed with the "g:" prefix.
|
||
*/
|
||
_globalVariables: {},
|
||
globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
|
||
get: function globalVariables() this._globalVariables
|
||
}),
|
||
|
||
loadPlugins: function loadPlugins(args, force) {
|
||
function sourceDirectory(dir) {
|
||
dactyl.assert(dir.isReadable(), _("io.notReadable", dir.path));
|
||
|
||
dactyl.log(_("dactyl.sourcingPlugins", dir.path), 3);
|
||
|
||
let loadplugins = options.get("loadplugins");
|
||
if (args)
|
||
loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
|
||
|
||
dir.readDirectory(true).forEach(function (file) {
|
||
if (file.leafName[0] == ".")
|
||
;
|
||
else if (file.isFile() && loadplugins.getKey(file.path)
|
||
&& !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
|
||
try {
|
||
io.source(file.path);
|
||
dactyl.pluginFiles[file.path] = file.lastModifiedTime;
|
||
}
|
||
catch (e) {
|
||
dactyl.reportError(e);
|
||
}
|
||
}
|
||
else if (file.isDirectory())
|
||
sourceDirectory(file);
|
||
});
|
||
}
|
||
|
||
let dirs = io.getRuntimeDirectories("plugins");
|
||
|
||
if (dirs.length == 0) {
|
||
dactyl.log(_("dactyl.noPluginDir"), 3);
|
||
return;
|
||
}
|
||
|
||
dactyl.echomsg(
|
||
_("plugin.searchingForIn",
|
||
JSON.stringify("plugins/**/*.{js," + config.fileExtension + "}"),
|
||
JSON.stringify([dir.path.replace(/.plugins$/, "")
|
||
for (dir of dirs)]
|
||
.join(","))),
|
||
2);
|
||
|
||
dirs.forEach(function (dir) {
|
||
dactyl.echomsg(_("plugin.searchingFor", JSON.stringify(dir.path + "/**/*.{js," + config.fileExtension + "}")), 3);
|
||
sourceDirectory(dir);
|
||
});
|
||
},
|
||
|
||
// TODO: add proper level constants
|
||
/**
|
||
* Logs a message to the JavaScript error console. Each message has an
|
||
* associated log level. Only messages with a log level less than or equal
|
||
* to *level* will be printed. If *msg* is an object, it is pretty printed.
|
||
*
|
||
* @param {string|Object} msg The message to print.
|
||
* @param {number} level The logging level 0 - 15.
|
||
*/
|
||
log: function log(msg, level) {
|
||
let verbose = config.prefs.get("loglevel", 0);
|
||
|
||
if (!level || level <= verbose) {
|
||
if (isObject(msg) && !isinstance(msg, _))
|
||
msg = util.objectToString(msg, false);
|
||
|
||
services.console.logStringMessage(config.name + ": " + msg);
|
||
}
|
||
},
|
||
|
||
events: {
|
||
beforecustomization: function onbeforecustomization(event) {
|
||
// Show navigation bar on Australis, where it's not supposed
|
||
// to be collapsible, and is therefore not handled by
|
||
// builtin code.
|
||
if ("CustomizableUI" in window)
|
||
this.setNodeVisible(document.getElementById("nav-bar"),
|
||
true);
|
||
},
|
||
|
||
aftercustomization: function onaftercustomization(event) {
|
||
// Restore toolbar states.
|
||
options["guioptions"] = options["guioptions"];
|
||
},
|
||
|
||
click: function onClick(event) {
|
||
let elem = event.originalTarget;
|
||
|
||
if (elem instanceof Ci.nsIDOMElement && services.security.isSystemPrincipal(elem.nodePrincipal)) {
|
||
let command = elem.getAttributeNS(NS, "command");
|
||
if (command && event.button == 0) {
|
||
event.preventDefault();
|
||
|
||
if (dactyl.commands[command])
|
||
dactyl.withSavedValues(["forceTarget"], () => {
|
||
if (event.ctrlKey || event.shiftKey || event.button == 1)
|
||
dactyl.forceTarget = dactyl.NEW_TAB;
|
||
dactyl.commands[command](event);
|
||
});
|
||
}
|
||
}
|
||
},
|
||
|
||
"dactyl.execute": function onExecute(event) {
|
||
let cmd = event.originalTarget.getAttribute("dactyl-execute");
|
||
commands.execute(cmd, null, false, null,
|
||
{ file: /*L*/"[Command Line]", line: 1 });
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Opens one or more URLs. Returns true when load was initiated, or
|
||
* false on error.
|
||
*
|
||
* @param {string|Array} urls A representation of the URLs to open.
|
||
* A string will be passed to {@link Dactyl#parseURLs}. An array may
|
||
* contain elements of the following forms:
|
||
*
|
||
* • {string} A URL to open.
|
||
* • {[string, {string|Array}]} Pair of a URL and POST data.
|
||
* • {object} Object compatible with those returned
|
||
* by {@link DOM#formData}.
|
||
*
|
||
* @param {object} params A set of parameters specifying how to open the
|
||
* URLs. The following properties are recognized:
|
||
*
|
||
* • background If true, new tabs are opened in the background.
|
||
*
|
||
* • from The designation of the opener, as appears in
|
||
* 'activate' and 'newtab' options. If present,
|
||
* the newtab option provides the default 'where'
|
||
* parameter, and the value of the 'activate'
|
||
* parameter is inverted if 'background' is true.
|
||
*
|
||
* • where One of CURRENT_TAB, NEW_TAB, or NEW_WINDOW
|
||
*
|
||
* As a deprecated special case, the where parameter may be provided
|
||
* by itself, in which case it is transformed into { where: params }.
|
||
*
|
||
* @param {boolean} force Don't prompt whether to open more than 20
|
||
* tabs.
|
||
* @returns {boolean}
|
||
*/
|
||
open: function open(urls, params={}, force=false) {
|
||
if (typeof urls == "string")
|
||
urls = dactyl.parseURLs(urls);
|
||
|
||
if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
|
||
return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ")
|
||
.then(function (resp) {
|
||
if (resp && resp.match(/^y(es)?$/i))
|
||
dactyl.open(urls, params, true);
|
||
});
|
||
|
||
if (isString(params))
|
||
params = { where: params };
|
||
|
||
let flags = 0;
|
||
for (let [opt, flag] of iter({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" }))
|
||
flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
|
||
|
||
let where = params.where || dactyl.CURRENT_TAB;
|
||
let background = dactyl.forceBackground != null ? dactyl.forceBackground :
|
||
("background" in params) ? params.background
|
||
: params.where == dactyl.NEW_BACKGROUND_TAB;
|
||
|
||
if (params.from && dactyl.has("tabs")) {
|
||
if (!params.where && options.get("newtab").has(params.from))
|
||
where = dactyl.NEW_TAB;
|
||
background ^= !options.get("activate").has(params.from);
|
||
}
|
||
|
||
if (urls.length == 0)
|
||
return;
|
||
|
||
let browser = config.tabbrowser;
|
||
function open(loc, where) {
|
||
try {
|
||
if (isArray(loc))
|
||
loc = { url: loc[0], postData: loc[1] };
|
||
else if (isString(loc))
|
||
loc = { url: loc };
|
||
else
|
||
loc = Object.create(loc);
|
||
|
||
if (isString(loc.postData))
|
||
loc.postData = ["application/x-www-form-urlencoded", loc.postData];
|
||
|
||
if (isArray(loc.postData)) {
|
||
let stream = services.MIMEStream(services.StringStream(loc.postData[1]));
|
||
stream.addHeader("Content-Type", loc.postData[0]);
|
||
stream.addContentLength = true;
|
||
loc.postData = stream;
|
||
}
|
||
|
||
// decide where to load the first url
|
||
switch (where) {
|
||
case dactyl.NEW_TAB:
|
||
if (!dactyl.has("tabs"))
|
||
return open(loc, dactyl.NEW_WINDOW);
|
||
|
||
return prefs.withContext(function () {
|
||
prefs.set("browser.tabs.loadInBackground", true);
|
||
return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
|
||
});
|
||
|
||
case dactyl.NEW_WINDOW:
|
||
let options = ["chrome", "all", "dialog=no"];
|
||
if (dactyl.forcePrivate)
|
||
options.push("private");
|
||
|
||
let win = window.openDialog(document.documentURI, "_blank", options.join(","));
|
||
util.waitFor(() => win.document.readyState === "complete");
|
||
browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
|
||
// FALLTHROUGH
|
||
case dactyl.CURRENT_TAB:
|
||
browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
|
||
return browser.contentWindow;
|
||
}
|
||
}
|
||
catch (e) {}
|
||
// Unfortunately, failed page loads throw exceptions and
|
||
// cause a lot of unwanted noise. This solution means that
|
||
// any genuine errors go unreported.
|
||
}
|
||
|
||
if (dactyl.forceTarget)
|
||
where = dactyl.forceTarget;
|
||
else if (!where)
|
||
where = dactyl.CURRENT_TAB;
|
||
|
||
return urls.map(function (url) {
|
||
let res = open(url, where);
|
||
where = dactyl.NEW_TAB;
|
||
background = true;
|
||
return res;
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Returns an array of URLs parsed from *str*.
|
||
*
|
||
* Given a string like 'google bla, www.osnews.com' return an array
|
||
* ['www.google.com/search?q=bla', 'www.osnews.com']
|
||
*
|
||
* @param {string} str
|
||
* @returns {[string]}
|
||
*/
|
||
parseURLs: function parseURLs(str) {
|
||
let urls;
|
||
|
||
if (options["urlseparator"])
|
||
urls = util.splitLiteral(str, util.regexp("\\s*" + options["urlseparator"] + "\\s*"));
|
||
else
|
||
urls = [str];
|
||
|
||
return urls.map(function (url) {
|
||
url = url.trim();
|
||
|
||
if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
|
||
try {
|
||
// Try to find a matching file.
|
||
let file = io.File(url);
|
||
if (file.exists() && file.isReadable())
|
||
return file.URI.spec;
|
||
}
|
||
catch (e) {}
|
||
}
|
||
|
||
// If it starts with a valid protocol, pass it through.
|
||
let proto = /^([-\w]+):/.exec(url);
|
||
if (proto && services.PROTOCOL + proto[1] in Cc)
|
||
return url;
|
||
|
||
// Check for a matching search keyword.
|
||
let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
|
||
if (searchURL)
|
||
return searchURL;
|
||
|
||
// If it looks like URL-ish (foo.com/bar), let Gecko figure it out.
|
||
if (this.urlish.test(url) || !this.has("bookmarks"))
|
||
return util.createURI(url).spec;
|
||
|
||
// Pass it off to the default search engine or, failing
|
||
// that, let Gecko deal with it as is.
|
||
return bookmarks.getSearchURL(url, true) || util.createURI(url).spec;
|
||
}, this);
|
||
},
|
||
stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
|
||
urlish: Class.Memoize(() => util.regexp(literal(function () /*
|
||
^ (
|
||
<domain>+ (:\d+)? (/ .*) |
|
||
<domain>+ (:\d+) |
|
||
<domain>+ \. [a-z0-9]+ |
|
||
localhost
|
||
) $
|
||
*/$), "ix", {
|
||
domain: util.regexp(String.replace(literal(function () /*
|
||
[^
|
||
U0000-U002c // U002d-U002e --.
|
||
U002f // /
|
||
// U0030-U0039 0-9
|
||
U003a-U0040 // U0041-U005a a-z
|
||
U005b-U0060 // U0061-U007a A-Z
|
||
U007b-U007f
|
||
]
|
||
*/$), /U/g, "\\u"), "x")
|
||
})),
|
||
|
||
pluginFiles: {},
|
||
|
||
get plugins() { return plugins; },
|
||
|
||
setNodeVisible: function setNodeVisible(node, visible) {
|
||
if (window.setToolbarVisibility && node.localName == "toolbar")
|
||
window.setToolbarVisibility(node, visible);
|
||
else
|
||
node.collapsed = !visible;
|
||
},
|
||
|
||
confirmQuit: function confirmQuit()
|
||
prefs.withContext(function () {
|
||
prefs.set("browser.warnOnQuit", false);
|
||
return window.canQuitApplication();
|
||
}),
|
||
|
||
/**
|
||
* Quit the host application, no matter how many tabs/windows are open.
|
||
*
|
||
* @param {boolean} saveSession If true the current session will be
|
||
* saved and restored when the host application is restarted.
|
||
* @param {boolean} force Forcibly quit irrespective of whether all
|
||
* windows could be closed individually.
|
||
*/
|
||
quit: function quit(saveSession, force) {
|
||
if (!force && !this.confirmQuit())
|
||
return;
|
||
|
||
let pref = "browser.startup.page";
|
||
prefs.save(pref);
|
||
if (saveSession)
|
||
prefs.safeSet(pref, 3);
|
||
if (!saveSession && prefs.get(pref) >= 2)
|
||
prefs.safeSet(pref, 1);
|
||
|
||
services.appStartup.quit(Ci.nsIAppStartup[force ? "eForceQuit" : "eAttemptQuit"]);
|
||
},
|
||
|
||
/**
|
||
* Restart the host application.
|
||
*/
|
||
restart: function restart(args) {
|
||
if (!this.confirmQuit())
|
||
return;
|
||
|
||
config.prefs.set("commandline-args", args);
|
||
|
||
services.appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
|
||
},
|
||
|
||
get assert() { return util.assert; },
|
||
|
||
/**
|
||
* Traps errors in the called function, possibly reporting them.
|
||
*
|
||
* @param {function} func The function to call
|
||
* @param {object} self The 'this' object for the function.
|
||
*/
|
||
trapErrors: function trapErrors(func, self, ...args) {
|
||
try {
|
||
if (isString(func))
|
||
func = self[func];
|
||
return func.apply(self || this, args);
|
||
}
|
||
catch (e) {
|
||
try {
|
||
dactyl.reportError(e, true);
|
||
}
|
||
catch (e) {
|
||
util.reportError(e);
|
||
}
|
||
return e;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Reports an error to both the console and the host application's
|
||
* Error Console.
|
||
*
|
||
* @param {Object} error The error object.
|
||
*/
|
||
reportError: function reportError(error, echo) {
|
||
if (error instanceof FailedAssertion && error.noTrace || error.message === "Interrupted") {
|
||
let context = contexts.context;
|
||
let prefix = context ? context.file + ":" + context.line + ": " : "";
|
||
if (error.message && !error.message.startsWith(prefix) &&
|
||
prefix != "[Command Line]:1: ")
|
||
error.message = prefix + error.message;
|
||
|
||
if (error.message)
|
||
dactyl.echoerr(template.linkifyHelp(error.message));
|
||
else
|
||
dactyl.beep();
|
||
|
||
if (!error.noTrace)
|
||
util.reportError(error);
|
||
return;
|
||
}
|
||
|
||
if (error.result == Cr.NS_BINDING_ABORTED)
|
||
return;
|
||
|
||
if (echo)
|
||
dactyl.echoerr(error, commandline.FORCE_SINGLELINE);
|
||
else
|
||
util.reportError(error);
|
||
},
|
||
|
||
/**
|
||
* Parses a Dactyl command-line string i.e. the value of the
|
||
* -dactyl command-line option.
|
||
*
|
||
* @param {string} cmdline The string to parse for command-line
|
||
* options.
|
||
* @returns {Object}
|
||
* @see Commands#parseArgs
|
||
*/
|
||
parseCommandLine: function parseCommandLine(cmdline) {
|
||
try {
|
||
return commands.get("rehash").parseArgs(cmdline);
|
||
}
|
||
catch (e) {
|
||
dactyl.reportError(e, true);
|
||
return [];
|
||
}
|
||
},
|
||
wrapCallback: function wrapCallback(callback, self=this) {
|
||
let save = ["forceOpen"];
|
||
let saved = save.map(p => dactyl[p]);
|
||
return function wrappedCallback() {
|
||
let args = arguments;
|
||
return dactyl.withSavedValues(save, () => {
|
||
saved.forEach((p, i) => { dactyl[save[i]] = p; });
|
||
try {
|
||
return callback.apply(self, args);
|
||
}
|
||
catch (e) {
|
||
dactyl.reportError(e, true);
|
||
}
|
||
});
|
||
};
|
||
},
|
||
|
||
/**
|
||
* @property {[Window]} Returns an array of all the host application's
|
||
* open windows.
|
||
*/
|
||
get windows() { return [w for (w of overlay.windows)]; }
|
||
|
||
}, {
|
||
isToolbarHidden: function isToolbarHidden(toolbar) {
|
||
return toolbar.getAttribute("autohide") == "true" ||
|
||
toolbar.getAttribute("collapsed") == "true";
|
||
},
|
||
getToolbarName: function getToolbarName(toolbar) {
|
||
return toolbar.getAttribute("aria-label") ||
|
||
toolbar.getAttribute("toolbarname");
|
||
}
|
||
}, {
|
||
cache: function initCache() {
|
||
cache.register("help/plugins.xml", () => {
|
||
// Process plugin help entries.
|
||
|
||
let body = [];
|
||
for (let context of values(plugins.contexts))
|
||
try {
|
||
let info = contexts.getDocs(context);
|
||
if (DOM.isJSONXML(info)) {
|
||
let langs = info.slice(2)
|
||
.filter(e => isArray(e) && isObject(e[1]) && e[1].lang);
|
||
if (langs) {
|
||
let lang = config.bestLocale(langs.map(l => l[1].lang));
|
||
|
||
info = info.slice(0, 2).concat(
|
||
info.slice(2).filter(e => !isArray(e)
|
||
|| !isObject(e[1])
|
||
|| e[1].lang == lang));
|
||
|
||
info.slice(2)
|
||
.filter(e => isArray(e) && e[0] == "info" && isObject(e[1]))
|
||
.forEach(elem => {
|
||
for (let attr of ["name", "summary", "href"])
|
||
if (attr in elem[1])
|
||
info[attr] = elem[1][attr];
|
||
});
|
||
}
|
||
body.push(["h2", { xmlns: "dactyl", tag: info[1].name + '-plugin' },
|
||
String(info[1].summary)]);
|
||
body.push(info);
|
||
}
|
||
}
|
||
catch (e) {
|
||
util.reportError(e);
|
||
}
|
||
|
||
return '<?xml version="1.0"?>\n' +
|
||
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
|
||
DOM.toXML(
|
||
["document", { xmlns: "dactyl", name: "plugins",
|
||
title: config.appName + ", Plugins" },
|
||
["h1", { tag: "using-plugins" }, _("help.title.Using Plugins")],
|
||
["toc", { start: "2" }],
|
||
|
||
body]);
|
||
}, true);
|
||
|
||
cache.register("help/index.xml", () => {
|
||
return '<?xml version="1.0"?>\n' +
|
||
DOM.toXML(["overlay", { xmlns: "dactyl" },
|
||
template.map(iter(dactyl.indices),
|
||
([name, iter]) =>
|
||
["dl", { insertafter: name + "-index" },
|
||
template.map(iter(), identity)],
|
||
"\n\n")]);
|
||
}, true);
|
||
|
||
cache.register("help/gui.xml", () => {
|
||
return '<?xml version="1.0"?>\n' +
|
||
DOM.toXML(["overlay", { xmlns: "dactyl" },
|
||
["dl", { insertafter: "dialog-list" },
|
||
template.map(iter(config.dialogs),
|
||
([name, val]) =>
|
||
(!val[2] || val[2]())
|
||
? [["dt", {}, name],
|
||
["dd", {}, val[0]]]
|
||
: undefined,
|
||
"\n")]]);
|
||
}, true);
|
||
|
||
cache.register("help/privacy.xml", () => {
|
||
return '<?xml version="1.0"?>\n' +
|
||
DOM.toXML(["overlay", { xmlns: "dactyl" },
|
||
["dl", { insertafter: "sanitize-items" },
|
||
template.map(options.get("sanitizeitems").values
|
||
.sort((a, b) => String.localeCompare(a.name,
|
||
b.name)),
|
||
({ name, description }) =>
|
||
[["dt", {}, name],
|
||
["dd", {}, template.linkifyHelp(description, true)]],
|
||
"\n")]]);
|
||
}, true);
|
||
},
|
||
events: function initEvents() {
|
||
events.listen(window, dactyl, "events", true);
|
||
},
|
||
// Only general options are added here, which are valid for all Dactyl extensions
|
||
options: function initOptions() {
|
||
options.add(["errorbells", "eb"],
|
||
"Ring the bell when an error message is displayed",
|
||
"boolean", false);
|
||
|
||
options.add(["exrc", "ex"],
|
||
"Enable automatic sourcing of an RC file in the current directory at startup",
|
||
"boolean", false);
|
||
|
||
options.add(["fullscreen", "fs"],
|
||
"Show the current window fullscreen",
|
||
"boolean", false, {
|
||
setter: function (value) { return window.fullScreen = value; },
|
||
getter: function () { return window.fullScreen; }
|
||
});
|
||
|
||
const groups = [
|
||
{
|
||
opts: {
|
||
c: ["Always show the command line, even when empty"],
|
||
C: ["Always show the command line outside of the status line"],
|
||
M: ["Always show messages outside of the status line"]
|
||
},
|
||
setter: function (opts) {
|
||
if (loaded.has("commandline") || ~opts.indexOf("c"))
|
||
commandline.widgets.updateVisibility();
|
||
}
|
||
},
|
||
{
|
||
opts: update({
|
||
s: ["Status bar", [statusline.statusBar.id]]
|
||
}, config.guioptions),
|
||
|
||
setter: function (opts) {
|
||
for (let [opt, [, ids]] of iter(this.opts)) {
|
||
ids.map(id => document.getElementById(id))
|
||
.forEach(function (elem) {
|
||
if (elem)
|
||
dactyl.setNodeVisible(elem, opts.indexOf(opt) >= 0);
|
||
});
|
||
}
|
||
}
|
||
},
|
||
{
|
||
opts: {
|
||
r: ["Right Scrollbar", "vertical"],
|
||
l: ["Left Scrollbar", "vertical"],
|
||
b: ["Bottom Scrollbar", "horizontal"]
|
||
},
|
||
setter: function (opts) {
|
||
let dir = ["horizontal", "vertical"].filter(
|
||
dir => !Array.some(opts,
|
||
o => this.opts[o] && this.opts[o][1] == dir));
|
||
let class_ = dir.map(dir => "html|html > xul|scrollbar[orient=" + dir + "]");
|
||
|
||
styles.system.add("scrollbar", "*",
|
||
class_.length ? class_.join(", ") + " { visibility: collapse !important; }" : "",
|
||
true);
|
||
|
||
prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
|
||
_("option.guioptions.safeSet"));
|
||
},
|
||
validator: function (opts) {
|
||
return Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
|
||
UTF8("Only one of ‘l’ or ‘r’ allowed"));
|
||
}
|
||
},
|
||
{
|
||
feature: "tabs",
|
||
opts: {
|
||
n: ["Tab number", highlight.selector("TabNumber")],
|
||
N: ["Tab number over icon", highlight.selector("TabIconNumber")]
|
||
},
|
||
setter: function (opts) {
|
||
let classes = [v[1] for ([k, v] of iter(this.opts)) if (opts.indexOf(k) < 0)];
|
||
|
||
styles.system.add("taboptions", "chrome://*",
|
||
classes.length ? classes.join(",") + "{ display: none; }" : "");
|
||
|
||
if (config.tabbrowser.tabContainer._positionPinnedTabs)
|
||
config.tabbrowser.tabContainer._positionPinnedTabs();
|
||
}
|
||
}
|
||
].filter(group => !group.feature || dactyl.has(group.feature));
|
||
|
||
options.add(["guioptions", "go"],
|
||
"Show or hide certain GUI elements like the menu or toolbar",
|
||
"charlist", "", {
|
||
|
||
// FIXME: cleanup
|
||
cleanupValue: config.cleanups.guioptions ||
|
||
"rb" + [k for ([k, v] of iter(groups[1].opts))
|
||
if (!Dactyl.isToolbarHidden(document.getElementById(v[1][0])))].join(""),
|
||
|
||
values: Ary(groups).map(g => [[k, v[0]] for ([k, v] of iter(g.opts))])
|
||
.flatten(),
|
||
|
||
setter: function (value) {
|
||
for (let group of groups)
|
||
group.setter(value);
|
||
events.checkFocus();
|
||
return value;
|
||
},
|
||
validator: function (val) {
|
||
return Option.validateCompleter.call(this, val) &&
|
||
groups.every(g => !g.validator || g.validator(val));
|
||
}
|
||
});
|
||
|
||
options.add(["loadplugins", "lpl"],
|
||
"A regexp list that defines which plugins are loaded at startup and via :loadplugins",
|
||
"regexplist", "'\\.(js|" + config.fileExtension + ")$'");
|
||
|
||
options.add(["titlestring"],
|
||
"The string shown at the end of the window title",
|
||
"string", config.host,
|
||
{
|
||
setter: function (value) {
|
||
let win = document.documentElement;
|
||
function updateTitle(old, current) {
|
||
if (config.browser.updateTitlebar)
|
||
config.browser.updateTitlebar();
|
||
else
|
||
document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
|
||
}
|
||
|
||
if (win.hasAttribute("titlemodifier_privatebrowsing")) {
|
||
let oldValue = win.getAttribute("titlemodifier_normal");
|
||
let suffix = win.getAttribute("titlemodifier_privatebrowsing").substr(oldValue.length);
|
||
|
||
win.setAttribute("titlemodifier_normal", value);
|
||
win.setAttribute("titlemodifier_privatebrowsing", value + suffix);
|
||
|
||
if (storage.privateMode) {
|
||
updateTitle(oldValue + suffix, value + suffix);
|
||
win.setAttribute("titlemodifier", value + suffix);
|
||
return value;
|
||
}
|
||
}
|
||
|
||
updateTitle(win.getAttribute("titlemodifier"), value);
|
||
win.setAttribute("titlemodifier", value);
|
||
|
||
return value;
|
||
}
|
||
});
|
||
|
||
options.add(["urlseparator", "urlsep", "us"],
|
||
"The regular expression used to separate multiple URLs in :open and friends",
|
||
"string", " \\| ",
|
||
{ validator: function (value) { return RegExp(value); } });
|
||
|
||
options.add(["verbose", "vbs"],
|
||
"Define which info messages are displayed",
|
||
"number", 1,
|
||
{
|
||
validator: function (value) {
|
||
return Option.validIf(value >= 0 && value <= 15,
|
||
"Value must be between 0 and 15");
|
||
}
|
||
});
|
||
|
||
options.add(["visualbell", "vb"],
|
||
"Use visual bell instead of beeping on errors",
|
||
"boolean", false,
|
||
{
|
||
setter: function (value) {
|
||
prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
|
||
_("option.safeSet", "visualbell"));
|
||
return value;
|
||
}
|
||
});
|
||
},
|
||
|
||
mappings: function initMappings() {
|
||
if (dactyl.has("session"))
|
||
mappings.add([modes.NORMAL], ["ZQ"],
|
||
"Quit and don't save the session",
|
||
function () { dactyl.quit(false); });
|
||
|
||
mappings.add([modes.NORMAL], ["ZZ"],
|
||
"Quit and save the session",
|
||
function () { dactyl.quit(true); });
|
||
},
|
||
|
||
commands: function initCommands() {
|
||
commands.add(["dia[log]"],
|
||
"Open a " + config.appName + " dialog",
|
||
function (args) {
|
||
let dialog = args[0];
|
||
|
||
dactyl.assert(dialog in config.dialogs,
|
||
_("error.invalidArgument", dialog));
|
||
dactyl.assert(!config.dialogs[dialog][2] || config.dialogs[dialog][2](),
|
||
_("dialog.notAvailable", dialog));
|
||
try {
|
||
config.dialogs[dialog][1]();
|
||
}
|
||
catch (e) {
|
||
dactyl.echoerr(_("error.cantOpen", JSON.stringify(dialog), e.message || e));
|
||
}
|
||
}, {
|
||
argCount: "1",
|
||
completer: function (context) {
|
||
context.ignoreCase = true;
|
||
completion.dialog(context);
|
||
}
|
||
});
|
||
|
||
commands.add(["em[enu]"],
|
||
"Execute the specified menu item from the command line",
|
||
function (args) {
|
||
let arg = args[0] || "";
|
||
let items = dactyl.getMenuItems(arg);
|
||
|
||
dactyl.assert(items.some(i => i.dactylPath == arg),
|
||
_("emenu.notFound", arg));
|
||
|
||
for (let item of items) {
|
||
if (item.dactylPath == arg) {
|
||
dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
|
||
item.doCommand();
|
||
}
|
||
}
|
||
}, {
|
||
argCount: "1",
|
||
completer: function (context) {
|
||
completion.menuItem(context);
|
||
},
|
||
literal: 0
|
||
});
|
||
|
||
commands.add(["exe[cute]"],
|
||
"Execute the argument as an Ex command",
|
||
function (args) {
|
||
try {
|
||
let cmd = dactyl.userEval(args[0] || "");
|
||
dactyl.execute(cmd || "", null, true);
|
||
}
|
||
catch (e) {
|
||
dactyl.echoerr(e);
|
||
}
|
||
}, {
|
||
completer: function (context) {
|
||
completion.javascript(context);
|
||
},
|
||
literal: 0
|
||
});
|
||
|
||
commands.add(["loadplugins", "lpl"],
|
||
"Load all or matching plugins",
|
||
function (args) {
|
||
dactyl.loadPlugins(args.length ? args : null, args.bang);
|
||
}, {
|
||
argCount: "*",
|
||
bang: true,
|
||
keepQuotes: true,
|
||
serialGroup: 10,
|
||
serialize: function () {
|
||
return [{
|
||
command: this.name,
|
||
literalArg: options["loadplugins"].join(" ")
|
||
}];
|
||
}
|
||
});
|
||
|
||
commands.add(["norm[al]"],
|
||
"Execute Normal mode commands",
|
||
function (args) { events.feedkeys(args[0], args.bang, false, modes.NORMAL); },
|
||
{
|
||
argCount: "1",
|
||
bang: true,
|
||
literal: 0
|
||
});
|
||
|
||
commands.add(["pr[ivate]", "pr0n", "porn"],
|
||
"Enable privacy features of a command, when applicable, and do not save the invocation in command history",
|
||
function (args) {
|
||
dactyl.withSavedValues(["forcePrivate"], function () {
|
||
this.forcePrivate = true;
|
||
dactyl.execute(args[0], null, true);
|
||
});
|
||
}, {
|
||
argCount: "1",
|
||
completer: function (context) { completion.ex(context); },
|
||
literal: 0,
|
||
privateData: "never-save",
|
||
subCommand: 0
|
||
});
|
||
|
||
commands.add(["exit", "x"],
|
||
"Quit " + config.appName,
|
||
function (args) {
|
||
dactyl.quit(false, args.bang);
|
||
}, {
|
||
argCount: "0",
|
||
bang: true
|
||
});
|
||
|
||
commands.add(["q[uit]"],
|
||
dactyl.has("tabs") ? "Quit current tab" : "Quit application",
|
||
function (args) {
|
||
if (dactyl.has("tabs") && tabs.remove(tabs.getTab(), 1, false))
|
||
return;
|
||
else if (dactyl.windows.length > 1)
|
||
window.close();
|
||
else
|
||
dactyl.quit(false, args.bang);
|
||
}, {
|
||
argCount: "0",
|
||
bang: true
|
||
});
|
||
|
||
let startupOptions = [
|
||
{
|
||
names: ["+u"],
|
||
description: "The initialization file to execute at startup",
|
||
type: CommandOption.STRING
|
||
},
|
||
{
|
||
names: ["++noplugin"],
|
||
description: "Do not automatically load plugins"
|
||
},
|
||
{
|
||
names: ["++cmd"],
|
||
description: "Ex commands to execute prior to initialization",
|
||
type: CommandOption.STRING,
|
||
multiple: true
|
||
},
|
||
{
|
||
names: ["+c"],
|
||
description: "Ex commands to execute after initialization",
|
||
type: CommandOption.STRING,
|
||
multiple: true
|
||
},
|
||
{
|
||
names: ["+purgecaches"],
|
||
description: "Purge " + config.appName + " caches at startup",
|
||
type: CommandOption.NOARG
|
||
}
|
||
];
|
||
|
||
commands.add(["reh[ash]"],
|
||
"Reload the " + config.appName + " add-on",
|
||
function (args) {
|
||
if (args.trailing)
|
||
storage.storeForSession("rehashCmd", args.trailing); // Hack.
|
||
args.break = true;
|
||
|
||
if (args["+purgecaches"])
|
||
cache.flush();
|
||
|
||
util.delay(() => { util.rehash(args); });
|
||
},
|
||
{
|
||
argCount: "0", // FIXME
|
||
options: startupOptions
|
||
});
|
||
|
||
commands.add(["res[tart]"],
|
||
"Force " + config.host + " to restart",
|
||
function (args) {
|
||
if (args["+purgecaches"])
|
||
cache.flush();
|
||
|
||
dactyl.restart(args.string);
|
||
},
|
||
{
|
||
argCount: "0",
|
||
options: startupOptions
|
||
});
|
||
|
||
if (config.toolbars.length) {
|
||
let toolbarCommand = function (names, desc, action, filter) {
|
||
commands.add(names, desc,
|
||
function (args) {
|
||
let name = args[0].trim();
|
||
let toolbar = config.toolbars.find(t => Dactyl.getToolbarName(t) === name);
|
||
dactyl.assert(toolbar, _("error.invalidArgument"));
|
||
action(toolbar);
|
||
events.checkFocus();
|
||
}, {
|
||
argCount: "1",
|
||
completer: function (context) {
|
||
completion.toolbar(context);
|
||
if (filter)
|
||
context.filters.push(filter);
|
||
},
|
||
literal: 0
|
||
});
|
||
};
|
||
|
||
toolbarCommand(["toolbars[how]", "tbs[how]"],
|
||
"Show the named toolbar",
|
||
toolbar => dactyl.setNodeVisible(toolbar, true),
|
||
({ item }) => Dactyl.isToolbarHidden(item));
|
||
toolbarCommand(["toolbarh[ide]", "tbh[ide]"],
|
||
"Hide the named toolbar",
|
||
toolbar => dactyl.setNodeVisible(toolbar, false),
|
||
({ item }) => !Dactyl.isToolbarHidden(item));
|
||
toolbarCommand(["toolbart[oggle]", "tbt[oggle]"],
|
||
"Toggle the named toolbar",
|
||
toolbar => dactyl.setNodeVisible(toolbar, Dactyl.isToolbarHidden(toolbar)));
|
||
}
|
||
|
||
commands.add(["time"],
|
||
"Profile a piece of code or run a command multiple times",
|
||
function (args) {
|
||
let count = args.count;
|
||
let special = args.bang;
|
||
args = args[0] || "";
|
||
|
||
if (args[0] == ":")
|
||
var func = () => commands.execute(args, null, false);
|
||
else
|
||
func = dactyl.userFunc(args);
|
||
|
||
try {
|
||
if (count > 1) {
|
||
let each, eachUnits, totalUnits;
|
||
let total = 0;
|
||
|
||
for (let i of util.interruptibleRange(0, count, 500)) {
|
||
let now = Date.now();
|
||
func();
|
||
total += Date.now() - now;
|
||
}
|
||
|
||
if (special)
|
||
return;
|
||
|
||
if (total / count >= 100) {
|
||
each = total / 1000.0 / count;
|
||
eachUnits = "sec";
|
||
}
|
||
else {
|
||
each = total / count;
|
||
eachUnits = "msec";
|
||
}
|
||
|
||
if (total >= 100) {
|
||
total = total / 1000.0;
|
||
totalUnits = "sec";
|
||
}
|
||
else
|
||
totalUnits = "msec";
|
||
|
||
commandline.commandOutput(
|
||
["table", {}
|
||
["tr", { highlight: "Title", align: "left" },
|
||
["th", { colspan: "3" }, _("title.Code execution summary")]],
|
||
["tr", {},
|
||
["td", {}, _("title.Executed"), ":"],
|
||
["td", { align: "right" },
|
||
["span", { class: "times-executed" }, count]],
|
||
["td", {}, /*L*/"times"]],
|
||
["tr", {},
|
||
["td", {}, _("title.Average time"), ":"],
|
||
["td", { align: "right" },
|
||
["span", { class: "time-average" }, each.toFixed(2)]],
|
||
["td", {}, eachUnits]],
|
||
["tr", {},
|
||
["td", {}, _("title.Total time"), ":"],
|
||
["td", { align: "right" },
|
||
["span", { class: "time-total" }, total.toFixed(2)]],
|
||
["td", {}, totalUnits]]]);
|
||
}
|
||
else {
|
||
let beforeTime = Date.now();
|
||
func();
|
||
|
||
if (special)
|
||
return;
|
||
|
||
let afterTime = Date.now();
|
||
|
||
if (afterTime - beforeTime >= 100)
|
||
dactyl.echo(_("time.total", ((afterTime - beforeTime) / 1000.0).toFixed(2) + " sec"));
|
||
else
|
||
dactyl.echo(_("time.total", (afterTime - beforeTime) + " msec"));
|
||
}
|
||
}
|
||
catch (e) {
|
||
dactyl.echoerr(e);
|
||
}
|
||
}, {
|
||
argCount: "1",
|
||
bang: true,
|
||
completer: function (context) {
|
||
if (/^:/.test(context.filter))
|
||
completion.ex(context);
|
||
else
|
||
completion.javascript(context);
|
||
},
|
||
count: true,
|
||
hereDoc: true,
|
||
literal: 0,
|
||
subCommand: 0
|
||
});
|
||
|
||
commands.add(["verb[ose]"],
|
||
"Execute a command with 'verbose' set",
|
||
function (args) {
|
||
let vbs = options.get("verbose");
|
||
let value = vbs.value;
|
||
let setFrom = vbs.setFrom;
|
||
|
||
try {
|
||
vbs.set(args.count || 1);
|
||
vbs.setFrom = null;
|
||
dactyl.execute(args[0] || "", null, true);
|
||
}
|
||
finally {
|
||
vbs.set(value);
|
||
vbs.setFrom = setFrom;
|
||
}
|
||
}, {
|
||
argCount: "1",
|
||
completer: function (context) { completion.ex(context); },
|
||
count: true,
|
||
literal: 0,
|
||
subCommand: 0
|
||
});
|
||
|
||
commands.add(["ve[rsion]"],
|
||
"Show version information",
|
||
function (args) {
|
||
if (args.bang)
|
||
dactyl.open("about:");
|
||
else {
|
||
let date = config.buildDate;
|
||
date = date ? " (" + date + ")" : "";
|
||
|
||
commandline.commandOutput([
|
||
["div", {}, [config.appName, " ", config.version, date, " running on: "].join("")],
|
||
["div", {}, [window.navigator.userAgent].join("")]
|
||
]);
|
||
}
|
||
}, {
|
||
argCount: "0",
|
||
bang: true
|
||
});
|
||
|
||
},
|
||
|
||
completion: function initCompletion() {
|
||
completion.dialog = function dialog(context) {
|
||
context.title = ["Dialog"];
|
||
context.filters.push(({ item }) => !item[2] || item[2]());
|
||
context.completions = [[k, v[0], v[2]] for ([k, v] of iter(config.dialogs))];
|
||
};
|
||
|
||
completion.menuItem = function menuItem(context) {
|
||
context.title = ["Menu Path", "Label"];
|
||
context.anchored = false;
|
||
context.keys = {
|
||
text: "dactylPath",
|
||
description: function (item) item.getAttribute("label"),
|
||
highlight: function (item) item.disabled ? "Disabled" : ""
|
||
};
|
||
context.generate = () => dactyl.menuItems;
|
||
};
|
||
|
||
completion.toolbar = function toolbar(context) {
|
||
context.title = ["Toolbar"];
|
||
context.keys = {
|
||
text: Dactyl.getToolbarName,
|
||
description: function () ""
|
||
};
|
||
context.completions = config.toolbars;
|
||
};
|
||
|
||
completion.window = function window(context) {
|
||
context.title = ["Window", "Title"];
|
||
context.keys = { text: win => dactyl.windows.indexOf(win) + 1,
|
||
description: win => win.document.title };
|
||
context.completions = dactyl.windows;
|
||
};
|
||
},
|
||
load: function initLoad() {
|
||
dactyl.triggerObserver("load");
|
||
|
||
dactyl.log(_("dactyl.modulesLoaded"), 3);
|
||
|
||
userContext.DOM = Class("DOM", DOM, { init: function DOM_(sel, ctxt) DOM(sel, ctxt || buffer.focusedFrame.document) });
|
||
userContext.$ = modules.userContext.DOM;
|
||
|
||
// Hack: disable disabling of Personas in private windows.
|
||
let root = document.documentElement;
|
||
|
||
if (PrivateBrowsingUtils && PrivateBrowsingUtils.isWindowPrivate(window)
|
||
&& root._lightweightTheme
|
||
&& root._lightweightTheme._lastScreenWidth == null) {
|
||
|
||
dactyl.withSavedValues.call(PrivateBrowsingUtils,
|
||
["isWindowPrivate"],
|
||
() => {
|
||
PrivateBrowsingUtils.isWindowPrivate = () => false;
|
||
Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", {})
|
||
.LightweightThemeConsumer.call(root._lightweightTheme, document);
|
||
});
|
||
}
|
||
|
||
if (config.has("default-theme") && "CustomizableUI" in window &&
|
||
config.tabbrowser.tabContainer.orient != "vertical")
|
||
overlay.overlayWindow(window, {
|
||
append: [
|
||
["window", { id: document.documentElement.id, "dactyl-australis": "true", xmlns: "xul" }]]
|
||
});
|
||
|
||
dactyl.timeout(function () {
|
||
try {
|
||
var args = config.prefs.get("commandline-args")
|
||
|| storage.session.commandlineArgs
|
||
|| services.commandLineHandler.optionValue;
|
||
|
||
config.prefs.reset("commandline-args");
|
||
|
||
if (isString(args))
|
||
args = dactyl.parseCommandLine(args);
|
||
|
||
if (args) {
|
||
dactyl.commandLineOptions.rcFile = args["+u"];
|
||
dactyl.commandLineOptions.noPlugins = "++noplugin" in args;
|
||
dactyl.commandLineOptions.postCommands = args["+c"];
|
||
dactyl.commandLineOptions.preCommands = args["++cmd"];
|
||
util.dump("Processing command-line option: " + args.string);
|
||
}
|
||
}
|
||
catch (e) {
|
||
dactyl.echoerr(_("dactyl.parsingCommandLine", e));
|
||
}
|
||
|
||
dactyl.log(_("dactyl.commandlineOpts", util.objectToString(dactyl.commandLineOptions)), 3);
|
||
|
||
if (config.prefs.get("first-run", true))
|
||
dactyl.timeout(function () {
|
||
config.prefs.set("first-run", false);
|
||
this.withSavedValues(["forceTarget"], function () {
|
||
this.forceTarget = dactyl.NEW_TAB;
|
||
help.help();
|
||
});
|
||
}, 1000);
|
||
|
||
// TODO: we should have some class where all this guioptions stuff fits well
|
||
// dactyl.hideGUI();
|
||
|
||
if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
|
||
jsmodules.__proto__ = window;
|
||
|
||
if (dactyl.commandLineOptions.preCommands)
|
||
dactyl.commandLineOptions.preCommands.forEach(function (cmd) {
|
||
dactyl.execute(cmd);
|
||
});
|
||
|
||
// finally, read the RC file and source plugins
|
||
let init = services.environment.get(config.idName + "_INIT");
|
||
let rcFile = io.getRCFile("~");
|
||
|
||
try {
|
||
if (dactyl.commandLineOptions.rcFile) {
|
||
let filename = dactyl.commandLineOptions.rcFile;
|
||
if (!/^(NONE|NORC)$/.test(filename))
|
||
io.source(io.File(filename).path, { group: contexts.user });
|
||
}
|
||
else {
|
||
if (init)
|
||
dactyl.execute(init);
|
||
else {
|
||
if (rcFile) {
|
||
io.source(rcFile.path, { group: contexts.user });
|
||
services.environment.set("MY_" + config.idName + "RC", rcFile.path);
|
||
}
|
||
else
|
||
dactyl.log(_("dactyl.noRCFile"), 3);
|
||
}
|
||
|
||
if (options["exrc"] && !dactyl.commandLineOptions.rcFile) {
|
||
let localRCFile = io.getRCFile(io.cwd);
|
||
if (localRCFile && !localRCFile.equals(rcFile))
|
||
io.source(localRCFile.path, { group: contexts.user });
|
||
}
|
||
}
|
||
|
||
if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
|
||
options["loadplugins"] = [];
|
||
|
||
if (options["loadplugins"].length)
|
||
dactyl.loadPlugins();
|
||
}
|
||
catch (e) {
|
||
dactyl.reportError(e, true);
|
||
}
|
||
|
||
// after sourcing the initialization files, this function will set
|
||
// all gui options to their default values, if they have not been
|
||
// set before by any RC file
|
||
for (let option of options.needInit)
|
||
option.initValue();
|
||
|
||
if (dactyl.commandLineOptions.postCommands)
|
||
dactyl.commandLineOptions.postCommands.forEach(function (cmd) {
|
||
dactyl.execute(cmd);
|
||
});
|
||
|
||
if (storage.session.rehashCmd)
|
||
dactyl.execute(storage.session.rehashCmd);
|
||
storage.session.rehashCmd = null;
|
||
|
||
dactyl.fullyInitialized = true;
|
||
dactyl.triggerObserver("enter", null);
|
||
autocommands.trigger("Enter", {});
|
||
}, 100);
|
||
|
||
statusline.update();
|
||
dactyl.log(_("dactyl.initialized", config.appName), 0);
|
||
dactyl.initialized = true;
|
||
}
|
||
});
|
||
|
||
// vim: set fdm=marker sw=4 sts=4 ts=8 et:
|