X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fio.jsm;h=9e717d74ed51ebd217ea017e821b017e7a7ef7c7;hb=8b6fcae7eaa413bc62d645d2d0c99835c47265e6;hp=a6487a99ea99d18ecd88928349a378180cafc0db;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/modules/io.jsm b/common/modules/io.jsm index a6487a9..9e717d7 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott -// Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2007-2012 by Doug Kearns +// Copyright (c) 2008-2012 Kris Maglione // Some code based on Venkman // // This work is licensed for reuse under an MIT license. Details are @@ -9,12 +9,16 @@ 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? /** @@ -22,14 +26,14 @@ defineModule("io", { * @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; @@ -39,26 +43,6 @@ var IO = Module("io", { 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, { @@ -82,10 +66,6 @@ var IO = Module("io", { } }), - destroy: function destroy() { - services.downloadManager.removeListener(this.downloadListener); - }, - /** * Returns all directories named *name* in 'runtimepath'. * @@ -105,7 +85,7 @@ var IO = Module("io", { * found it is sourced. Only the first file found (per specified path) is * sourced unless *all* is specified, then all found files are sourced. * - * @param {string[]} paths An array of relative paths to source. + * @param {[string]} paths An array of relative paths to source. * @param {boolean} all Whether all found files should be sourced. */ sourceFromRuntimePath: function sourceFromRuntimePath(paths, all) { @@ -116,7 +96,7 @@ var IO = Module("io", { outer: for (let dir in values(dirs)) { - for (let [,path] in Iterator(paths)) { + for (let [, path] in Iterator(paths)) { let file = dir.child(path); dactyl.echomsg(_("io.searchingFor", file.path.quote()), 3); @@ -164,25 +144,42 @@ var IO = Module("io", { 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)) @@ -196,19 +193,19 @@ 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); - dactyl.log("Sourced: " + filename, 3); return context; } catch (e) { - dactyl.reportError(e); - let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); + util.reportError(e); + let message = _("io.sourcingError", e.echoerr || (file ? file.path : filename) + ": " + e); if (!params.silent) dactyl.echoerr(message); } @@ -216,7 +213,7 @@ var IO = Module("io", { defineModule.loadLog.push("done sourcing " + filename + ": " + (Date.now() - time) + "ms"); } }, this); - }, + } }), // TODO: there seems to be no way, short of a new component, to change @@ -267,7 +264,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 +288,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 +306,26 @@ 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); - 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); }, @@ -335,9 +339,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); } @@ -354,9 +361,9 @@ var IO = Module("io", { */ listJar: function listJar(file, path) { file = util.getFile(file); - if (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)) + "[^/]*/?$"); @@ -366,12 +373,13 @@ var IO = Module("io", { yield entry; } finally { - jar.close(); + if (jar) + jar.close(); } } }, - readHeredoc: function (end) { + readHeredoc: function readHeredoc(end) { return ""; }, @@ -385,14 +393,16 @@ 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.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)) @@ -405,7 +415,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); @@ -422,9 +432,9 @@ var IO = Module("io", { * Runs an external program. * * @param {File|string} program The program to run. - * @param {string[]} args An array of arguments to pass to *program*. + * @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); @@ -436,7 +446,7 @@ var IO = Module("io", { 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)) @@ -444,7 +454,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); @@ -463,17 +473,21 @@ var IO = Module("io", { // 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) @@ -481,33 +495,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); }, /** @@ -521,8 +543,9 @@ 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; @@ -530,7 +553,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; } @@ -543,7 +566,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; @@ -554,7 +577,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 initCommands(dactyl, modules, window) { const { commands, completion, io } = modules; commands.add(["cd", "chd[ir]"], @@ -569,7 +592,6 @@ var IO = Module("io", { // go directly to an absolute path or look for a relative path // match in 'cdpath' - // TODO: handle ../ and ./ paths if (File.isAbsolutePath(arg)) { io.cwd = arg; dactyl.echomsg(io.cwd.path); @@ -610,7 +632,7 @@ var IO = Module("io", { dactyl.assert(!file.exists() || args.bang, _("io.exists", file.path.quote())); // TODO: Use a set/specifiable list here: - let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator(true)) if (cmd.serialize)]; + let lines = [cmd.serialize().map(commands.commandToString, cmd) for (cmd in commands.iterator()) if (cmd.serialize)]; lines = array.flatten(lines); lines.unshift('"' + config.version + "\n"); @@ -618,10 +640,11 @@ var IO = Module("io", { try { file.write(lines.join("\n")); + dactyl.echomsg(_("io.writing", file.path.quote()), 2); } catch (e) { - dactyl.echoerr(_("io.notWriteable"), file.path.quote()); - dactyl.log("Could not write to " + file.path + ": " + e.message); // XXX + dactyl.echoerr(_("io.notWriteable", file.path.quote())); + dactyl.log(_("error.notWriteable", file.path, e.message)); // XXX } }, { argCount: "*", // FIXME: should be "?" but kludged for proper error message @@ -629,24 +652,65 @@ var IO = Module("io", { 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 (file.exists() && file.isDirectory() || args[0] && /\/$/.test(args[0])) - file.append(config.name + ".vim"); - dactyl.assert(!file.exists() || args.bang, "File exists"); + 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/"); - let template = util.compileMacro( + dactyl.assert(!rtDir.exists() || rtDir.isDirectory(), _("io.eNotDir", rtDir.path.quote())); -" TODO: make this specific - shared dactyl config? + let rtItems = { ftdetect: {}, ftplugin: {}, syntax: {} }; + + // 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 +
+ +au BufNewFile,BufRead *rc*,*. set filetype= +*/);//}}} + rtItems.ftplugin.template = // {{{ +literal(/*" Vim filetype plugin file +
+ +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 = " Config Files (*.)\t*.\n" . + \ "All Files (*.*)\t*.*\n" +endif + +let &cpo = s:cpo_save +unlet s:cpo_save +*/);//}}} + rtItems.syntax.template = // {{{ +literal(/*" Vim syntax file +
if exists("b:current_syntax") finish @@ -699,22 +763,20 @@ execute 'syn region Css matchgroup=CssDelimiter' syn match Notation "<[0-9A-Za-z-]\+>" -syn match Comment +".*$+ contains=Todo,@Spell syn keyword Todo FIXME NOTE TODO XXX contained syn region String start="\z(["']\)" end="\z1" skip="\\\\\|\\\z1" oneline -syn match LineComment +^\s*".*$+ contains=Todo,@Spell +syn match Comment +^\s*".*$+ contains=Todo,@Spell " NOTE: match vim.vim highlighting group names hi def link AutoCmd Command hi def link AutoEvent Type hi def link Command Statement -hi def link Comment Comment hi def link JavaScriptDelimiter Delimiter hi def link CssDelimiter Delimiter hi def link Notation Special -hi def link LineComment Comment +hi def link Comment Comment hi def link Option PreProc hi def link SetMod Option hi def link String String @@ -725,11 +787,13 @@ let b:current_syntax = "" 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 = []; @@ -746,11 +810,16 @@ unlet s:cpo_save } 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 ', + '" Version: ' + config.version].join("\n"), name: config.name, + appname: config.appName, + fileext: config.fileExtension, + maintainer: "Doug Kearns ", autocommands: wrap("syn keyword " + config.name + "AutoEvent ", keys(config.autocommands)), commands: wrap("syn keyword " + config.name + "Command ", @@ -761,11 +830,22 @@ unlet s:cpo_save 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 }); @@ -782,14 +862,19 @@ unlet s:cpo_save commands.add(["scrip[tnames]"], "List all sourced script names", function () { - modules.commandline.commandOutput( - template.tabular(["", "Filename"], ["text-align: right; padding-right: 1em;"], - ([i + 1, file] for ([i, file] in Iterator(io._scriptNames))))); // TODO: add colon and remove column titles for pedantic Vim compatibility? + 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(names))))); + }, { argCount: "0" }); commands.add(["so[urce]"], - "Read Ex commands from a file", + "Read Ex commands, JavaScript or CSS from a file", function (args) { if (args.length > 1) dactyl.echoerr(_("io.oneFileAllowed")); @@ -811,15 +896,15 @@ 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, - "E34: No previous command"); + _("command.run.noPrevious")); arg = arg.replace(/(\\)*!/g, function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", io._lastRunCommand) @@ -830,21 +915,21 @@ unlet s:cpo_save let result = io.system(arg); if (result.returnValue != 0) - result.output += "\nshell returned " + result.returnValue; + result.output += "\n" + _("io.shellReturn", result.returnValue); - modules.commandline.command = "!" + arg; - modules.commandline.commandOutput({result.output}); + modules.commandline.command = args.commandName.replace("run", "$& ") + arg; + modules.commandline.commandOutput(["span", { highlight: "CmdOutput" }, result.output]); modules.autocommands.trigger("ShellCmdPost", {}); }, { - argCount: "?", // TODO: "1" - probably not worth supporting weird Vim edge cases. The dream is dead. --djk + argCount: "?", bang: true, // This is abominably slow. // completer: function (context) completion.shellCommand(context), literal: 0 }); }, - completion: function (dactyl, modules, window) { + completion: function initCompletion(dactyl, modules, window) { const { completion, io } = modules; completion.charset = function (context) { @@ -871,13 +956,16 @@ 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)); }; completion.file = function file(context, full, dir) { + if (/^jar:[^!]*$/.test(context.filter)) + context.advance(4); + // dir == "" is expanded inside readDirectory to the current dir function getDir(str) str.match(/^(?:.*[\/\\])?/)[0]; dir = getDir(dir || context.filter); @@ -900,10 +988,8 @@ unlet s:cpo_save }; context.compare = function (a, b) b.isdir - a.isdir || String.localeCompare(a.text, b.text); - if (modules.options["wildignore"]) { - let wig = modules.options.get("wildignore"); - context.filters.push(function (item) item.isdir || !wig.getKey(this.name)); - } + if (modules.options["wildignore"]) + context.filters.push(function (item) !modules.options.get("wildignore").getKey(item.path)); // context.background = true; context.key = dir; @@ -940,7 +1026,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)) { @@ -954,8 +1040,8 @@ unlet s:cpo_save }; }; - completion.addUrlCompleter("f", "Local files", function (context, full) { - let match = util.regexp( (?P @@ -966,29 +1052,32 @@ unlet s:cpo_save ) (?P \/[^\/]* )? $ - ]]>, "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; context.advance(match.prefix.length + 1); context.generate = function () iter({ - content: "Chrome content", - locale: "Locale-specific content", - skin: "Theme-specific content" + content: /*L*/"Chrome content", + locale: /*L*/"Locale-specific content", + skin: /*L*/"Theme-specific content" }); } } if (!match || match.scheme === "resource" && match.path) - if (/^(\.{0,2}|~)\/|^file:/.test(context.filter) || util.getFile(context.filter) || io.isJarURL(context.filter)) + if (/^(\.{0,2}|~)\/|^file:/.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] = ""; @@ -1007,11 +1096,11 @@ unlet s:cpo_save 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"; } @@ -1022,7 +1111,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", @@ -1058,7 +1147,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"; } @@ -1067,7 +1156,7 @@ unlet s:cpo_save options["shellcmdflag"]; options.add(["wildignore", "wig"], - "List of file patterns to ignore when completing file names", + "List of path name patterns to ignore when completing files and directories", "regexplist", ""); } });