//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("io", {
exports: ["IO", "io"],
- require: ["services"],
- use: ["config", "messages", "storage", "styles", "template", "util"]
+ require: ["services"]
}, this);
+this.lazyRequire("config", ["config"]);
+this.lazyRequire("contexts", ["Contexts", "contexts"]);
+
// TODO: why are we passing around strings rather than file objects?
/**
* Provides a basic interface to common system I/O operations.
* @instance io
*/
var IO = Module("io", {
- init: function () {
+ init: function init() {
this._processDir = services.directory.get("CurWorkD", Ci.nsIFile);
this._cwd = this._processDir.path;
this._oldcwd = null;
- this.config = config;
+ lazyRequire("config", ["config"], this);
},
- Local: function (dactyl, modules, window) let ({ io, plugins } = modules) ({
+ Local: function Local(dactyl, modules, window) let ({ io, plugins } = modules) ({
init: function init() {
this.config = modules.config;
dactyl.echomsg(_("io.sourcing", filename.quote()), 2);
- let uri = services.io.newFileURI(file);
+ let uri = file.URI;
+
+ let sourceJSM = function sourceJSM() {
+ context = contexts.Module(uri);
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
+ }
- // handle pure JavaScript files specially
- if (/\.js$/.test(filename)) {
+ if (/\.js,$/.test(filename))
+ sourceJSM();
+ else if (/\.js$/.test(filename)) {
try {
var context = contexts.Script(file, params.group);
+ if (Set.has(this._scriptNames, file.path))
+ util.flushCache();
+
dactyl.loadScript(uri.spec, context);
- dactyl.helpInitialized = false;
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
catch (e) {
- if (e.fileName)
- try {
- e.fileName = util.fixURI(e.fileName);
- if (e.fileName == uri.spec)
- e.fileName = filename;
- e.echoerr = <>{e.fileName}:{e.lineNumber}: {e}</>;
- }
- catch (e) {}
- throw e;
+ if (e == Contexts) { // Hack;
+ context.unload();
+ sourceJSM();
+ }
+ else {
+ if (e.fileName && !(e instanceof FailedAssertion))
+ try {
+ e.fileName = util.fixURI(e.fileName);
+ if (e.fileName == uri.spec)
+ e.fileName = filename;
+ e.echoerr = <>{e.fileName}:{e.lineNumber}: {e}</>;
+ }
+ catch (e) {}
+ throw e;
+ }
}
}
else if (/\.css$/.test(filename))
group: context.GROUP,
line: 1
});
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
}
- if (this._scriptNames.indexOf(file.path) == -1)
- this._scriptNames.push(file.path);
+ Set.add(this._scriptNames, file.path);
dactyl.echomsg(_("io.sourcingEnd", filename.quote()), 2);
dactyl.log(_("dactyl.sourced", filename), 3);
return context;
}
catch (e) {
- dactyl.reportError(e);
+ util.reportError(e);
let message = _("io.sourcingError", e.echoerr || (file ? file.path : filename) + ": " + e);
if (!params.silent)
dactyl.echoerr(message);
* @property {function} File class.
* @final
*/
- File: Class.memoize(function () let (io = this)
+ File: Class.Memoize(function () let (io = this)
Class("File", File, {
init: function init(path, checkCWD)
init.supercall(this, path, (arguments.length < 2 || checkCWD) && io.cwd)
* @default $HOME.
* @returns {nsIFile} The RC file or null if none is found.
*/
- getRCFile: function (dir, always) {
+ getRCFile: function getRCFile(dir, always) {
dir = this.File(dir || "~");
let rcFile1 = dir.child("." + config.name + "rc");
let rcFile2 = dir.child("_" + config.name + "rc");
- if (util.OS.isWindows)
+ if (config.OS.isWindows)
[rcFile1, rcFile2] = [rcFile2, rcFile1];
if (rcFile1.exists() && rcFile1.isFile())
return null;
},
- // TODO: make secure
/**
* Creates a temporary file.
*
* @returns {File}
*/
- createTempFile: function () {
- let file = services.directory.get("TmpD", Ci.nsIFile);
- file.append(this.config.tempFile);
+ createTempFile: function createTempFile(name) {
+ if (name instanceof Ci.nsIFile)
+ var file = name.clone();
+ else {
+ file = services.directory.get("TmpD", Ci.nsIFile);
+ file.append(this.config.tempFile + (name ? "." + name : ""));
+ }
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(600));
- Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
- .getService(Ci.nsPIExternalAppLauncher).deleteTemporaryFileOnExit(file);
+ services.externalApp.deleteTemporaryFileOnExit(file);
return File(file);
},
*/
isJarURL: function isJarURL(url) {
try {
- let uri = util.newURI(util.fixURI(url));
+ let uri = util.newURI(url);
+ if (uri instanceof Ci.nsIJARURI)
+ return uri;
+
let channel = services.io.newChannelFromURI(uri);
- channel.cancel(Cr.NS_BINDING_ABORTED);
+ try { channel.cancel(Cr.NS_BINDING_ABORTED); } catch (e) {}
if (channel instanceof Ci.nsIJARChannel)
return channel.URI.QueryInterface(Ci.nsIJARURI);
}
}
},
- readHeredoc: function (end) {
+ readHeredoc: function readHeredoc(end) {
return "";
},
* name and searched for in turn.
*
* @param {string} bin The name of the executable to find.
+ * @returns {File|null}
*/
- pathSearch: function (bin) {
+ pathSearch: function pathSearch(bin) {
if (bin instanceof File || File.isAbsolutePath(bin))
return this.File(bin);
- let dirs = services.environment.get("PATH").split(util.OS.isWindows ? ";" : ":");
+ let dirs = services.environment.get("PATH").split(config.OS.isWindows ? ";" : ":");
// Windows tries the CWD first TODO: desirable?
- if (util.OS.isWindows)
+ if (config.OS.isWindows)
dirs = [io.cwd].concat(dirs);
for (let [, dir] in Iterator(dirs))
// TODO: couldn't we just palm this off to the start command?
// automatically try to add the executable path extensions on windows
- if (util.OS.isWindows) {
+ if (config.OS.isWindows) {
let extensions = services.environment.get("PATHEXT").split(";");
for (let [, extension] in Iterator(extensions)) {
file = dir.child(bin + extension);
* @param {File|string} program The program to run.
* @param {[string]} args An array of arguments to pass to *program*.
*/
- run: function (program, args, blocking) {
+ run: function run(program, args, blocking, self) {
args = args || [];
let file = this.pathSearch(program);
function () {
if (!process.isRunning) {
timer.cancel();
- util.trapErrors(blocking);
+ util.trapErrors(blocking, self, process.exitValue);
}
},
100, services.Timer.TYPE_REPEATING_SLACK);
*
* @param {string} command The command to run.
* @param {string} input Any input to be provided to the command on stdin.
- * @returns {object}
+ * @param {function(object)} callback A callback to be called when
+ * the command completes. @optional
+ * @returns {object|null}
*/
- system: function (command, input) {
+ system: function system(command, input, callback) {
util.dactyl.echomsg(_("io.callingShell", command), 4);
- function escape(str) '"' + str.replace(/[\\"$]/g, "\\$&") + '"';
+ let { shellEscape } = util.closure;
return this.withTempFiles(function (stdin, stdout, cmd) {
if (input instanceof File)
else if (input)
stdin.write(input);
+ function result(status, output) ({
+ __noSuchMethod__: function (meth, args) this.output[meth].apply(this.output, args),
+ valueOf: function () this.output,
+ output: output.replace(/^(.*)\n$/, "$1"),
+ returnValue: status,
+ toString: function () this.output
+ });
+
+ function async(status) {
+ let output = stdout.read();
+ [stdin, stdout, cmd].forEach(function (f) f.exists() && f.remove(false));
+ callback(result(status, output));
+ }
+
let shell = io.pathSearch(storage["options"].get("shell").value);
let shcf = storage["options"].get("shellcmdflag").value;
util.assert(shell, _("error.invalid", "'shell'"));
if (isArray(command))
- command = command.map(escape).join(" ");
+ command = command.map(shellEscape).join(" ");
// TODO: implement 'shellredir'
- if (util.OS.isWindows && !/sh/.test(shell.leafName)) {
+ if (config.OS.isWindows && !/sh/.test(shell.leafName)) {
command = "cd /D " + this.cwd.path + " && " + command + " > " + stdout.path + " 2>&1" + " < " + stdin.path;
- var res = this.run(shell, shcf.split(/\s+/).concat(command), true);
+ var res = this.run(shell, shcf.split(/\s+/).concat(command), callback ? async : true);
}
else {
- cmd.write("cd " + escape(this.cwd.path) + "\n" +
- ["exec", ">" + escape(stdout.path), "2>&1", "<" + escape(stdin.path),
- escape(shell.path), shcf, escape(command)].join(" "));
- res = this.run("/bin/sh", ["-e", cmd.path], true);
+ cmd.write("cd " + shellEscape(this.cwd.path) + "\n" +
+ ["exec", ">" + shellEscape(stdout.path), "2>&1", "<" + shellEscape(stdin.path),
+ shellEscape(shell.path), shcf, shellEscape(command)].join(" "));
+ res = this.run("/bin/sh", ["-e", cmd.path], callback ? async : true);
}
- return {
- __noSuchMethod__: function (meth, args) this.output[meth].apply(this.output, args),
- valueOf: function () this.output,
- output: stdout.read().replace(/^(.*)\n$/, "$1"),
- returnValue: res,
- toString: function () this.output
- };
- }) || "";
+ return callback ? true : result(res, stdout.read());
+ }, this, true);
},
/**
* @returns {boolean} false if temp files couldn't be created,
* otherwise, the return value of *func*.
*/
- withTempFiles: function (func, self, checked) {
- let args = array(util.range(0, func.length)).map(this.closure.createTempFile).array;
+ withTempFiles: function withTempFiles(func, self, checked, ext) {
+ let args = array(util.range(0, func.length)).map(bind("createTempFile", this, ext)).array;
try {
if (!args.every(util.identity))
return false;
}
finally {
if (!checked || res !== true)
- args.forEach(function (f) f && f.remove(false));
+ args.forEach(function (f) f.remove(false));
}
return res;
}
const rtpvar = config.idName + "_RUNTIME";
let rtp = services.environment.get(rtpvar);
if (!rtp) {
- rtp = "~/" + (util.OS.isWindows ? "" : ".") + config.name;
+ rtp = "~/" + (config.OS.isWindows ? "" : ".") + config.name;
services.environment.set(rtpvar, rtp);
}
return rtp;
*/
PATH_SEP: deprecated("File.PATH_SEP", { get: function PATH_SEP() File.PATH_SEP })
}, {
- commands: function (dactyl, modules, window) {
+ commands: function init_commands(dactyl, modules, window) {
const { commands, completion, io } = modules;
commands.add(["cd", "chd[ir]"],
commands.add(["mks[yntax]"],
"Generate a Vim syntax file",
function (args) {
- let runtime = util.OS.isWindows ? "~/vimfiles/" : "~/.vim/";
+ let runtime = config.OS.isWindows ? "~/vimfiles/" : "~/.vim/";
let file = io.File(runtime + "syntax/" + config.name + ".vim");
if (args.length)
file = io.File(args[0]);
commands.add(["scrip[tnames]"],
"List all sourced script names",
function () {
- if (!io._scriptNames.length)
+ let names = Object.keys(io._scriptNames);
+ if (!names.length)
dactyl.echomsg(_("command.scriptnames.none"));
else
modules.commandline.commandOutput(
template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
- ([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))));
+ ([i + 1, file] for ([i, file] in Iterator(names)))));
},
{ argCount: "0" });
if (args.bang)
arg = "!" + arg;
- // NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable?
- // pass through a raw bang when escaped or substitute the last command
-
- // This is an asinine and irritating feature when we have searchable
+ // This is an asinine and irritating "feature" when we have searchable
// command-line history. --Kris
if (modules.options["banghist"]) {
+ // NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable?
+ // pass through a raw bang when escaped or substitute the last command
+
// replaceable bang and no previous command?
dactyl.assert(!/((^|[^\\])(\\\\)*)!/.test(arg) || io._lastRunCommand,
_("command.run.noPrevious"));
literal: 0
});
},
- completion: function (dactyl, modules, window) {
+ completion: function init_completion(dactyl, modules, window) {
const { completion, io } = modules;
completion.charset = function (context) {
completion.environment = function environment(context) {
context.title = ["Environment Variable", "Value"];
context.generate = function ()
- io.system(util.OS.isWindows ? "set" : "env")
+ io.system(config.OS.isWindows ? "set" : "env")
.output.split("\n")
.filter(function (line) line.indexOf("=") > 0)
.map(function (line) line.match(/([^=]+)=(.*)/).slice(1));
completion.shellCommand = function shellCommand(context) {
context.title = ["Shell Command", "Path"];
context.generate = function () {
- let dirNames = services.environment.get("PATH").split(util.OS.isWindows ? ";" : ":");
+ let dirNames = services.environment.get("PATH").split(config.OS.pathListSep);
let commands = [];
for (let [, dirName] in Iterator(dirNames)) {
};
};
- completion.addUrlCompleter("f", "Local files", function (context, full) {
+ completion.addUrlCompleter("file", "Local files", function (context, full) {
let match = util.regexp(<![CDATA[
^
(?P<prefix>
if (!match.path) {
context.key = match.proto;
context.advance(match.proto.length);
- context.generate = function () util.chromePackages.map(function (p) [p, match.proto + p + "/"]);
+ context.generate = function () config.chromePackages.map(function (p) [p, match.proto + p + "/"]);
}
else if (match.scheme === "chrome") {
context.key = match.prefix;
}
if (!match || match.scheme === "resource" && match.path)
if (/^(\.{0,2}|~)\/|^file:/.test(context.filter)
- || util.OS.isWindows && /^[a-z]:/i.test(context.filter)
+ || config.OS.isWindows && /^[a-z]:/i.test(context.filter)
|| util.getFile(context.filter)
|| io.isJarURL(context.filter))
completion.file(context, full);
});
},
- javascript: function (dactyl, modules, window) {
+ javascript: function init_javascript(dactyl, modules, window) {
modules.JavaScript.setCompleter([File, File.expandPath],
[function (context, obj, args) {
context.quote[2] = "";
input: true
});
},
- options: function (dactyl, modules, window) {
+ options: function init_options(dactyl, modules, window) {
const { completion, options } = modules;
var shell, shellcmdflag;
- if (util.OS.isWindows) {
+ if (config.OS.isWindows) {
shell = "cmd.exe";
shellcmdflag = "/c";
}
options.add(["banghist", "bh"],
"Replace occurrences of ! with the previous command when executing external commands",
- "boolean", true);
+ "boolean", false);
options.add(["fileencoding", "fenc"],
"The character encoding used when reading and writing files",
"string", shellcmdflag,
{
getter: function (value) {
- if (this.hasChanged || !util.OS.isWindows)
+ if (this.hasChanged || !config.OS.isWindows)
return value;
return /sh/.test(options["shell"]) ? "-c" : "/c";
}