diff --git a/content/bookmarks.js b/content/bookmarks.js index f2c84363..fc287b5d 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -108,6 +108,7 @@ vimperator.Bookmarks = function () //{{{ /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + var modes = vimperator.config.browserModes || [vimperator.modes.NORMAL]; vimperator.mappings.add(modes, ["a"], @@ -124,6 +125,71 @@ vimperator.Bookmarks = function () //{{{ "Toggle bookmarked state of current URL", function () { vimperator.bookmarks.toggle(vimperator.buffer.URL); }); + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["bma[rk]"], + "Add a bookmark", + function (args) + { + var res = vimperator.commands.parseArgs(args, this.args); + if (!res) + return; + + var url = res.args.length == 0 ? vimperator.buffer.URL : res.args[0]; + var title = vimperator.commands.getOption(res.opts, "-title", res.args.length == 0 ? vimperator.buffer.title : null); + if (!title) + title = url; + var keyword = vimperator.commands.getOption(res.opts, "-keyword", null); + var tags = vimperator.commands.getOption(res.opts, "-tags", []); + + if (vimperator.bookmarks.add(false, title, url, keyword, tags)) + { + var extra = ""; + if (title != url) + extra = " (" + title + ")"; + vimperator.echo("Added bookmark: " + url + extra, vimperator.commandline.FORCE_SINGLELINE); + } + else + vimperator.echoerr("Exxx: Could not add bookmark `" + title + "'", vimperator.commandline.FORCE_SINGLELINE); + }, + { + args: [[["-title", "-t"], vimperator.commands.OPTION_STRING], + [["-tags", "-T"], vimperator.commands.OPTION_LIST], + [["-keyword", "-k"], vimperator.commands.OPTION_STRING, function (arg) { return /\w/.test(arg); }]] + }); + + vimperator.commands.add(["bmarks"], + "List or open multiple bookmarks", + function (args, special) + { + var res = vimperator.commands.parseArgs(args, this.args); + if (!res) + return; + + var tags = vimperator.commands.getOption(res.opts, "-tags", []); + vimperator.bookmarks.list(res.args.join(" "), tags, special); + }, + { + completer: function (filter) { return [0, vimperator.bookmarks.get(filter)]; }, + args: [[["-tags", "-T"], vimperator.commands.OPTION_LIST]] + }); + + vimperator.commands.add(["delbm[arks]"], + "Delete a bookmark", + function (args, special) + { + var url = args; + if (!url) + url = vimperator.buffer.URL; + + var deletedCount = vimperator.bookmarks.remove(url); + vimperator.echo(deletedCount + " bookmark(s) with url `" + url + "' deleted", vimperator.commandline.FORCE_SINGLELINE); + }, + { + completer: function (filter) { return [0, vimperator.bookmarks.get(filter)]; } + }); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// @@ -460,6 +526,7 @@ vimperator.History = function () //{{{ /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + var modes = vimperator.config.browserModes || [vimperator.modes.NORMAL]; vimperator.mappings.add(modes, @@ -483,6 +550,94 @@ vimperator.History = function () //{{{ { flags: vimperator.Mappings.flags.COUNT }); /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["ba[ck]"], + "Go back in the browser history", + function (args, special, count) + { + if (special) + vimperator.history.goToStart(); + else + { + if (args) + { + var sh = getWebNavigation().sessionHistory; + for (var i = sh.index - 1; i >= 0; i--) + { + if (sh.getEntryAtIndex(i, false).URI.spec == args) + { + getWebNavigation().gotoIndex(i); + return; + } + } + } + vimperator.history.stepTo(count > 0 ? -1 * count : -1); + } + }, + { + completer: function (filter) + { + var sh = getWebNavigation().sessionHistory; + var completions = []; + for (var i = sh.index - 1; i >= 0; i--) + { + var entry = sh.getEntryAtIndex(i, false); + var url = entry.URI.spec; + var title = entry.title; + if (vimperator.completion.match([url, title], filter, false)) + completions.push([url, title]); + } + return [0, completions]; + } + }); + + vimperator.commands.add(["fo[rward]", "fw"], + "Go forward in the browser history", + function (args, special, count) + { + if (special) + vimperator.history.goToEnd(); + else + { + if (args) + { + var sh = getWebNavigation().sessionHistory; + for (var i = sh.index + 1; i < sh.count; i++) + { + if (sh.getEntryAtIndex(i, false).URI.spec == args) + { + getWebNavigation().gotoIndex(i); + return; + } + } + } + vimperator.history.stepTo(count > 0 ? count : 1); + } + }, + { + completer: function (filter) + { + var sh = getWebNavigation().sessionHistory; + var completions = []; + for (var i = sh.index + 1; i < sh.count; i++) + { + var entry = sh.getEntryAtIndex(i, false); + var url = entry.URI.spec; + var title = entry.title; + if (vimperator.completion.match([url, title], filter, false)) + completions.push([url, title]); + } + return [0, completions]; + } + }); + + vimperator.commands.add(["hist[ory]", "hs"], + "Show recently visited URLs", + function (args, special) { vimperator.history.list(args, special); }, + { completer: function (filter) { return [0, vimperator.history.get(filter)]; } }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -652,6 +807,65 @@ vimperator.QuickMarks = function () //{{{ { flags: vimperator.Mappings.flags.ARGUMENT }); /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["delqm[arks]"], + "Delete the specified QuickMarks", + function (args, special) + { + // TODO: finish arg parsing - we really need a proper way to do this. :) + if (!special && !args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + if (special && args) + { + vimperator.echoerr("E474: Invalid argument"); + return; + } + + if (special) + vimperator.quickmarks.removeAll(); + else + vimperator.quickmarks.remove(args); + }); + + vimperator.commands.add(["qma[rk]"], + "Mark a URL with a letter for quick access", + function (args) + { + if (!args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + + var matches = args.match(/^([a-zA-Z0-9])(?:\s+(.+))?$/); + if (!matches) + vimperator.echoerr("E488: Trailing characters"); + else if (!matches[2]) + vimperator.quickmarks.add(matches[1], vimperator.buffer.URL); + else + vimperator.quickmarks.add(matches[1], matches[2]); + }); + + vimperator.commands.add(["qmarks"], + "Show all QuickMarks", + function (args) + { + // ignore invalid mark characters unless there are no valid mark chars + if (args && !/[a-zA-Z0-9]/.test(args)) + { + vimperator.echoerr("E283: No QuickMarks matching \"" + args + "\""); + return; + } + + var filter = args.replace(/[^a-zA-Z0-9]/g, ""); + vimperator.quickmarks.list(filter); + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ diff --git a/content/buffer.js b/content/buffer.js index 99ccf36d..3683edfa 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -408,20 +408,109 @@ vimperator.Buffer = function () //{{{ function (count) { vimperator.buffer.showPageInfo(true); }); - /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// COMMANDS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - - vimperator.commands.addUserCommand(new vimperator.Command(["test"], + + vimperator.commands.add(["ha[rdcopy]"], + "Print current document", + function () { getBrowser().contentWindow.print(); }); + + vimperator.commands.add(["pa[geinfo]"], + "Show various page information", + function () { vimperator.buffer.showPageInfo(true); }); + + vimperator.commands.add(["re[load]"], + "Reload current page", + function (args, special) { vimperator.tabs.reload(getBrowser().mCurrentTab, special); }); + + vimperator.commands.add(["sav[eas]", "w[rite]"], + "Save current document to disk", function (args, special) { - alert(args) - }, + var file = vimperator.io.getFile(args || ""); + // we always want to save that link relative to the current working directory + vimperator.options.setPref("browser.download.lastDir", vimperator.io.getCurrentDirectory()); + //if (args) + //{ + // saveURL(vimperator.buffer.URL, args, null, true, special, // special == skipPrompt + // makeURI(vimperator.buffer.URL, content.document.characterSet)); + //} + //else + saveDocument(window.content.document, special); + }); + + vimperator.commands.add(["st[op]"], + "Stop loading", + function() { BrowserStop(); }); + + vimperator.commands.add(["vie[wsource]"], + "View source code of current document", + function (args, special) { - shortHelp: "Test command" - } - )); + var url = args || vimperator.buffer.URL; + if (special) // external editor + { + // TODO: make that a helper function + // TODO: save return value in v:shell_error + var newThread = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0); + var editor = vimperator.options["editor"]; + var args = editor.split(" "); // FIXME: too simple + if (args.length < 1) + { + vimperator.open("view-source:" + url) + vimperator.echoerr("no editor specified"); + return; + } + + var prog = args.shift(); + args.push(url) + vimperator.callFunctionInThread(newThread, vimperator.io.run, [prog, args, true]); + } + else + { + vimperator.open("view-source:" + url) + } + }); + + vimperator.commands.add(["zo[om]"], + "Set zoom value of current web page", + function (args, special) + { + var level; + + if (!args) + { + level = 100; + } + else if (/^\d+$/.test(args)) + { + level = parseInt(args, 10); + } + else if (/^[+-]\d+$/.test(args)) + { + if (special) + level = vimperator.buffer.fullZoom + parseInt(args, 10); + else + level = vimperator.buffer.textZoom + parseInt(args, 10); + + // relative args shouldn't take us out of range + if (level < 1) + level = 1; + if (level > 2000) + level = 2000; + } + else + { + vimperator.echoerr("E488: Trailing characters"); + return; + } + + if (special) + vimperator.buffer.fullZoom = level; + else + vimperator.buffer.textZoom = level; + }); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// @@ -1340,6 +1429,7 @@ vimperator.Marks = function () //{{{ /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + var modes = vimperator.config.browserModes || [vimperator.modes.NORMAL]; vimperator.mappings.add(modes, @@ -1362,6 +1452,92 @@ vimperator.Marks = function () //{{{ { flags: vimperator.Mappings.flags.ARGUMENT }); + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["delm[arks]"], + "Delete the specified marks", + function (args, special) + { + if (!special && !args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + if (special && args) + { + vimperator.echoerr("E474: Invalid argument"); + return; + } + var matches; + if (matches = args.match(/(?:(?:^|[^a-zA-Z0-9])-|-(?:$|[^a-zA-Z0-9])|[^a-zA-Z0-9 -]).*/)) + { + // NOTE: this currently differs from Vim's behavior which + // deletes any valid marks in the arg list, up to the first + // invalid arg, as well as giving the error message. + vimperator.echoerr("E475: Invalid argument: " + matches[0]); + return; + } + // check for illegal ranges - only allow a-z A-Z 0-9 + if (matches = args.match(/[a-zA-Z0-9]-[a-zA-Z0-9]/g)) + { + for (var i = 0; i < matches.length; i++) + { + var start = matches[i][0]; + var end = matches[i][2]; + if (/[a-z]/.test(start) != /[a-z]/.test(end) || + /[A-Z]/.test(start) != /[A-Z]/.test(end) || + /[0-9]/.test(start) != /[0-9]/.test(end) || + start > end) + { + vimperator.echoerr("E475: Invalid argument: " + args.match(new RegExp(matches[i] + ".*"))[0]); + return; + } + } + } + + vimperator.marks.remove(args, special); + }); + + vimperator.commands.add(["ma[rk]"], + "Mark current location within the web page", + function (args) + { + if (!args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + if (args.length > 1) + { + vimperator.echoerr("E488: Trailing characters"); + return; + } + if (!/[a-zA-Z]/.test(args)) + { + vimperator.echoerr("E191: Argument must be a letter or forward/backward quote"); + return; + } + + vimperator.marks.add(args); + }); + + vimperator.commands.add(["marks"], + "Show all location marks of current web page", + function (args) + { + // ignore invalid mark characters unless there are no valid mark chars + if (args && !/[a-zA-Z]/.test(args)) + { + vimperator.echoerr("E283: No marks matching \"" + args + "\""); + return; + } + + var filter = args.replace(/[^a-zA-Z]/g, ""); + vimperator.marks.list(filter); + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ diff --git a/content/commands.js b/content/commands.js index df40a17a..5c2e3c42 100644 --- a/content/commands.js +++ b/content/commands.js @@ -130,15 +130,6 @@ vimperator.Commands = function () //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - const OPTION_ANY = 0; // can be given no argument or an argument of any type, user is responsible - // for parsing the return value - const OPTION_NOARG = 1; - const OPTION_BOOL = 2; - const OPTION_STRING = 3; - const OPTION_INT = 4; - const OPTION_FLOAT = 5; - const OPTION_LIST = 6; - var exCommands = []; var lastRunCommand = ""; // updated whenever the users runs a command with :! @@ -148,7 +139,7 @@ vimperator.Commands = function () //{{{ // // "options" is an array [name, type, validator, completions] and could look like: // options = [[["-force"], OPTION_NOARG], - // [["-fullscreen"], OPTION_BOOL], + // [["-fullscreen", "-f"], OPTION_BOOL], // [["-language"], OPTION_STRING, validateFunc, ["perl", "ruby"]], // [["-speed"], OPTION_INT], // [["-acceleration"], OPTION_FLOAT], @@ -304,7 +295,7 @@ vimperator.Commands = function () //{{{ count++; // to compensate the "=" character } - else if (options[opt][1] != OPTION_NOARG && /\s/.test(sub[optname.length])) + else if (options[opt][1] != commandManager.OPTION_NOARG && /\s/.test(sub[optname.length])) { [count, arg] = getNextArg(sub.substr(optname.length + 1)); if (count == -1) @@ -329,14 +320,14 @@ vimperator.Commands = function () //{{{ { switch (options[opt][1]) // type { - case OPTION_NOARG: + case commandManager.OPTION_NOARG: if (arg != null) { vimperator.echoerr("No argument allowed for option: " + optname); return null; } break; - case OPTION_BOOL: + case commandManager.OPTION_BOOL: if (arg == "true" || arg == "1" || arg == "on") arg = true; else if (arg == "false" || arg == "0" || arg == "off") @@ -347,14 +338,14 @@ vimperator.Commands = function () //{{{ return null; } break; - case OPTION_STRING: + case commandManager.OPTION_STRING: if (arg == null) { vimperator.echoerr("Argument required for string option: " + optname); return null; } break; - case OPTION_INT: + case commandManager.OPTION_INT: arg = parseInt(arg, 10); if (isNaN(arg)) { @@ -362,7 +353,7 @@ vimperator.Commands = function () //{{{ return null; } break; - case OPTION_FLOAT: + case commandManager.OPTION_FLOAT: arg = parseFloat(arg); if (isNaN(arg)) { @@ -370,7 +361,7 @@ vimperator.Commands = function () //{{{ return null; } break; - case OPTION_LIST: + case commandManager.OPTION_LIST: if (arg == null) { vimperator.echoerr("Argument required for list option: " + optname); @@ -463,6 +454,22 @@ vimperator.Commands = function () //{{{ var commandManager = { + // FIXME: remove later, when our option handler is better + // Idea: If v.commands.add() specifies args or opts in extraInfo, don't call the function + // with args as a string, but already pass an object like: + // args = { -option: value, -anotheroption: true, arguments: [] } + getOption: function(opts, option, def) { return getOption(opts, option, def); }, + parseArgs: function(str, options) { return parseArgs(str, options); }, + + OPTION_ANY: 0, // can be given no argument or an argument of any type, + // caller is responsible for parsing the return value + OPTION_NOARG: 1, + OPTION_BOOL:2, + OPTION_STRING: 3, + OPTION_INT: 4, + OPTION_FLOAT: 5, + OPTION_LIST: 6, + __iterator__: function () { return commandsIterator(); @@ -588,105 +595,19 @@ vimperator.Commands = function () //{{{ ////////////////////// DEFAULT COMMANDS //////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + // move to vim.js: commandManager.addUserCommand(new vimperator.Command(["addo[ns]"], function () { vimperator.open("chrome://mozapps/content/extensions/extensions.xul", vimperator.NEW_TAB); }, { shortHelp: "Show available Browser Extensions and Themes" } )); - commandManager.addUserCommand(new vimperator.Command(["ba[ck]"], - function (args, special, count) - { - if (special) - vimperator.history.goToStart(); - else - { - if (args) - { - var sh = getWebNavigation().sessionHistory; - for (var i = sh.index - 1; i >= 0; i--) - { - if (sh.getEntryAtIndex(i, false).URI.spec == args) - { - getWebNavigation().gotoIndex(i); - return; - } - } - } - vimperator.history.stepTo(count > 0 ? -1 * count : -1); - } - }, - { - shortHelp: "Go back in the browser history", - completer: function (filter) - { - var sh = getWebNavigation().sessionHistory; - var completions = []; - for (var i = sh.index - 1; i >= 0; i--) - { - var entry = sh.getEntryAtIndex(i, false); - var url = entry.URI.spec; - var title = entry.title; - if (vimperator.completion.match([url, title], filter, false)) - completions.push([url, title]); - } - return [0, completions]; - } - } - )); commandManager.addUserCommand(new vimperator.Command(["beep"], function () { vimperator.beep(); }, { shortHelp: "Play a system beep" } )); - commandManager.addUserCommand(new vimperator.Command(["bma[rk]"], - function (args) - { - var res = parseArgs(args, this.args); - if (!res) - return; - - var url = res.args.length == 0 ? vimperator.buffer.URL : res.args[0]; - var title = getOption(res.opts, "-title", res.args.length == 0 ? vimperator.buffer.title : null); - if (!title) - title = url; - var keyword = getOption(res.opts, "-keyword", null); - var tags = getOption(res.opts, "-tags", []); - - if (vimperator.bookmarks.add(false, title, url, keyword, tags)) - { - var extra = ""; - if (title != url) - extra = " (" + title + ")"; - vimperator.echo("Added bookmark: " + url + extra, vimperator.commandline.FORCE_SINGLELINE); - } - else - vimperator.echoerr("Exxx: Could not add bookmark `" + title + "'", vimperator.commandline.FORCE_SINGLELINE); - }, - { - shortHelp: "Add a bookmark", - args: [[["-title", "-t"], OPTION_STRING], - [["-tags", "-T"], OPTION_LIST], - [["-keyword", "-k"], OPTION_STRING, function (arg) { return /\w/.test(arg); }]] - } - )); - commandManager.addUserCommand(new vimperator.Command(["bmarks"], - function (args, special) - { - var res = parseArgs(args, this.args); - if (!res) - return; - - var tags = getOption(res.opts, "-tags", []); - vimperator.bookmarks.list(res.args.join(" "), tags, special); - }, - { - shortHelp: "List or open multiple bookmarks", - completer: function (filter) { return [0, vimperator.bookmarks.get(filter)]; }, - args: [[["-tags", "-T"], OPTION_LIST]] - } - )); commandManager.addUserCommand(new vimperator.Command(["dia[log]"], function (args, special) { @@ -695,7 +616,7 @@ vimperator.Commands = function () //{{{ // copied (and tuned somebit) from browser.jar -> nsContextMenu.js var focusedWindow = document.commandDispatcher.focusedWindow; if (focusedWindow == window) - focusedWindow = content; + focusedWindow = content; var docCharset = null; if (focusedWindow) @@ -752,231 +673,9 @@ vimperator.Commands = function () //{{{ completer: function (filter) { return vimperator.completion.dialog(filter); } } )); - commandManager.addUserCommand(new vimperator.Command(["delbm[arks]"], - function (args, special) - { - var url = args; - if (!url) - url = vimperator.buffer.URL; - - var deletedCount = vimperator.bookmarks.remove(url); - vimperator.echo(deletedCount + " bookmark(s) with url `" + url + "' deleted", vimperator.commandline.FORCE_SINGLELINE); - }, - { - shortHelp: "Delete a bookmark", - completer: function (filter) { return [0, vimperator.bookmarks.get(filter)]; } - } - )); - commandManager.addUserCommand(new vimperator.Command(["cd", "chd[ir]"], - function (args) - { - if (!args) - args = "~"; - - if (vimperator.io.setCurrentDirectory(args)) - vimperator.echo(vimperator.io.getCurrentDirectory()); - }, - { - shortHelp: "Change the current directory", - completer: function (filter) { return vimperator.completion.file(filter, true); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["pw[d]"], - function (args) - { - if (args) - vimperator.echoerr("E488: Trailing characters"); - else - vimperator.echo(vimperator.io.getCurrentDirectory()); - }, - { - shortHelp: "Print the current directory name" - } - )); - commandManager.addUserCommand(new vimperator.Command(["com[mand]"], - function (args, special) - { - if (args) - { - var res = args.match(/^(\w+)(?:\s+(.+))?$/); - if (!res) - { - vimperator.echoerr("E182: Invalid command name"); - return false; - } - var [cmd, rep] = [res[1], res[2]] - } - - if (rep) - { - if (!vimperator.commands.addUserCommand(new vimperator.Command([cmd], function (args, special, count, modifiers) { eval(rep) }, { isUserCommand: rep } ), special)) - vimperator.echoerr("E174: Command already exists: add ! to replace it"); - } - else - { - var cmdlist = getUserCommands(cmd); - if (cmdlist.length > 0) - { - var str = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - ""; - for (var i = 0; i < cmdlist.length; i++) - str += ""; - str += "
NameArgsDefinition
" + cmdlist[i].name + "" + "*" + "" + cmdlist[i].isUserCommand + "
" - vimperator.commandline.echo(str, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } - else - vimperator.echo("No user-defined commands found"); - } - }, - { - shortHelp: "Lists and defines commands" /*, - args: [[["-nargs"], OPTION_STRING, function (arg) { return /^(0|1|\*|\?|\+)$/.test(arg); }], - [["-bang"], OPTION_NOARG], - [["-bar"], OPTION_NOARG]] */ - } - )); - commandManager.addUserCommand(new vimperator.Command(["delm[arks]"], - function (args, special) - { - if (!special && !args) - { - vimperator.echoerr("E471: Argument required"); - return; - } - if (special && args) - { - vimperator.echoerr("E474: Invalid argument"); - return; - } - var matches; - if (matches = args.match(/(?:(?:^|[^a-zA-Z0-9])-|-(?:$|[^a-zA-Z0-9])|[^a-zA-Z0-9 -]).*/)) - { - // TODO: this currently differs from Vim's behaviour which - // deletes any valid marks in the arg list, up to the first - // invalid arg, as well as giving the error message. Do we want - // to match this behaviour? - vimperator.echoerr("E475: Invalid argument: " + matches[0]); - return; - } - // check for illegal ranges - only allow a-z A-Z 0-9 - if (matches = args.match(/[a-zA-Z0-9]-[a-zA-Z0-9]/g)) - { - for (var i = 0; i < matches.length; i++) - { - var start = matches[i][0]; - var end = matches[i][2]; - if (/[a-z]/.test(start) != /[a-z]/.test(end) || - /[A-Z]/.test(start) != /[A-Z]/.test(end) || - /[0-9]/.test(start) != /[0-9]/.test(end) || - start > end) - { - vimperator.echoerr("E475: Invalid argument: " + args.match(new RegExp(matches[i] + ".*"))[0]); - return; - } - } - } - - vimperator.marks.remove(args, special); - }, - { - shortHelp: "Delete the specified marks" - } - - )); - commandManager.addUserCommand(new vimperator.Command(["delqm[arks]"], - function (args, special) - { - // TODO: finish arg parsing - we really need a proper way to do this. :) - if (!special && !args) - { - vimperator.echoerr("E471: Argument required"); - return; - } - if (special && args) - { - vimperator.echoerr("E474: Invalid argument"); - return; - } - - if (special) - vimperator.quickmarks.removeAll(); - else - vimperator.quickmarks.remove(args); - }, - { - shortHelp: "Delete the specified QuickMarks" - } - )); - commandManager.addUserCommand(new vimperator.Command(["downl[oads]", "dl"], - function () { vimperator.open("chrome://mozapps/content/downloads/downloads.xul", vimperator.NEW_TAB); }, - { - shortHelp: "Show progress of current downloads" - } - )); - - // TODO: move helper function somewhere else? - function argToString(arg, color) - { - if (!arg) - return ""; - - try - { - // TODO: move to vimperator.eval()? - // with (vimperator) means, vimperator is the default namespace "inside" eval - arg = eval("with(vimperator){" + arg + "}"); - } - catch (e) - { - vimperator.echoerr(e.toString()); - return null; - } - - if (typeof arg === "object") - arg = vimperator.util.objectToString(arg, color); - else if (typeof arg === "function") - arg = vimperator.util.escapeHTML(arg.toString()); - else if (typeof arg === "number" || typeof arg === "boolean") - arg = "" + arg; - else if (typeof arg === "undefined") - arg = "undefined"; - - return arg; - } - commandManager.addUserCommand(new vimperator.Command(["ec[ho]"], - function (args) - { - var res = argToString(args, true); - if (res != null) - vimperator.echo(res); - }, - { - shortHelp: "Display a string at the bottom of the window", - completer: function (filter) { return vimperator.completion.javascript(filter); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["echoe[rr]"], - function (args) - { - var res = argToString(args, false); - if (res != null) - vimperator.echoerr(res); - }, - { - shortHelp: "Display an error string at the bottom of the window", - completer: function (filter) { return vimperator.completion.javascript(filter); } - } - )); commandManager.addUserCommand(new vimperator.Command(["exe[cute]"], function (args) { - // TODO: :exec has some difficult semantics -> later - // var res = parseArgs(args, this.args); - // if (!res) - // return; - // - // vimperator.execute(res.args); - vimperator.execute(args); }, { @@ -1001,52 +700,6 @@ vimperator.Commands = function () //{{{ shortHelp: "Show help for Ex commands" } )); - commandManager.addUserCommand(new vimperator.Command(["fo[rward]", "fw"], - function (args, special, count) - { - if (special) - vimperator.history.goToEnd(); - else - { - if (args) - { - var sh = getWebNavigation().sessionHistory; - for (var i = sh.index + 1; i < sh.count; i++) - { - if (sh.getEntryAtIndex(i, false).URI.spec == args) - { - getWebNavigation().gotoIndex(i); - return; - } - } - } - vimperator.history.stepTo(count > 0 ? count : 1); - } - }, - { - shortHelp: "Go forward in the browser history", - completer: function (filter) - { - var sh = getWebNavigation().sessionHistory; - var completions = []; - for (var i = sh.index + 1; i < sh.count; i++) - { - var entry = sh.getEntryAtIndex(i, false); - var url = entry.URI.spec; - var title = entry.title; - if (vimperator.completion.match([url, title], filter, false)) - completions.push([url, title]); - } - return [0, completions]; - } - } - )); - commandManager.addUserCommand(new vimperator.Command(["ha[rdcopy]"], - function () { getBrowser().contentWindow.print(); }, - { - shortHelp: "Print current document" - } - )); commandManager.addUserCommand(new vimperator.Command(["h[elp]"], function (args, special, count, modifiers) { @@ -1093,13 +746,6 @@ vimperator.Commands = function () //{{{ completer: function (filter) { return vimperator.completion.help(filter); } } )); - commandManager.addUserCommand(new vimperator.Command(["hist[ory]", "hs"], - function (args, special) { vimperator.history.list(args, special); }, - { - shortHelp: "Show recently visited URLs", - completer: function (filter) { return [0, vimperator.history.get(filter)]; } - } - )); commandManager.addUserCommand(new vimperator.Command(["javas[cript]", "js"], function (args, special) { @@ -1234,7 +880,362 @@ vimperator.Commands = function () //{{{ shortHelp: "Sets or lists a variable" } )); - // code for abbreviations + commandManager.addUserCommand(new vimperator.Command(["q[uit]"], + function () { vimperator.tabs.remove(getBrowser().mCurrentTab, 1, false, 1); }, + { shortHelp: "Quit current tab" } + )); + // TODO: check for "tabs" feature + commandManager.addUserCommand(new vimperator.Command(["quita[ll]", "qa[ll]"], + function () { vimperator.quit(false); }, + { shortHelp: "Quit Vimperator", } + )); + commandManager.addUserCommand(new vimperator.Command(["res[tart]"], + function () { vimperator.restart(); }, + { shortHelp: "Force the browser to restart" } + )); + commandManager.addUserCommand(new vimperator.Command(["time"], + function (args, special, count) + { + try + { + if (count > 1) + { + var i = count; + var beforeTime = Date.now(); + + if (args && args[0] == ":") + { + while (i--) + vimperator.execute(args); + } + else + { + while (i--) + eval("with(vimperator){" + args + "}"); + } + + if (special) + return; + + var afterTime = Date.now(); + + if ((afterTime - beforeTime) / count >= 100) + { + var each = ((afterTime - beforeTime) / 1000.0 / count); + var eachUnits = "sec"; + } + else + { + var each = ((afterTime - beforeTime) / count); + var eachUnits = "msec"; + } + + if (afterTime - beforeTime >= 100) + { + var total = ((afterTime - beforeTime) / 1000.0); + var totalUnits = "sec"; + } + else + { + var total = (afterTime - beforeTime); + var totalUnits = "msec"; + } + + var str = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + "" + + "" + + "" + + "" + + "" + + "
Code execution summary
Executed:" + count + "times
Average time:" + each.toFixed(2) + "" + eachUnits + "
Total time:" + total.toFixed(2) + "" + totalUnits + "
"; + + vimperator.commandline.echo(str, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } + else + { + var beforeTime = Date.now(); + if (args && args[0] == ":") + vimperator.execute(args); + else + eval("with(vimperator){" + args + "}"); + + if (special) + return; + + var afterTime = Date.now(); + + if (afterTime - beforeTime >= 100) + vimperator.echo("Total time: " + ((afterTime - beforeTime) / 1000.0).toFixed(2) + " sec"); + else + vimperator.echo("Total time: " + (afterTime - beforeTime) + " msec"); + } + } + catch (e) + { + vimperator.echoerr(e); + } + }, + { + shortHelp: "Profile a piece of code or a command" + } + )); + commandManager.addUserCommand(new vimperator.Command(["unl[et]"], + function (args, special) + { + if (!args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + + var names = args.split(/ /); + if (typeof names == "string") names = [names]; + var length = names.length; + for (var i = 0, name = names[i]; i < length; name = names[++i]) + { + var reference = vimperator.variableReference(name); + if (!reference[0]) + { + if (!special) + vimperator.echoerr("E108: No such variable: " + name); + return; + } + + delete reference[0][reference[1]]; + } + }, + { + shortHelp: "Deletes a variable." + } + )); + commandManager.addUserCommand(new vimperator.Command(["ve[rsion]"], + function (args, special) + { + if (special) + vimperator.open("about:"); + else + vimperator.echo(":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + + "\nVimperator " + vimperator.version + " running on:\n" + navigator.userAgent); + }, + { + shortHelp: "Show version information" + } + )); + commandManager.addUserCommand(new vimperator.Command(["viu[sage]"], + function (args, special, count, modifiers) + { + var usage = ""; + for (let mapping in vimperator.mappings) + { + usage += ""; + } + usage += "
" + + vimperator.util.escapeHTML(mapping.names[0]) + "" + + vimperator.util.escapeHTML(mapping.shortHelp || "") + "
"; + + vimperator.echo(usage, vimperator.commandline.FORCE_MULTILINE); + }, + { + shortHelp: "Show help for normal mode commands" + } + )); + + // move to io.js: + commandManager.addUserCommand(new vimperator.Command(["cd", "chd[ir]"], + function (args) + { + if (!args) + args = "~"; + + if (vimperator.io.setCurrentDirectory(args)) + vimperator.echo(vimperator.io.getCurrentDirectory()); + }, + { + shortHelp: "Change the current directory", + completer: function (filter) { return vimperator.completion.file(filter, true); } + } + )); + commandManager.addUserCommand(new vimperator.Command(["pw[d]"], + function (args) + { + if (args) + vimperator.echoerr("E488: Trailing characters"); + else + vimperator.echo(vimperator.io.getCurrentDirectory()); + }, + { + shortHelp: "Print the current directory name" + } + )); + commandManager.addUserCommand(new vimperator.Command(["mkv[imperatorrc]"], + function (args, special) + { + // TODO: "E172: Only one file name allowed" + var filename; + if (args) + filename = args; + else + filename = (navigator.platform == "Win32") ? "~/_vimperatorrc" : "~/.vimperatorrc"; + + var file = vimperator.io.getFile(filename); + if (file.exists() && !special) + { + vimperator.echoerr("E189: \".vimperatorrc\" exists (add ! to override)"); + return; + } + + var line = "\" " + vimperator.version + "\n"; + line += "\" Mappings\n"; + + var mode = [[[vimperator.modes.NORMAL], ""], [[vimperator.modes.COMMAND_LINE], "c"], + [[vimperator.modes.INSERT, vimperator.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 vimperator.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 vimperator.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 vimperator.autocommands) + // line += "autocmd " + item + "\n"; + + line += "\n\" Abbreviations\n"; + for (var abbrCmd in vimperator.editor.abbreviations) + line += abbrCmd; + + // if (vimperator.events.getMapLeader() != "\\") + // line += "\nlet mapleader = \"" + vimperator.events.getMapLeader() + "\"\n"; + + // source a user .vimperatorrc file + line += "\nsource! " + filename + ".local\n"; + line += "\n\" vim: set ft=vimperator:"; + + vimperator.io.writeFile(file, line); + }, + { + shortHelp: "Write current key mappings and changed options to [file]" + } + )); + commandManager.addUserCommand(new vimperator.Command(["so[urce]"], + function (args, special) + { + // FIXME: implement proper filename quoting + //if (/[^\\]\s/.test(args)) + //{ + // vimperator.echoerr("E172: Only one file name allowed"); + // return; + //} + + vimperator.io.source(args, special); + }, + { + shortHelp: "Read Ex commands from {file}", + completer: function (filter) { return vimperator.completion.file(filter, true); } + } + )); + commandManager.addUserCommand(new vimperator.Command(["!", "run"], + 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 = vimperator.io.system(args); + if (output) + vimperator.echo(vimperator.util.escapeHTML(output)); + }, + { + shortHelp: "Run a command" + } + )); + + // TODO: move where? (commands.js would be fine, but timing issue) + commandManager.addUserCommand(new vimperator.Command(["com[mand]"], + function (args, special) + { + if (args) + { + var res = args.match(/^(\w+)(?:\s+(.+))?$/); + if (!res) + { + vimperator.echoerr("E182: Invalid command name"); + return false; + } + var [cmd, rep] = [res[1], res[2]] + } + + if (rep) + { + if (!vimperator.commands.addUserCommand(new vimperator.Command([cmd], function (args, special, count, modifiers) { eval(rep) }, { isUserCommand: rep } ), special)) + vimperator.echoerr("E174: Command already exists: add ! to replace it"); + } + else + { + var cmdlist = getUserCommands(cmd); + if (cmdlist.length > 0) + { + var str = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + + ""; + for (var i = 0; i < cmdlist.length; i++) + str += ""; + str += "
NameArgsDefinition
" + cmdlist[i].name + "" + "*" + "" + cmdlist[i].isUserCommand + "
" + vimperator.commandline.echo(str, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); + } + else + vimperator.echo("No user-defined commands found"); + } + }, + { + shortHelp: "Lists and defines commands" /*, + args: [[["-nargs"], OPTION_STRING, function (arg) { return /^(0|1|\*|\?|\+)$/.test(arg); }], + [["-bang"], OPTION_NOARG], + [["-bar"], OPTION_NOARG]] */ + } + )); + + // TODO: part of vimperator.js or vim.js? + commandManager.addUserCommand(new vimperator.Command(["o[pen]", "e[dit]"], + function (args, special) + { + if (args) + { + vimperator.open(args); + } + else + { + if (special) + BrowserReloadSkipCache(); + else + BrowserReload(); + } + }, + { + shortHelp: "Open one or more URLs in the current tab", + completer: function (filter) { return vimperator.completion.url(filter); } + } + )); + + // move to editor.js: - TODO: unify commandManager.addUserCommand(new vimperator.Command(["ab[breviate]"], function (args) { @@ -1317,6 +1318,8 @@ vimperator.Commands = function () //{{{ function (args) { vimperator.editor.removeAllAbbreviations("i"); }, { shortHelp: "Remove all abbreviations for Insert mode" } )); + + // move to v.AutoCommands: commandManager.addUserCommand(new vimperator.Command(["au[tocmd]"], function (args, special) { @@ -1363,6 +1366,8 @@ vimperator.Commands = function () //{{{ completer: function (filter) { return vimperator.completion.autocommands(filter); } } )); + + // move to events.js: commandManager.addUserCommand(new vimperator.Command(["macros"], function (arg) { @@ -1405,6 +1410,8 @@ vimperator.Commands = function () //{{{ completer: function (filter) { return vimperator.completion.macros(filter); } } )); + + // TODO: add helper method: addMappingCommand // 0 args -> list all maps // 1 arg -> list the maps starting with args // 2 args -> map arg1 to arg* @@ -1496,117 +1503,97 @@ vimperator.Commands = function () //{{{ shortHelp: "Remove all mappings (in insert mode)" } )); - commandManager.addUserCommand(new vimperator.Command(["ma[rk]"], + // TODO: remove duplication in :map + commandManager.addUserCommand(new vimperator.Command(["no[remap]"], + function (args) { map(args, [vimperator.modes.NORMAL], true); }, + { shortHelp: "Map the key sequence {lhs} to {rhs}" } + )); + // XXX: TODO: remove duplication in :cmap + commandManager.addUserCommand(new vimperator.Command(["cno[remap]"], + function (args) { map(args, [vimperator.modes.COMMAND_LINE], true); }, + { shortHelp: "Map the key sequence {lhs} to {rhs} (in command-line mode)" } + )); + commandManager.addUserCommand(new vimperator.Command(["ino[remap]"], + function (args) { map(args, [vimperator.modes.INSERT, vimperator.modes.TEXTAREA], true); }, + { shortHelp: "Map the key sequence {lhs} to {rhs} (in insert mode)" } + )); + commandManager.addUserCommand(new vimperator.Command(["unm[ap]"], function (args) { if (!args) { - vimperator.echoerr("E471: Argument required"); - return; - } - if (args.length > 1) - { - vimperator.echoerr("E488: Trailing characters"); - return; - } - if (!/[a-zA-Z]/.test(args)) - { - vimperator.echoerr("E191: Argument must be a letter or forward/backward quote"); + vimperator.echoerr("E474: Invalid argument"); return; } - vimperator.marks.add(args); + var lhs = args; + + if (vimperator.mappings.hasMap(vimperator.modes.NORMAL, lhs)) + vimperator.mappings.remove(vimperator.modes.NORMAL, lhs); + else + vimperator.echoerr("E31: No such mapping"); }, { - shortHelp: "Mark current location within the web page" + shortHelp: "Remove the mapping of {lhs}" } )); - commandManager.addUserCommand(new vimperator.Command(["marks"], + commandManager.addUserCommand(new vimperator.Command(["cunm[ap]"], function (args) { - // ignore invalid mark characters unless there are no valid mark chars - if (args && !/[a-zA-Z]/.test(args)) + if (!args) { - vimperator.echoerr("E283: No marks matching \"" + args + "\""); + vimperator.echoerr("E474: Invalid argument"); return; } - var filter = args.replace(/[^a-zA-Z]/g, ""); - vimperator.marks.list(filter); - }, - { - shortHelp: "Show all location marks of current web page" - } - )); - commandManager.addUserCommand(new vimperator.Command(["mkv[imperatorrc]"], - function (args, special) - { - // TODO: "E172: Only one file name allowed" - var filename; - if (args) - filename = args; + var lhs = args; + + if (vimperator.mappings.hasMap(vimperator.modes.COMMAND_LINE, lhs)) + vimperator.mappings.remove(vimperator.modes.COMMAND_LINE, lhs); else - filename = (navigator.platform == "Win32") ? "~/_vimperatorrc" : "~/.vimperatorrc"; - - var file = vimperator.io.getFile(filename); - if (file.exists() && !special) + vimperator.echoerr("E31: No such mapping"); + }, + { + shortHelp: "Remove the mapping of {lhs} (in command-line mode)" + } + )); + commandManager.addUserCommand(new vimperator.Command(["iunm[ap]"], + function (args) + { + if (!args) { - vimperator.echoerr("E189: \".vimperatorrc\" exists (add ! to override)"); + vimperator.echoerr("E474: Invalid argument"); return; } - var line = "\" " + vimperator.version + "\n"; - line += "\" Mappings\n"; + var lhs = args; + var flag = false; - var mode = [[[vimperator.modes.NORMAL], ""], [[vimperator.modes.COMMAND_LINE], "c"], - [[vimperator.modes.INSERT, vimperator.modes.TEXTAREA], "i"]]; - for (var y = 0; y < mode.length; y++) + if (vimperator.mappings.hasMap(vimperator.modes.INSERT, lhs)) { - // NOTE: names.length is always 1 on user maps. If that changes, also fix getUserIterator and v.m.list - for (var map in vimperator.mappings.getUserIterator(mode[y][0])) - line += mode[y][1] + (map.noremap ? "nore" : "") + "map " + map.names[0] + " " + map.rhs + "\n"; + vimperator.mappings.remove(vimperator.modes.INSERT, lhs); + flag = true; } - - line += "\n\" Options\n"; - for (var option in vimperator.options) + if (vimperator.mappings.hasMap(vimperator.modes.TEXTAREA, lhs)) { - // 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"; - } + vimperator.mappings.remove(vimperator.modes.TEXTAREA, lhs); + flag = true; } - - // :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 vimperator.autocommands) - // line += "autocmd " + item + "\n"; - - line += "\n\" Abbreviations\n"; - for (var abbrCmd in vimperator.editor.abbreviations) - line += abbrCmd; - - // if (vimperator.events.getMapLeader() != "\\") - // line += "\nlet mapleader = \"" + vimperator.events.getMapLeader() + "\"\n"; - - // source a user .vimperatorrc file - line += "\nsource! " + filename + ".local\n"; - line += "\n\" vim: set ft=vimperator:"; - - vimperator.io.writeFile(file, line); + if (!flag) + vimperator.echoerr("E31: No such mapping"); }, { - shortHelp: "Write current key mappings and changed options to [file]" + shortHelp: "Remove the mapping of {lhs} (in insert mode)" } )); + + // move to find.js: commandManager.addUserCommand(new vimperator.Command(["noh[lsearch]"], function (args) { vimperator.search.clear(); }, { shortHelp: "Remove the search highlighting" } )); + + // move to either events.js or vim.js? commandManager.addUserCommand(new vimperator.Command(["norm[al]"], function (args, special) { @@ -1622,48 +1609,13 @@ vimperator.Commands = function () //{{{ shortHelp: "Execute Normal mode commands" } )); - // TODO: remove duplication in :map - commandManager.addUserCommand(new vimperator.Command(["no[remap]"], - function (args) { map(args, [vimperator.modes.NORMAL], true); }, - { shortHelp: "Map the key sequence {lhs} to {rhs}" } - )); - // XXX: TODO: remove duplication in :cmap - commandManager.addUserCommand(new vimperator.Command(["cno[remap]"], - function (args) { map(args, [vimperator.modes.COMMAND_LINE], true); }, - { shortHelp: "Map the key sequence {lhs} to {rhs} (in command-line mode)" } - )); - commandManager.addUserCommand(new vimperator.Command(["ino[remap]"], - function (args) { map(args, [vimperator.modes.INSERT, vimperator.modes.TEXTAREA], true); }, - { shortHelp: "Map the key sequence {lhs} to {rhs} (in insert mode)" } - )); - commandManager.addUserCommand(new vimperator.Command(["o[pen]", "e[dit]"], - function (args, special) - { - if (args) - { - vimperator.open(args); - } - else - { - if (special) - BrowserReloadSkipCache(); - else - BrowserReload(); - } - }, - { - shortHelp: "Open one or more URLs in the current tab", - completer: function (filter) { return vimperator.completion.url(filter); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["pa[geinfo]"], - function () { vimperator.buffer.showPageInfo(true); }, - { shortHelp: "Show various page information" } - )); + // TODO: remove/change preview window commandManager.addUserCommand(new vimperator.Command(["pc[lose]"], function () { vimperator.previewwindow.hide(); }, { shortHelp: "Close preview window on bottom of screen" } )); + + // move where? commandManager.addUserCommand(new vimperator.Command(["pref[erences]", "prefs"], function (args, special, count, modifiers) { @@ -1692,91 +1644,14 @@ vimperator.Commands = function () //{{{ shortHelp: "Show Browser Preferences" } )); - commandManager.addUserCommand(new vimperator.Command(["qma[rk]"], - function (args) - { - if (!args) - { - vimperator.echoerr("E471: Argument required"); - return; - } - var matches = args.match(/^([a-zA-Z0-9])(?:\s+(.+))?$/); - if (!matches) - vimperator.echoerr("E488: Trailing characters"); - else if (!matches[2]) - vimperator.quickmarks.add(matches[1], vimperator.buffer.URL); - else - vimperator.quickmarks.add(matches[1], matches[2]); - }, - { - shortHelp: "Mark a URL with a letter for quick access" - } - )); - commandManager.addUserCommand(new vimperator.Command(["qmarks"], - function (args) - { - // ignore invalid mark characters unless there are no valid mark chars - if (args && !/[a-zA-Z0-9]/.test(args)) - { - vimperator.echoerr("E283: No QuickMarks matching \"" + args + "\""); - return; - } - - var filter = args.replace(/[^a-zA-Z0-9]/g, ""); - vimperator.quickmarks.list(filter); - }, - { - shortHelp: "Show all QuickMarks" - } - )); - commandManager.addUserCommand(new vimperator.Command(["q[uit]"], - function () { vimperator.tabs.remove(getBrowser().mCurrentTab, 1, false, 1); }, - { shortHelp: "Quit current tab" } - )); - commandManager.addUserCommand(new vimperator.Command(["quita[ll]", "qa[ll]"], - function () { vimperator.quit(false); }, - { shortHelp: "Quit Vimperator", } - )); - commandManager.addUserCommand(new vimperator.Command(["redr[aw]"], - function () - { - var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). - getInterface(Components.interfaces.nsIDOMWindowUtils); - wu.redraw(); - }, - { shortHelp: "Redraw the screen", } - )); - commandManager.addUserCommand(new vimperator.Command(["re[load]"], - function (args, special) { vimperator.tabs.reload(getBrowser().mCurrentTab, special); }, - { shortHelp: "Reload current page" } - )); + // move to tabs.js commandManager.addUserCommand(new vimperator.Command(["reloada[ll]"], function (args, special) { vimperator.tabs.reloadAll(special); }, { shortHelp: "Reload all pages" } )); - commandManager.addUserCommand(new vimperator.Command(["res[tart]"], - function () { vimperator.restart(); }, - { shortHelp: "Force the browser to restart" } - )); - commandManager.addUserCommand(new vimperator.Command(["sav[eas]", "w[rite]"], - function (args, special) - { - var file = vimperator.io.getFile(args || ""); - // we always want to save that link relative to the current working directory - vimperator.options.setPref("browser.download.lastDir", vimperator.io.getCurrentDirectory()); - //if (args) - //{ - // saveURL(vimperator.buffer.URL, args, null, true, special, // special == skipPrompt - // makeURI(vimperator.buffer.URL, content.document.characterSet)); - //} - //else - saveDocument(window.content.document, special); - }, - { - shortHelp: "Save current web page to disk" - } - )); + + // move where? commandManager.addUserCommand(new vimperator.Command(["se[t]"], // TODO: support setting multiple options at once function (args, special, count, modifiers) @@ -2027,324 +1902,7 @@ vimperator.Commands = function () //{{{ completer: function (filter, special) { return vimperator.completion.option(filter, special); } } )); - // TODO: sclose instead? - commandManager.addUserCommand(new vimperator.Command(["sbcl[ose]"], - function (args) - { - if (args) - { - vimperator.echoerr("E488: Trailing characters"); - return; - } - - if (document.getElementById("sidebar-box").hidden == false) - toggleSidebar(); - }, - { - shortHelp: "Close the sidebar window" - } - )); - // TODO: sopen instead? Separate :sidebar from :sbopen and make them behave - // more like :cw, :cope etc - commandManager.addUserCommand(new vimperator.Command(["sideb[ar]", "sb[ar]", "sbope[n]"], - function (args) - { - if (!args) - { - vimperator.echoerr("E471: Argument required"); - return; - } - - // do nothing if the requested sidebar is already open - if (document.getElementById("sidebar-title").value == args) - { - document.getElementById("sidebar-box").contentWindow.focus(); - return; - } - - var menu = document.getElementById("viewSidebarMenu"); - - for (var i = 0; i < menu.childNodes.length; i++) - { - if (menu.childNodes[i].label == args) - { - eval(menu.childNodes[i].getAttribute("oncommand")); - break; - } - } - }, - { - shortHelp: "Open the sidebar window", - completer: function (filter) { return vimperator.completion.sidebar(filter); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["so[urce]"], - function (args, special) - { - // FIXME: implement proper filename quoting - //if (/[^\\]\s/.test(args)) - //{ - // vimperator.echoerr("E172: Only one file name allowed"); - // return; - //} - - vimperator.io.source(args, special); - }, - { - shortHelp: "Read Ex commands from {file}", - completer: function (filter) { return vimperator.completion.file(filter, true); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["st[op]"], - function() { BrowserStop(); }, - { shortHelp: "Stop loading" } - )); - commandManager.addUserCommand(new vimperator.Command(["time"], - function (args, special, count) - { - try - { - if (count > 1) - { - var i = count; - var beforeTime = Date.now(); - - if (args && args[0] == ":") - { - while (i--) - vimperator.execute(args); - } - else - { - while (i--) - eval("with(vimperator){" + args + "}"); - } - - if (special) - return; - - var afterTime = Date.now(); - - if ((afterTime - beforeTime) / count >= 100) - { - var each = ((afterTime - beforeTime) / 1000.0 / count); - var eachUnits = "sec"; - } - else - { - var each = ((afterTime - beforeTime) / count); - var eachUnits = "msec"; - } - - if (afterTime - beforeTime >= 100) - { - var total = ((afterTime - beforeTime) / 1000.0); - var totalUnits = "sec"; - } - else - { - var total = (afterTime - beforeTime); - var totalUnits = "msec"; - } - - var str = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + - "" + - "" + - "" + - "" + - "" + - "
Code execution summary
Executed:" + count + "times
Average time:" + each.toFixed(2) + "" + eachUnits + "
Total time:" + total.toFixed(2) + "" + totalUnits + "
"; - - vimperator.commandline.echo(str, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); - } - else - { - var beforeTime = Date.now(); - if (args && args[0] == ":") - vimperator.execute(args); - else - eval("with(vimperator){" + args + "}"); - - if (special) - return; - - var afterTime = Date.now(); - - if (afterTime - beforeTime >= 100) - vimperator.echo("Total time: " + ((afterTime - beforeTime) / 1000.0).toFixed(2) + " sec"); - else - vimperator.echo("Total time: " + (afterTime - beforeTime) + " msec"); - } - } - catch (e) - { - vimperator.echoerr(e); - } - }, - { - shortHelp: "Profile a piece of code or a command" - } - )); - commandManager.addUserCommand(new vimperator.Command(["unl[et]"], - function (args, special) - { - if (!args) - { - vimperator.echoerr("E471: Argument required"); - return; - } - - var names = args.split(/ /); - if (typeof names == "string") names = [names]; - var length = names.length; - for (var i = 0, name = names[i]; i < length; name = names[++i]) - { - var reference = vimperator.variableReference(name); - if (!reference[0]) - { - if (!special) - vimperator.echoerr("E108: No such variable: " + name); - return; - } - - delete reference[0][reference[1]]; - } - }, - { - shortHelp: "Deletes a variable." - } - )); - commandManager.addUserCommand(new vimperator.Command(["unm[ap]"], - function (args) - { - if (!args) - { - vimperator.echoerr("E474: Invalid argument"); - return; - } - - var lhs = args; - - if (vimperator.mappings.hasMap(vimperator.modes.NORMAL, lhs)) - vimperator.mappings.remove(vimperator.modes.NORMAL, lhs); - else - vimperator.echoerr("E31: No such mapping"); - }, - { - shortHelp: "Remove the mapping of {lhs}" - } - )); - commandManager.addUserCommand(new vimperator.Command(["cunm[ap]"], - function (args) - { - if (!args) - { - vimperator.echoerr("E474: Invalid argument"); - return; - } - - var lhs = args; - - if (vimperator.mappings.hasMap(vimperator.modes.COMMAND_LINE, lhs)) - vimperator.mappings.remove(vimperator.modes.COMMAND_LINE, lhs); - else - vimperator.echoerr("E31: No such mapping"); - }, - { - shortHelp: "Remove the mapping of {lhs} (in command-line mode)" - } - )); - commandManager.addUserCommand(new vimperator.Command(["iunm[ap]"], - function (args) - { - if (!args) - { - vimperator.echoerr("E474: Invalid argument"); - return; - } - - var lhs = args; - var flag = false; - - if (vimperator.mappings.hasMap(vimperator.modes.INSERT, lhs)) - { - vimperator.mappings.remove(vimperator.modes.INSERT, lhs); - flag = true; - } - if (vimperator.mappings.hasMap(vimperator.modes.TEXTAREA, lhs)) - { - vimperator.mappings.remove(vimperator.modes.TEXTAREA, lhs); - flag = true; - } - if (!flag) - vimperator.echoerr("E31: No such mapping"); - }, - { - shortHelp: "Remove the mapping of {lhs} (in insert mode)" - } - )); - commandManager.addUserCommand(new vimperator.Command(["ve[rsion]"], - function (args, special) - { - if (special) - vimperator.open("about:"); - else - vimperator.echo(":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + - "\nVimperator " + vimperator.version + " running on:\n" + navigator.userAgent); - }, - { - shortHelp: "Show version information" - } - )); - commandManager.addUserCommand(new vimperator.Command(["vie[wsource]"], - function (args, special) - { - var url = args || vimperator.buffer.URL; - if (special) // external editor - { - // TODO: make that a helper function - // TODO: save return value in v:shell_error - var newThread = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0); - var editor = vimperator.options["editor"]; - var args = editor.split(" "); // FIXME: too simple - if (args.length < 1) - { - vimperator.open("view-source:" + url) - vimperator.echoerr("no editor specified"); - return; - } - - var prog = args.shift(); - args.push(url) - vimperator.callFunctionInThread(newThread, vimperator.io.run, [prog, args, true]); - } - else - { - vimperator.open("view-source:" + url) - } - }, - { - shortHelp: "View source code of current document" - } - )); - commandManager.addUserCommand(new vimperator.Command(["viu[sage]"], - function (args, special, count, modifiers) - { - var usage = ""; - for (let mapping in vimperator.mappings) - { - usage += ""; - } - usage += "
" + - vimperator.util.escapeHTML(mapping.names[0]) + "" + - vimperator.util.escapeHTML(mapping.shortHelp || "") + "
"; - - vimperator.echo(usage, vimperator.commandline.FORCE_MULTILINE); - }, - { - shortHelp: "Show help for normal mode commands" - } - )); + // TODO: check for v.has("windows") commandManager.addUserCommand(new vimperator.Command(["winc[lose]", "wc[lose]"], function (args) { window.close(); }, { shortHelp: "Close window" } @@ -2361,70 +1919,11 @@ vimperator.Commands = function () //{{{ shortHelp: "Open one or more URLs in a new window" } )); + // TODO: check for v.has("session")? commandManager.addUserCommand(new vimperator.Command(["wqa[ll]", "wq", "xa[ll]"], function () { vimperator.quit(true); }, { shortHelp: "Save the session and quit" } )); - commandManager.addUserCommand(new vimperator.Command(["zo[om]"], - function (args, special) - { - var level; - - if (!args) - { - level = 100; - } - else if (/^\d+$/.test(args)) - { - level = parseInt(args, 10); - } - else if (/^[+-]\d+$/.test(args)) - { - if (special) - level = vimperator.buffer.fullZoom + parseInt(args, 10); - else - level = vimperator.buffer.textZoom + parseInt(args, 10); - - // relative args shouldn't take us out of range - if (level < 1) - level = 1; - if (level > 2000) - level = 2000; - } - else - { - vimperator.echoerr("E488: Trailing characters"); - return; - } - - if (special) - vimperator.buffer.fullZoom = level; - else - vimperator.buffer.textZoom = level; - }, - { - shortHelp: "Set zoom value of current web page" - } - )); - commandManager.addUserCommand(new vimperator.Command(["!", "run"], - 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 = vimperator.io.system(args); - if (output) - vimperator.echo(vimperator.util.escapeHTML(output)); - }, - { - shortHelp: "Run a command" - } - )); //}}} return commandManager; diff --git a/content/tabs.js b/content/tabs.js index c9671893..b4d84b0b 100644 --- a/content/tabs.js +++ b/content/tabs.js @@ -26,12 +26,6 @@ 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 *****/ -/** - * provides functions for working with tabs - * XXX: ATTENTION: We are planning to move to the FUEL API once we switch to - * Firefox 3.0, then this class should go away and their tab methods should be used - * @deprecated - */ vimperator.Tabs = function () //{{{ { //////////////////////////////////////////////////////////////////////////////// @@ -139,6 +133,7 @@ vimperator.Tabs = function () //{{{ /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// MAPPINGS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + vimperator.mappings.add([vimperator.modes.NORMAL], ["b"], "Open a prompt to switch buffers", function () { vimperator.commandline.open(":", "buffer! ", vimperator.modes.EX); }); @@ -213,9 +208,11 @@ vimperator.Tabs = function () //{{{ vimperator.tabs.select(index); }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// COMMANDS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + vimperator.commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"], "Delete current buffer", function (args, special, count) @@ -394,7 +391,6 @@ vimperator.Tabs = function () //{{{ }); - /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ diff --git a/content/ui.js b/content/ui.js index 1ad7afef..6809449f 100644 --- a/content/ui.js +++ b/content/ui.js @@ -238,6 +238,36 @@ vimperator.CommandLine = function () //{{{ multilineInputWidget.setAttribute("rows", lines.toString()); } + // used for the :echo[err] commands + function echoArgumentToString(arg, useColor) + { + if (!arg) + return ""; + + try + { + // TODO: move to vimperator.eval()? + // with (vimperator) means, vimperator is the default namespace "inside" eval + arg = eval("with(vimperator){" + arg + "}"); + } + catch (e) + { + vimperator.echoerr(e.toString()); + return null; + } + + if (typeof arg === "object") + arg = vimperator.util.objectToString(arg, useColor); + else if (typeof arg === "function") + arg = vimperator.util.escapeHTML(arg.toString()); + else if (typeof arg === "number" || typeof arg === "boolean") + arg = "" + arg; + else if (typeof arg === "undefined") + arg = "undefined"; + + return arg; + } + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// OPTIONS ///////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -287,6 +317,29 @@ vimperator.CommandLine = function () //{{{ ["", ""], "Expand command line abbreviation", function () { vimperator.editor.expandAbbreviation("c"); }); + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["ec[ho]"], + "Display a string at the bottom of the window", + function (args) + { + var res = echoArgumentToString(args, true); + if (res != null) + vimperator.echo(res); + }, + { completer: function (filter) { return vimperator.completion.javascript(filter); } }); + + vimperator.commands.add(["echoe[rr]"], + "Display an error string at the bottom of the window", + function (args) + { + var res = echoArgumentToString(args, false); + if (res != null) + vimperator.echoerr(res); + }, + { completer: function (filter) { return vimperator.completion.javascript(filter); } }); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// diff --git a/content/vim.js b/content/vim.js index dc3345e1..b02ece67 100644 --- a/content/vim.js +++ b/content/vim.js @@ -114,6 +114,11 @@ const vimperator = (function () //{{{ function () { vimperator.quit(true); }); } + function addCommands() + { + + } + // initially hide all GUI, it is later restored unless the user has :set go= or something // similar in his config function hideGUI() diff --git a/content/vimperator.js b/content/vimperator.js index c034cb21..8a43d5f7 100644 --- a/content/vimperator.js +++ b/content/vimperator.js @@ -32,7 +32,7 @@ vimperator.config = { hostApplication: "Firefox", /*** optional options, there are checked for existance and a fallback provided ***/ - features: ["bookmarks", "history", "marks", "quickmarks", "hints", "tabs"], + features: ["bookmarks", "history", "marks", "quickmarks", "hints", "tabs", "windows"], dialogs: [], guioptions: { m: ["toolbar-menubar"], T: ["nav-bar"], b: ["PersonalToolbar"] }, @@ -61,6 +61,10 @@ vimperator.config = { vimperator.open(matches[1] + newNum + matches[3]); } + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// MAPPINGS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + vimperator.mappings.add([vimperator.modes.NORMAL], ["y"], "Yank current location to the clipboard", function () { vimperator.copyToClipboard(vimperator.buffer.URL, true); }); @@ -167,6 +171,69 @@ vimperator.config = { vimperator.mappings.add([vimperator.modes.NORMAL], [""], "Redraw the screen", function () { vimperator.commands.redraw(); }); + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["downl[oads]", "dl"], + "Show progress of current downloads", + function () { vimperator.open("chrome://mozapps/content/downloads/downloads.xul", vimperator.NEW_TAB); }); + + vimperator.commands.add(["redr[aw]"], + "Redraw the screen", + function () + { + var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + wu.redraw(); + }); + + // TODO: move sidebar commands to ui.js? + vimperator.commands.add(["sbcl[ose]"], + "Close the sidebar window", + function (args) + { + if (args) + { + vimperator.echoerr("E488: Trailing characters"); + return; + } + + if (document.getElementById("sidebar-box").hidden == false) + toggleSidebar(); + }); + + vimperator.commands.add(["sideb[ar]", "sb[ar]", "sbope[n]"], + "Open the sidebar window", + function (args) + { + if (!args) + { + vimperator.echoerr("E471: Argument required"); + return; + } + + // do nothing if the requested sidebar is already open + if (document.getElementById("sidebar-title").value == args) + { + document.getElementById("sidebar-box").contentWindow.focus(); + return; + } + + var menu = document.getElementById("viewSidebarMenu"); + + for (var i = 0; i < menu.childNodes.length; i++) + { + if (menu.childNodes[i].label == args) + { + eval(menu.childNodes[i].getAttribute("oncommand")); + break; + } + } + }, + { completer: function (filter) { return vimperator.completion.sidebar(filter); } }); + } }