X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fio.jsm;h=9e717d74ed51ebd217ea017e821b017e7a7ef7c7;hb=8b6fcae7eaa413bc62d645d2d0c99835c47265e6;hp=63ac6061921a35d2d88c12a13d1f65c7712c458b;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 63ac606..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'. * @@ -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,10 +193,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 +204,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 +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); } @@ -356,7 +363,7 @@ var IO = Module("io", { 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)) + "[^/]*/?$"); @@ -372,7 +379,7 @@ var IO = Module("io", { } }, - readHeredoc: function (end) { + readHeredoc: function readHeredoc(end) { return ""; }, @@ -386,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)) @@ -406,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); @@ -425,7 +434,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); @@ -437,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)) @@ -445,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); @@ -464,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) @@ -482,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); }, /** @@ -522,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; @@ -531,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; } @@ -544,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; @@ -555,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]"], @@ -618,6 +640,7 @@ 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())); @@ -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 (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 +
- let template = util.compileMacro( +au BufNewFile,BufRead *rc*,*. set filetype= +*/);//}}} + rtItems.ftplugin.template = // {{{ +literal(/*" Vim filetype plugin file +
-" TODO: make this 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 = " 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 @@ -723,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 = []; @@ -744,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 ", @@ -759,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 }); @@ -780,12 +862,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 +896,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")); @@ -835,7 +918,7 @@ unlet s:cpo_save result.output += "\n" + _("io.shellReturn", result.returnValue); modules.commandline.command = args.commandName.replace("run", "$& ") + arg; - modules.commandline.commandOutput({result.output}); + modules.commandline.commandOutput(["span", { highlight: "CmdOutput" }, result.output]); modules.autocommands.trigger("ShellCmdPost", {}); }, { @@ -846,7 +929,7 @@ unlet s:cpo_save literal: 0 }); }, - completion: function (dactyl, modules, window) { + completion: function initCompletion(dactyl, modules, window) { const { completion, io } = modules; completion.charset = function (context) { @@ -873,7 +956,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 +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)) { @@ -957,8 +1040,8 @@ unlet s:cpo_save }; }; - completion.addUrlCompleter("f", "Local files", function (context, full) { - let match = util.regexp( (?P @@ -969,12 +1052,12 @@ 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; @@ -988,13 +1071,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 initJavascript(dactyl, modules, window) { modules.JavaScript.setCompleter([File, File.expandPath], [function (context, obj, args) { context.quote[2] = ""; @@ -1013,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"; } @@ -1028,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", @@ -1064,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"; }