X-Git-Url: https://git.donarmstrong.com/dactyl.git?a=blobdiff_plain;f=common%2Fmodules%2Fio.jsm;h=5eefcc17b6bb6aeaceecae247a49df9035a36846;hb=9044153cb63835e39b9de8ec4ade237c03e3888a;hp=63ac6061921a35d2d88c12a13d1f65c7712c458b;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 63ac606..5eefcc1 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -5,31 +5,33 @@ // // 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; @@ -164,25 +166,40 @@ var IO = Module("io", { 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)) @@ -196,10 +213,10 @@ var IO = Module("io", { 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); @@ -207,7 +224,7 @@ var IO = Module("io", { 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); @@ -267,7 +284,7 @@ var IO = Module("io", { * @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) @@ -291,13 +308,13 @@ var IO = Module("io", { * @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()) @@ -309,19 +326,21 @@ var IO = Module("io", { 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); }, @@ -335,9 +354,12 @@ var IO = Module("io", { */ 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); } @@ -372,7 +394,7 @@ var IO = Module("io", { } }, - readHeredoc: function (end) { + readHeredoc: function readHeredoc(end) { return ""; }, @@ -386,14 +408,15 @@ var IO = Module("io", { * 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)) @@ -406,7 +429,7 @@ var IO = Module("io", { // 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); @@ -425,7 +448,7 @@ var IO = Module("io", { * @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); @@ -445,7 +468,7 @@ var IO = Module("io", { function () { if (!process.isRunning) { timer.cancel(); - util.trapErrors(blocking); + util.trapErrors(blocking, self, process.exitValue); } }, 100, services.Timer.TYPE_REPEATING_SLACK); @@ -469,12 +492,14 @@ var IO = Module("io", { * * @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) @@ -482,33 +507,41 @@ var IO = Module("io", { 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); }, /** @@ -522,8 +555,8 @@ var IO = Module("io", { * @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; @@ -531,7 +564,7 @@ var IO = Module("io", { } finally { if (!checked || res !== true) - args.forEach(function (f) f && f.remove(false)); + args.forEach(function (f) f.remove(false)); } return res; } @@ -544,7 +577,7 @@ var IO = Module("io", { 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; @@ -555,7 +588,7 @@ var IO = Module("io", { */ 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]"], @@ -632,7 +665,7 @@ var IO = Module("io", { 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]); @@ -780,12 +813,13 @@ unlet s:cpo_save 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(["", "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" }); @@ -813,12 +847,12 @@ unlet s:cpo_save 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")); @@ -846,7 +880,7 @@ unlet s:cpo_save literal: 0 }); }, - completion: function (dactyl, modules, window) { + completion: function init_completion(dactyl, modules, window) { const { completion, io } = modules; completion.charset = function (context) { @@ -873,7 +907,7 @@ unlet s:cpo_save 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)); @@ -943,7 +977,7 @@ unlet s:cpo_save 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)) { @@ -957,7 +991,7 @@ unlet s:cpo_save }; }; - completion.addUrlCompleter("f", "Local files", function (context, full) { + completion.addUrlCompleter("file", "Local files", function (context, full) { let match = util.regexp( @@ -974,7 +1008,7 @@ unlet s:cpo_save 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; @@ -988,13 +1022,13 @@ unlet s:cpo_save } 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] = ""; @@ -1013,11 +1047,11 @@ unlet s:cpo_save 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"; } @@ -1028,7 +1062,7 @@ unlet s:cpo_save 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", @@ -1064,7 +1098,7 @@ unlet s:cpo_save "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"; }