1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-22 23:58:00 +01:00
Files
pentadactyl-pm/content/io.js

629 lines
24 KiB
JavaScript

/***** BEGIN LICENSE BLOCK ***** {{{
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
Code based on venkman
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
}}} ***** END LICENSE BLOCK *****/
liberator.IO = function () //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
var environmentService = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
const WINDOWS = navigator.platform == "Win32";
var cwd = null, oldcwd = null;
var extname = liberator.config.name.toLowerCase(); // "vimperator" or "muttator"
var lastRunCommand = ""; // updated whenever the users runs a command with :!
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
liberator.commands.add(["cd", "chd[ir]"],
"Change the current directory",
function (args)
{
if (!args)
args = "~";
if (liberator.io.setCurrentDirectory(args))
liberator.echo(liberator.io.getCurrentDirectory());
},
{
completer: function (filter) { return liberator.completion.file(filter, true); }
});
liberator.commands.add(["pw[d]"],
"Print the current directory name",
function (args)
{
if (args)
liberator.echoerr("E488: Trailing characters");
else
liberator.echo(liberator.io.getCurrentDirectory());
});
// mkv[imperatorrc] or mkm[uttatorrc]
liberator.commands.add(["mk" + extname.substr(0, 1) + "[" + extname.substr(1) + "rc]"],
"Write current key mappings and changed options to the config file",
function (args, special)
{
// TODO: "E172: Only one file name allowed"
var filename;
if (args)
filename = args;
else
{
filename = (navigator.platform == "Win32") ? "~/_" : "~/.";
filename += extname+ "rc";
}
var file = liberator.io.getFile(filename);
if (file.exists() && !special)
{
liberator.echoerr("E189: \"" + filename + "\" exists (add ! to override)");
return;
}
var line = "\" " + liberator.version + "\n";
line += "\" Mappings\n";
var mode = [[[liberator.modes.NORMAL], ""], [[liberator.modes.COMMAND_LINE], "c"],
[[liberator.modes.INSERT, liberator.modes.TEXTAREA], "i"]];
for (var y = 0; y < mode.length; y++)
{
// NOTE: names.length is always 1 on user maps. If that changes, also fix getUserIterator and v.m.list
for (var map in liberator.mappings.getUserIterator(mode[y][0]))
line += mode[y][1] + (map.noremap ? "nore" : "") + "map " + map.names[0] + " " + map.rhs + "\n";
}
line += "\n\" Options\n";
for (var option in liberator.options)
{
// TODO: options should be queried for this info
// TODO: string/list options might need escaping in future
if (!/fullscreen|usermode/.test(option.name) && option.value != option.defaultValue)
{
if (option.type == "boolean")
line += "set " + (option.value ? option.name : "no" + option.name) + "\n";
else
line += "set " + option.name + "=" + option.value + "\n";
}
}
// :mkvimrc doesn't save autocommands, so we don't either - remove this code at some point
// line += "\n\" Auto-Commands\n";
// for (var item in liberator.autocommands)
// line += "autocmd " + item + "\n";
line += "\n\" Abbreviations\n";
for (var abbrCmd in liberator.editor.abbreviations)
line += abbrCmd;
// if (liberator.events.getMapLeader() != "\\")
// line += "\nlet mapleader = \"" + liberator.events.getMapLeader() + "\"\n";
// source a user .vimperatorrc file
line += "\nsource! " + filename + ".local\n";
line += "\n\" vim: set ft=vimperator:";
liberator.io.writeFile(file, line);
});
liberator.commands.add(["so[urce]"],
"Read Ex commands from a file",
function (args, special)
{
// FIXME: implement proper filename quoting
//if (/[^\\]\s/.test(args))
//{
// liberator.echoerr("E172: Only one file name allowed");
// return;
//}
liberator.io.source(args, special);
},
{
completer: function (filter) { return liberator.completion.file(filter, true); }
});
liberator.commands.add(["!", "run"],
"Run a command",
function (args, special)
{
// :!! needs to be treated specially as the command parser sets the
// special flag but removes the ! from args
if (special)
args = "!" + (args || "");
// TODO: better escaping of ! to also substitute \\! correctly
args = args.replace(/(^|[^\\])!/g, "$1" + lastRunCommand);
lastRunCommand = args;
var output = liberator.io.system(args);
if (output)
liberator.echo(liberator.util.escapeHTML(output));
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
var ioManager = {
MODE_RDONLY: 0x01,
MODE_WRONLY: 0x02,
MODE_RDWR: 0x04,
MODE_CREATE: 0x08,
MODE_APPEND: 0x10,
MODE_TRUNCATE: 0x20,
MODE_SYNC: 0x40,
MODE_EXCL: 0x80,
get directorySeperator()
{
return WINDOWS ? "\\" : "/";
},
expandPath: function (path)
{
// TODO: proper pathname separator translation like Vim
if (WINDOWS)
path = path.replace("/", "\\", "g");
// expand "~" to VIMPERATOR_HOME or HOME (USERPROFILE or HOMEDRIVE\HOMEPATH on Windows if HOME is not set)
if (/^~/.test(path))
{
var home = environmentService.get("VIMPERATOR_HOME");
if (!home)
home = environmentService.get("HOME");
if (WINDOWS && !home)
home = environmentService.get("USERPROFILE") ||
environmentService.get("HOMEDRIVE") + environmentService.get("HOMEPATH");
path = path.replace("~", home);
}
// expand any $ENV vars
var envVars = path.match(/\$\w+\b/g); // this is naive but so is Vim and we like to be compatible
if (envVars)
{
var expansion;
for (var i = 0; i < envVars.length; i++)
{
expansion = environmentService.get(envVars[i].replace("$", ""));
if (expansion)
path = path.replace(envVars[i], expansion);
}
}
return path;
},
getCurrentDirectory: function ()
{
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
var dirs = [cwd, "$PWD", "~"];
for (var i = 0; i < dirs.length; i++)
{
if (!dirs[i])
continue;
var fullname = ioManager.expandPath(dirs[i]);
try
{
file.initWithPath(fullname);
}
catch (e)
{
continue;
}
if (file.exists() && file.isDirectory())
return fullname;
}
// just make sure we return something which always is a directory
return WINDOWS ? "C:\\" : "/";
},
setCurrentDirectory: function (newdir)
{
if (!newdir)
newdir = "~";
if (newdir == "-")
{
[cwd, oldcwd] = [oldcwd, cwd];
}
else
{
newdir = ioManager.expandPath(newdir);
var file = ioManager.getFile(newdir);
if (!file.exists() || !file.isDirectory())
{
liberator.echoerr("E344: Can't find directory \"" + newdir + "\" in path");
return null;
}
[cwd, oldcwd] = [newdir, cwd];
}
return ioManager.getCurrentDirectory();
},
getSpecialDirectory: function (directory)
{
var pluginDir;
if (WINDOWS)
pluginDir = "~/" + liberator.config.name.toLowerCase() + "/" + directory;
else
pluginDir = "~/." + liberator.config.name.toLowerCase() + "/" + directory;
pluginDir = ioManager.getFile(ioManager.expandPath(pluginDir));
return pluginDir.exists() && pluginDir.isDirectory() ? pluginDir : null;
},
getRCFile: function ()
{
var rcFile1 = ioManager.getFile("~/." + liberator.config.name.toLowerCase() + "rc");
var rcFile2 = ioManager.getFile("~/_" + liberator.config.name.toLowerCase() + "rc");
if (WINDOWS)
[rcFile1, rcFile2] = [rcFile2, rcFile1]
if (rcFile1.exists() && rcFile1.isFile())
return rcFile1;
else if (rcFile2.exists() && rcFile2.isFile())
return rcFile2;
else
return null;
},
// return a nsILocalFile for path where you can call isDirectory(), etc. on
// caller must check with .exists() if the returned file really exists
// also expands relative paths
getFile: function (path)
{
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
// convert relative to absolute pathname
path = ioManager.expandPath(path);
if (!/^(file:|[a-zA-Z]:|\/)/.test(path)) // starts not with either /, C: or file:
path = ioManager.getCurrentDirectory() + (WINDOWS ? "\\" : "/") + path; // TODO: for now homedir, later relative to current dir?
else
path = path.replace(/^file:(\/\/)?/, "");
file.initWithPath(path);
return file;
},
// TODO: make secure
// returns a nsILocalFile or null if it could not be created
createTempFile: function ()
{
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
var tmpname = liberator.config.name.toLowerCase() + ".tmp";
if (liberator.config.name == "Muttator")
tmpname = "mutt-ator-mail"; // to allow vim to :set ft=mail automatically
if (WINDOWS)
{
var dir = environmentService.get("TMP") || environmentService.get("TEMP") || "C:\\";
file.initWithPath(dir + "\\" + tmpname);
}
else
{
var dir = environmentService.get("TMP") || environmentService.get("TEMP") || "/tmp/";
file.initWithPath(dir + "/" + tmpname);
}
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
if (!file.exists())
return null;
return file;
},
// file is either a full pathname or an instance of file instanceof nsILocalFile
readDirectory: function (file)
{
if (typeof file == "string")
file = ioManager.getFile(file);
else if (!(file instanceof Components.interfaces.nsILocalFile))
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
if (file.isDirectory())
{
var entries = file.directoryEntries;
var array = [];
while (entries.hasMoreElements())
{
var entry = entries.getNext();
entry.QueryInterface(Components.interfaces.nsIFile);
array.push(entry);
}
return array;
}
else
return []; // XXX: or should it throw an error, probably yes?
},
// file is either a full pathname or an instance of file instanceof nsILocalFile
// reads a file in "text" mode and returns the string
readFile: function (file)
{
var ifstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var icstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
var charset = "UTF-8";
if (typeof file == "string")
file = ioManager.getFile(file);
else if (!(file instanceof Components.interfaces.nsILocalFile))
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
ifstream.init(file, -1, 0, 0);
const replacementChar = Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
icstream.init(ifstream, charset, 4096, replacementChar); // 4096 bytes buffering
var buffer = "";
var str = {};
while (icstream.readString(4096, str) != 0)
buffer += str.value;
icstream.close();
ifstream.close();
return buffer;
},
// file is either a full pathname or an instance of file instanceof nsILocalFile
// default permission = 0644, only used when creating a new file, does not change permissions if the file exists
// mode can be ">" or ">>" in addition to the normal MODE_* flags
writeFile: function (file, buf, mode, perms)
{
var ofstream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
var ocstream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Components.interfaces.nsIConverterOutputStream);
var charset = "UTF-8"; // Can be any character encoding name that Mozilla supports
if (typeof file == "string")
file = ioManager.getFile(file);
else if (!(file instanceof Components.interfaces.nsILocalFile))
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
if (mode == ">>")
mode = ioManager.MODE_WRONLY | ioManager.MODE_CREATE | ioManager.MODE_APPEND;
else if (!mode || mode == ">")
mode = ioManager.MODE_WRONLY | ioManager.MODE_CREATE | ioManager.MODE_TRUNCATE;
if (!perms)
perms = 0644;
ofstream.init(file, mode, perms, 0);
ocstream.init(ofstream, charset, 0, 0x0000);
ocstream.writeString(buf);
ocstream.close();
ofstream.close();
},
run: function (program, args, blocking)
{
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
if (!args)
args = [];
if (typeof blocking != "boolean")
blocking = false;
try
{
file.initWithPath(program);
}
catch (e)
{
var dirs = environmentService.get("PATH").split(WINDOWS ? ";" : ":");
lookup:
for (var i = 0; i < dirs.length; i++)
{
var path = dirs[i] + (WINDOWS ? "\\" : "/") + program;
try
{
file.initWithPath(path);
if (file.exists())
break;
// automatically try to add the executable path extensions on windows
if (WINDOWS)
{
var extensions = environmentService.get("PATHEXT").split(";");
for (let j = 0; j < extensions.length; j++)
{
path = dirs[i] + "\\" + program + extensions[j];
file.initWithPath(path);
if (file.exists())
break lookup;
}
}
}
catch (e) { }
}
}
if (!file.exists())
{
liberator.echoerr("command not found: " + program);
return -1;
}
var process = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
process.init(file);
var ec = process.run(blocking, args, args.length);
return ec;
},
// when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is fixed
// is fixed, should use that instead of a tmpfile
// TODO: add shell/shellcmdflag options to replace "sh" and "-c"
system: function (str, input)
{
var fileout = ioManager.createTempFile();
if (!fileout)
return "";
if (WINDOWS)
var command = str + " > " + fileout.path;
else
var command = str + " > \"" + fileout.path.replace('"', '\\"') + "\"";
var filein = null;
if (input)
{
filein = ioManager.createTempFile();
ioManager.writeFile(filein, input);
command += " < \"" + filein.path.replace('"', '\\"') + "\"";
}
var res;
if (WINDOWS)
res = ioManager.run("cmd.exe", ["/C", command], true);
else
res = ioManager.run("sh", ["-c", command], true);
var output = ioManager.readFile(fileout);
fileout.remove(false);
if (filein)
filein.remove(false);
// if there is only one \n at the end, chop it off
if (output && output.indexOf("\n") == output.length - 1)
output = output.substr(0, output.length - 1);
return output;
},
// files which end in .js are sourced as pure javascript files,
// no need (actually forbidden) to add: js <<EOF ... EOF around those files
source: function (filename, silent)
{
try
{
var file = ioManager.getFile(filename);
if (!file.exists())
{
if (!silent)
liberator.echoerr("E484: Can't open file " + filename);
return false;
}
var str = ioManager.readFile(filename);
// handle pure javascript files specially
if (/\.js$/.test(filename))
{
eval("with(liberator){" + str + "}");
}
else
{
var heredoc = "";
var heredocEnd = null; // the string which ends the heredoc
str.split("\n").forEach(function (line)
{
if (heredocEnd) // we already are in a heredoc
{
if (heredocEnd.test(line))
{
eval("with(liberator){" + heredoc + "}");
heredoc = "";
heredocEnd = null;
}
else
{
heredoc += line + "\n";
}
}
else
{
// check for a heredoc
var [count, cmd, special, args] = liberator.commands.parseCommand(line);
var command = liberator.commands.get(cmd);
if (command && command.name == "javascript")
{
var matches = args.match(/(.*)<<\s*([^\s]+)$/);
if (matches)
{
heredocEnd = new RegExp("^" + matches[2] + "$", "m");
if (matches[1])
heredoc = matches[1] + "\n";
}
else
{
command.execute(args, special, count);
}
}
else
{
// execute a normal liberator command
liberator.execute(line);
}
}
});
}
liberator.log("Sourced: " + filename, 3);
}
catch (e)
{
if (!silent)
liberator.echoerr(e);
}
}
};
return ioManager;
//}}}
}; //}}}
// vim: set fdm=marker sw=4 ts=4 et: