// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
-// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2007-2012 by Doug Kearns <dougkearns@gmail.com>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
// Some code based on Venkman
//
// This work is licensed for reuse under an MIT license. Details are
try {
-Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("io", {
exports: ["IO", "io"],
- require: ["services"],
- use: ["config", "messages", "storage", "styles", "template", "util"]
-}, this);
+ require: ["services"]
+});
+
+lazyRequire("config", ["config"]);
+lazyRequire("contexts", ["Contexts", "contexts"]);
+lazyRequire("storage", ["File", "storage"]);
+lazyRequire("styles", ["styles"]);
+lazyRequire("template", ["template"]);
// TODO: why are we passing around strings rather than file objects?
/**
* @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;
this._lastRunCommand = ""; // updated whenever the users runs a command with :!
this._scriptNames = [];
-
- this.downloadListener = {
- onDownloadStateChange: function (state, download) {
- if (download.state == services.downloadManager.DOWNLOAD_FINISHED) {
- let url = download.source.spec;
- let title = download.displayName;
- let file = download.targetFile.path;
- let size = download.size;
-
- dactyl.echomsg({ domains: [util.getHost(url)], message: _("io.downloadFinished", title, file) },
- 1, modules.commandline.ACTIVE_WINDOW);
- modules.autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size });
- }
- },
- onStateChange: function () {},
- onProgressChange: function () {},
- onSecurityChange: function () {}
- };
-
- services.downloadManager.addListener(this.downloadListener);
},
CommandFileMode: Class("CommandFileMode", modules.CommandMode, {
}
}),
- destroy: function destroy() {
- services.downloadManager.removeListener(this.downloadListener);
- },
-
/**
* Returns all directories named *name* in 'runtimepath'.
*
dactyl.echomsg(_("io.sourcing", filename.quote()), 2);
- let uri = services.io.newFileURI(file);
+ let uri = file.URI;
- // handle pure JavaScript files specially
- if (/\.js$/.test(filename)) {
+ let sourceJSM = function sourceJSM() {
+ context = contexts.Module(uri);
+ dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
+ }
+
+ if (/\.jsm$/.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 instanceof Finished)
+ return;
+ 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].join("");
+ }
+ 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);
- file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(600));
+ createTempFile: function createTempFile(name, type) {
+ if (name instanceof Ci.nsIFile) {
+ var file = name.clone();
+ if (!type || type == "file")
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666));
+ else
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, octal(777));
+ }
+ else {
+ file = services.directory.get("TmpD", Ci.nsIFile);
+ file.append(this.config.tempFile + (name ? "." + name : ""));
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666));
+ }
- 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);
}
file = util.getFile(file);
if (file && file.exists() && file.isFile() && file.isReadable()) {
// let jar = services.zipReader.getZip(file); Crashes.
- let jar = services.ZipReader(file);
+ let jar = services.ZipReader(file.file);
try {
let filter = RegExp("^" + util.regexp.escape(decodeURI(path))
+ "[^/]*/?$");
}
},
- 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.pathListSep);
// 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);
return -1;
}
- let process = services.Process(file);
+ let process = services.Process(file.file);
process.run(false, args.map(String), args.length);
try {
if (callable(blocking))
function () {
if (!process.isRunning) {
timer.cancel();
- util.trapErrors(blocking);
+ util.trapErrors(blocking, self, process.exitValue);
}
},
100, services.Timer.TYPE_REPEATING_SLACK);
// TODO: when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is
// fixed use that instead of a tmpfile
/**
- * Runs *command* in a subshell and returns the output in a string. The
- * shell used is that specified by the 'shell' option.
+ * Runs *command* in a subshell and returns the output. The shell used is
+ * that specified by the 'shell' option.
*
- * @param {string} command The command to run.
+ * @param {string|[string]} command The command to run. This can be a shell
+ * command string or an array of strings (a command and arguments)
+ * which will be escaped and concatenated.
* @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 initCommands(dactyl, modules, window) {
const { commands, completion, io } = modules;
commands.add(["cd", "chd[ir]"],
try {
file.write(lines.join("\n"));
+ dactyl.echomsg(_("io.writing", file.path.quote()), 2);
}
catch (e) {
dactyl.echoerr(_("io.notWriteable", file.path.quote()));
completer: function (context) completion.file(context, true)
});
- commands.add(["mks[yntax]"],
- "Generate a Vim syntax file",
+ commands.add(["mkv[imruntime]"],
+ "Create and install Vim runtime files for " + config.appName,
function (args) {
- let runtime = util.OS.isWindows ? "~/vimfiles/" : "~/.vim/";
- let file = io.File(runtime + "syntax/" + config.name + ".vim");
- if (args.length)
- file = io.File(args[0]);
+ dactyl.assert(args.length <= 1, _("io.oneFileAllowed"));
+
+ if (args.length) {
+ var rtDir = io.File(args[0]);
+ dactyl.assert(rtDir.exists(), _("io.noSuchDir", rtDir.path.quote()));
+ }
+ else
+ rtDir = io.File(config.OS.isWindows ? "~/vimfiles/" : "~/.vim/");
+
+ dactyl.assert(!rtDir.exists() || rtDir.isDirectory(), _("io.eNotDir", rtDir.path.quote()));
+
+ let rtItems = { ftdetect: {}, ftplugin: {}, syntax: {} };
- if (file.exists() && file.isDirectory() || args[0] && /\/$/.test(args[0]))
- file.append(config.name + ".vim");
- dactyl.assert(!file.exists() || args.bang, _("io.exists"));
+ // require bang if any of the paths exist
+ for (let [type, item] in iter(rtItems)) {
+ let file = io.File(rtDir).child(type, config.name + ".vim");
+ dactyl.assert(!file.exists() || args.bang, _("io.exists", file.path.quote()));
+ item.file = file;
+ }
+
+ rtItems.ftdetect.template = // {{{
+literal(/*" Vim filetype detection file
+<header>
- let template = util.compileMacro(<![CDATA[
-" Vim syntax file
-" Language: Pentadactyl configuration file
-" Maintainer: Doug Kearns <dougkearns@gmail.com>
+au BufNewFile,BufRead *<name>rc*,*.<fileext> set filetype=<name>
+*/);//}}}
+ rtItems.ftplugin.template = // {{{
+literal(/*" Vim filetype plugin file
+<header>
-" TODO: make this <name> specific - shared dactyl config?
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+let b:undo_ftplugin = "setl com< cms< fo< ofu< | unlet! b:browsefilter"
+
+setlocal comments=:\"
+setlocal commentstring=\"%s
+setlocal formatoptions-=t formatoptions+=croql
+setlocal omnifunc=syntaxcomplete#Complete
+
+if has("gui_win32") && !exists("b:browsefilter")
+ let b:browsefilter = "<appname> Config Files (*.<fileext>)\t*.<fileext>\n" .
+ \ "All Files (*.*)\t*.*\n"
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+*/);//}}}
+ rtItems.syntax.template = // {{{
+literal(/*" Vim syntax file
+<header>
if exists("b:current_syntax")
finish
let &cpo = s:cpo_save
unlet s:cpo_save
-" vim: tw=130 et ts=4 sw=4:
-]]>, true);
+" vim: tw=130 et ts=8 sts=4 sw=4:
+*/);//}}}
+
+ const { options } = modules;
const WIDTH = 80;
- function wrap(prefix, items, sep) {
+ function wrap(prefix, items, sep) {//{{{
sep = sep || " ";
let width = 0;
let lines = [];
}
lines.last.pop();
return lines.map(function (l) l.join("")).join("\n").replace(/\s+\n/gm, "\n");
- }
+ }//}}}
- const { commands, options } = modules;
- file.write(template({
+ let params = { // {{{
+ header: ['" Language: ' + config.appName + ' configuration file',
+ '" Maintainer: Doug Kearns <dougkearns@gmail.com>',
+ '" Version: ' + config.version].join("\n"),
name: config.name,
+ appname: config.appName,
+ fileext: config.fileExtension,
+ maintainer: "Doug Kearns <dougkearns@gmail.com>",
autocommands: wrap("syn keyword " + config.name + "AutoEvent ",
keys(config.autocommands)),
commands: wrap("syn keyword " + config.name + "Command ",
array(o.realNames for (o in options) if (o.type == "boolean"))
.flatten().map(String.quote),
", ") + "]"
- }));
+ }; // }}}
+
+ for (let { file, template } in values(rtItems)) {
+ try {
+ file.write(util.compileMacro(template, true)(params));
+ dactyl.echomsg(_("io.writing", file.path.quote()), 2);
+ }
+ catch (e) {
+ dactyl.echoerr(_("io.notWriteable", file.path.quote()));
+ dactyl.log(_("error.notWriteable", file.path, e.message));
+ }
+ }
}, {
argCount: "?",
bang: true,
- completer: function (context) completion.file(context, true),
+ completer: function (context) completion.directory(context, true),
literal: 1
});
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"));
result.output += "\n" + _("io.shellReturn", result.returnValue);
modules.commandline.command = args.commandName.replace("run", "$& ") + arg;
- modules.commandline.commandOutput(<span highlight="CmdOutput">{result.output}</span>);
+ modules.commandline.commandOutput(["span", { highlight: "CmdOutput" }, result.output]);
modules.autocommands.trigger("ShellCmdPost", {});
}, {
literal: 0
});
},
- completion: function (dactyl, modules, window) {
+ completion: function initCompletion(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) {
- let match = util.regexp(<![CDATA[
+ completion.addUrlCompleter("file", "Local files", function (context, full) {
+ let match = util.regexp(literal(/*
^
(?P<prefix>
(?P<proto>
)
(?P<path> \/[^\/]* )?
$
- ]]>, "x").exec(context.filter);
+ */), "x").exec(context.filter);
if (match) {
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 initJavascript(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 initOptions(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";
}