diff --git a/TODO b/TODO index c84479b8..0955bde0 100644 --- a/TODO +++ b/TODO @@ -22,6 +22,8 @@ FEATURES: yd=yank domain name, yt=yank title, yw=yank current word, yf=yank filename, (other things to yank?) 8 all search commands should start searching from the top of the visible viewport 8 :bdelete full_url and :bdelete! filter should delete all tabs matching filter or full_url +7 adaptive learning for tab-completions + (https://bugzilla.mozilla.org/show_bug.cgi?id=395739 could help) 7 use ctrl-n/p in insert mode for word completion 7 implement LocationList window, and get rid off/change PreviewWindow to be a real preview window being able to display html pages diff --git a/content/buffer.js b/content/buffer.js index 3683edfa..431b90c6 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -1318,6 +1318,26 @@ vimperator.Buffer = function () //{{{ if (!retVal) vimperator.beep(); + }, + + viewSelectionSource: function() + { + // copied (and tuned somebit) from browser.jar -> nsContextMenu.js + var focusedWindow = document.commandDispatcher.focusedWindow; + if (focusedWindow == window) + focusedWindow = content; + + var docCharset = null; + if (focusedWindow) + docCharset = "charset=" + focusedWindow.document.characterSet; + + var reference = null; + reference = focusedWindow.getSelection(); + + var docUrl = null; + window.openDialog("chrome://global/content/viewPartialSource.xul", + "_blank", "scrollbars,resizable,chrome,dialog=no", + docUrl, docCharset, reference, "selection"); } }; //}}} diff --git a/content/commands.js b/content/commands.js index 5c2e3c42..368f8ea8 100644 --- a/content/commands.js +++ b/content/commands.js @@ -588,7 +588,6 @@ vimperator.Commands = function () //{{{ return matches; } - }; /////////////////////////////////////////////////////////////////////////////}}} @@ -596,198 +595,7 @@ vimperator.Commands = function () //{{{ /////////////////////////////////////////////////////////////////////////////{{{ // 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(["beep"], - function () { vimperator.beep(); }, - { - shortHelp: "Play a system beep" - } - )); - commandManager.addUserCommand(new vimperator.Command(["dia[log]"], - function (args, special) - { - function viewPartialSource() - { - // copied (and tuned somebit) from browser.jar -> nsContextMenu.js - var focusedWindow = document.commandDispatcher.focusedWindow; - if (focusedWindow == window) - focusedWindow = content; - var docCharset = null; - if (focusedWindow) - docCharset = "charset=" + focusedWindow.document.characterSet; - - var reference = null; - reference = focusedWindow.getSelection(); - - var docUrl = null; - window.openDialog("chrome://global/content/viewPartialSource.xul", - "_blank", "scrollbars,resizable,chrome,dialog=no", - docUrl, docCharset, reference, "selection"); - } - - try - { - switch (args) - { - case "about": openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); break; - case "addbookmark": PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksRootId); break; - case "addons": BrowserOpenAddonsMgr(); break; - case "bookmarks": openDialog("chrome://browser/content/bookmarks/bookmarksPanel.xul", "Bookmarks", "dialog,centerscreen,width=600,height=600"); break; - case "checkupdates": checkForUpdates(); break; - case "cleardata": Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); break; - case "console": toJavaScriptConsole(); break; - case "customizetoolbar": BrowserCustomizeToolbar(); break; - case "dominspector": inspectDOMDocument(content.document); break; - case "downloads": toOpenWindowByType('Download:Manager', 'chrome://mozapps/content/downloads/downloads.xul', 'chrome,dialog=no,resizable'); break; - case "history": openDialog("chrome://browser/content/history/history-panel.xul", "History", "dialog,centerscreen,width=600,height=600"); break; - case "import": BrowserImport(); break; - case "openfile": BrowserOpenFileWindow(); break; - case "pageinfo": BrowserPageInfo(); break; - case "pagesource": BrowserViewSourceOfDocument(content.document); break; - case "places": PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); break; - case "preferences": openPreferences(); break; - case "printpreview": PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); break; - case "print": PrintUtils.print(); break; - case "printsetup": PrintUtils.showPageSetup(); break; - case "saveframe": saveFrameDocument(); break; - case "savepage": saveDocument(window.content.document); break; - case "searchengines": openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); break; - case "selectionsource": viewPartialSource(); break; - case "": vimperator.echoerr("E474: Invalid argument"); break; - default: vimperator.echoerr("Dialog '" + args + "' not available"); - } - } - catch (e) - { - vimperator.echoerr("Error opening '" + args + "': " + e); - } - }, - { - shortHelp: "Open a Firefox dialog", - completer: function (filter) { return vimperator.completion.dialog(filter); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["exe[cute]"], - function (args) - { - vimperator.execute(args); - }, - { - shortHelp: "Execute the string that results from the evaluation of {expr1} as an Ex command." - } - )); - commandManager.addUserCommand(new vimperator.Command(["exu[sage]"], - function (args, special, count, modifiers) - { - var usage = ""; - for (let command in vimperator.commands) - { - usage += ""; - } - usage += "
:" + - vimperator.util.escapeHTML(command.name) + "" + - vimperator.util.escapeHTML(command.shortHelp) + "
"; - - vimperator.echo(usage, vimperator.commandline.FORCE_MULTILINE); - }, - { - shortHelp: "Show help for Ex commands" - } - )); - commandManager.addUserCommand(new vimperator.Command(["h[elp]"], - function (args, special, count, modifiers) - { - function jumpToTag(file, tag) - { - vimperator.open("chrome://" + vimperator.config.name.toLowerCase() + "/locale/" + file); - setTimeout(function() { - var elem = vimperator.buffer.getElement('@class="tag" and text()="' + tag + '"'); - if (elem) - window.content.scrollTo(0, elem.getBoundingClientRect().top - 10); // 10px context - else - dump('no element: ' + '@class="tag" and text()="' + tag + '"\n' ); - }, 200); - } - - if (!args) - { - vimperator.open("chrome://" + vimperator.config.name.toLowerCase() + "/locale/intro.html"); - return; - } - - var [, items] = vimperator.completion.help(args); - var partialMatch = -1; - for (var i = 0; i < items.length; i++) - { - if (items[i][0] == args) - { - jumpToTag(items[i][1], items[i][0]); - return; - } - else if (partialMatch == -1 && items[i][0].indexOf(args) > -1) - { - partialMatch = i; - } - } - - if (partialMatch > -1) - jumpToTag(items[partialMatch][1], items[partialMatch][0]); - else - vimperator.echoerr("E149: Sorry, no help for " + args); - }, - { - shortHelp: "Display help", - completer: function (filter) { return vimperator.completion.help(filter); } - } - )); - commandManager.addUserCommand(new vimperator.Command(["javas[cript]", "js"], - function (args, special) - { - if (special) // open javascript console - vimperator.open("chrome://global/content/console.xul", vimperator.NEW_TAB); - else - { - // check for a heredoc - var matches = args.match(/(.*)<<\s*([^\s]+)$/); - if (matches && matches[2]) - { - vimperator.commandline.inputMultiline(new RegExp("^" + matches[2] + "$", "m"), - function (code) - { - try - { - eval(matches[1] + "\n" + code); - } - catch (e) - { - vimperator.echoerr(e.name + ": " + e.message); - } - }); - } - else // single line javascript code - { - try - { - eval("with(vimperator){" + args + "}"); - } - catch (e) - { - vimperator.echoerr(e.name + ": " + e.message); - } - } - } - }, - { - shortHelp: "Run any JavaScript command through eval()", - completer: function (filter) { return vimperator.completion.javascript(filter); } - } - )); commandManager.addUserCommand(new vimperator.Command(["let"], function (args) { @@ -1041,133 +849,6 @@ vimperator.Commands = function () //{{{ )); // 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]"], @@ -1615,293 +1296,12 @@ vimperator.Commands = function () //{{{ { shortHelp: "Close preview window on bottom of screen" } )); - // move where? - commandManager.addUserCommand(new vimperator.Command(["pref[erences]", "prefs"], - function (args, special, count, modifiers) - { - if (!args) - { - // TODO: copy these snippets to more function which should work with :tab xxx - if (modifiers && modifiers.inTab) - { - vimperator.open(special ? "about:config" : - "chrome://browser/content/preferences/preferences.xul", vimperator.NEW_TAB); - } - else - { - if (special) // open firefox settings gui dialog - vimperator.open("about:config", vimperator.CURRENT_TAB); - else - openPreferences(); - } - } - else - { - vimperator.echoerr("E488: Trailing characters"); - } - }, - { - shortHelp: "Show Browser Preferences" - } - )); - // move to tabs.js commandManager.addUserCommand(new vimperator.Command(["reloada[ll]"], function (args, special) { vimperator.tabs.reloadAll(special); }, { shortHelp: "Reload all pages" } )); - // move where? - commandManager.addUserCommand(new vimperator.Command(["se[t]"], - // TODO: support setting multiple options at once - function (args, special, count, modifiers) - { - if (special) - { - var onlyNonDefault = false; - if (!args) - { - args = "all"; - onlyNonDefault = true; - } - // 1 2 3 4 5 - var matches = args.match(/^\s*?([a-zA-Z0-9\.\-_{}]+)([?&!])?\s*(([+-^]?)=(.*))?\s*$/); - var name = matches[1]; - var reset = false; - var invertBoolean = false; - - if (matches[2] == "&") - reset = true; - else if (matches[2] == "!") - invertBoolean = true; - - if (name == "all" && reset) - vimperator.echoerr("You can't reset all the firefox options, it could make your browser unusable."); - else if (name == "all") - vimperator.options.listPrefs(onlyNonDefault, ""); - else if (reset) - vimperator.options.resetPref(name); - else if (invertBoolean) - vimperator.options.invertPref(name); - else if (matches[3]) - { - var value = matches[5]; - switch (value) - { - case undefined: - value = ""; - break; - case "true": - value = true; - break; - case "false": - value = false; - break; - default: - var valueInt = parseInt(value, 10); - if (!isNaN(valueInt)) - value = valueInt; - } - vimperator.options.setPref(name, value); - } - else - { - vimperator.options.listPrefs(onlyNonDefault, name); - } - return; - } - - var onlyNonDefault = false; // used for :set to print non-default options - if (!args) - { - args = "all"; - onlyNonDefault = true; - } - - // 1 2 3 4 5 6 - var matches = args.match(/^\s*(no|inv)?([a-z]+)([?&!])?\s*(([+-^]?)=(.*))?\s*$/); - if (!matches) - { - vimperator.echoerr("E518: Unknown option: " + args); - return; - } - - var unsetBoolean = false; - if (matches[1] == "no") - unsetBoolean = true; - - var name = matches[2]; - var all = false; - if (name == "all") - all = true; - - var option = vimperator.options.get(name); - if (!option && !all) - { - vimperator.echoerr("E518: Unknown option: " + args); - return; - } - - var valueGiven = !!matches[4]; - - var get = false; - if (all || matches[3] == "?" || (option.type != "boolean" && !valueGiven)) - get = true; - - var reset = false; - if (matches[3] == "&") - reset = true; - - var invertBoolean = false; - if (matches[1] == "inv" || matches[3] == "!") - invertBoolean = true; - - var operator = matches[5]; - - var value = matches[6]; - if (value === undefined) - value = ""; - - // reset a variable to its default value - if (reset) - { - if (all) - { - for (let option in vimperator.options) - option.reset(); - } - else - { - option.reset(); - } - } - // read access - else if (get) - { - if (all) - { - vimperator.options.list(onlyNonDefault); - } - else - { - if (option.type == "boolean") - vimperator.echo((option.value ? " " : "no") + option.name); - else - vimperator.echo(" " + option.name + "=" + option.value); - } - } - // write access - // NOTE: the behaviour is generally Vim compatible but could be - // improved. i.e. Vim's behaviour is pretty sloppy to no real - // benefit - else - { - var currentValue = option.value; - var newValue; - - switch (option.type) - { - case "boolean": - if (valueGiven) - { - vimperator.echoerr("E474: Invalid argument: " + args); - return; - } - - if (invertBoolean) - newValue = !option.value; - else - newValue = !unsetBoolean; - - break; - - case "number": - value = parseInt(value); - - if (isNaN(value)) - { - vimperator.echoerr("E521: Number required after =: " + args); - return; - } - - if (operator == "+") - newValue = currentValue + value; - else if (operator == "-") - newValue = currentValue - value; - else if (operator == "^") - newValue = currentValue * value; - else - newValue = value; - - break; - - case "charlist": - if (operator == "+") - newValue = currentValue.replace(new RegExp("[" + value + "]", "g"), "") + value; - else if (operator == "-") - newValue = currentValue.replace(value, ""); - else if (operator == "^") - // NOTE: Vim doesn't prepend if there's a match in the current value - newValue = value + currentValue.replace(new RegExp("[" + value + "]", "g"), ""); - else - newValue = value; - - break; - - case "stringlist": - if (operator == "+") - { - if (!currentValue.match(value)) - newValue = (currentValue ? currentValue + "," : "") + value; - else - newValue = currentValue; - } - else if (operator == "-") - { - newValue = currentValue.replace(new RegExp("^" + value + ",?|," + value), ""); - } - else if (operator == "^") - { - if (!currentValue.match(value)) - newValue = value + (currentValue ? "," : "") + currentValue; - else - newValue = currentValue; - } - else - { - newValue = value; - } - - break; - - case "string": - if (operator == "+") - newValue = currentValue + value; - else if (operator == "-") - newValue = currentValue.replace(value, ""); - else if (operator == "^") - newValue = value + currentValue; - else - newValue = value; - - break; - - default: - vimperator.echoerr("E685: Internal error: option type `" + option.type + "' not supported"); - } - - if (option.isValidValue(newValue)) - option.value = newValue; - else - // FIXME: need to be able to specify more specific errors - vimperator.echoerr("E474: Invalid argument: " + args); - } - }, - { - shortHelp: "Set an option", - completer: function (filter, special) { return vimperator.completion.option(filter, special); } - } - )); // TODO: check for v.has("windows") commandManager.addUserCommand(new vimperator.Command(["winc[lose]", "wc[lose]"], function (args) { window.close(); }, diff --git a/content/completion.js b/content/completion.js index daeaf0f5..4f965ddc 100644 --- a/content/completion.js +++ b/content/completion.js @@ -150,32 +150,7 @@ vimperator.Completion = function () //{{{ dialog: function (filter) { substrings = []; - var nodes = [ - ["about", "About Firefox"], - ["addbookmark", "Add bookmarks for the current page"], - ["addons", "Manage Add-ons"], - ["bookmarks", "List your bookmarks"], - ["checkupdates", "Check for updates"], - ["cleardata", "Clear private data"], - ["console", "JavaScript console"], - ["customizetoolbar", "Customize the Toolbar"], - ["dominspector", "DOM Inspector"], - ["downloads", "Manage Downloads"], - ["history", "List your history"], - ["import", "Import Preferences, Bookmarks, History, etc. from other browsers"], - ["openfile", "Open the file selector dialog"], - ["pageinfo", "Show information about the current page"], - ["pagesource", "View page source"], - ["places", "Places Organizer: Manage your bookmarks and history"], - ["preferences", "Show Firefox preferences dialog"], - ["printpreview", "Preview the page before printing"], - ["printsetup", "Setup the page size and orientation before printing"], - ["print", "Show print dialog"], - ["saveframe", "Save frame to disk"], - ["savepage", "Save page to disk"], - ["searchengines", "Manage installed search engines"], - ["selectionsource", "View selection source"] - ]; + var nodes = vimperator.config.dialogs || []; if (!filter) return [0, nodes]; @@ -186,6 +161,7 @@ vimperator.Completion = function () //{{{ return [0, buildLongestCommonSubstring(mapped, filter)]; }, + macros: function (filter) { var macros = []; diff --git a/content/io.js b/content/io.js index 33b0ea2d..5e6b1f33 100644 --- a/content/io.js +++ b/content/io.js @@ -38,6 +38,137 @@ vimperator.IO = function () //{{{ const WINDOWS = navigator.platform == "Win32"; var cwd = null, oldcwd = null; + var extname = vimperator.config.name.toLowerCase(); // "vimperator" or "muttator" + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["cd", "chd[ir]"], + "Change the current directory", + function (args) + { + if (!args) + args = "~"; + + if (vimperator.io.setCurrentDirectory(args)) + vimperator.echo(vimperator.io.getCurrentDirectory()); + }, + { + completer: function (filter) { return vimperator.completion.file(filter, true); } + }); + + vimperator.commands.add(["pw[d]"], + "Print the current directory name", + function (args) + { + if (args) + vimperator.echoerr("E488: Trailing characters"); + else + vimperator.echo(vimperator.io.getCurrentDirectory()); + }); + + // mkv[imperatorrc] or mkm[uttatorrc] + vimperator.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 = vimperator.io.getFile(filename); + if (file.exists() && !special) + { + vimperator.echoerr("E189: \"" + filename + "\" 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); + }); + + vimperator.commands.add(["so[urce]"], + "Read Ex commands from a file", + 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); + }, + { + completer: function (filter) { return vimperator.completion.file(filter, true); } + }); + + vimperator.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 = vimperator.io.system(args); + if (output) + vimperator.echo(vimperator.util.escapeHTML(output)); + }); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// diff --git a/content/mail.js b/content/mail.js index e1830350..f4c91656 100644 --- a/content/mail.js +++ b/content/mail.js @@ -76,6 +76,10 @@ vimperator.Mail = function () "Reply to sender", function () { goDoCommand("cmd_reply"); }); + vimperator.mappings.add(modes, ["gm"], + "Get new messages", + function () { goDoCommand("cmd_getNewMessages"); }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ diff --git a/content/muttator.js b/content/muttator.js index c93b43ab..d6db5700 100644 --- a/content/muttator.js +++ b/content/muttator.js @@ -36,8 +36,49 @@ vimperator.config = { get browserModes() { return [vimperator.modes.MESSAGE]; }, get mainWidget() { return GetThreadTree(); }, // focusContent() focuses this widget mainWindowID: "messengerWindow", // used for :set titlestring - dialogs: [], guioptions: { m: ["mail-toolbar-menubar2"], T: ["mail-bar2"], f: ["folderPaneBox", "folderpane_splitter"], F: ["folderPaneHeader"] }, + dialogs: [ + /*["about", "About Firefox", + function() { openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], + ["addons", "Manage Add-ons", + function() { BrowserOpenAddonsMgr(); }],*/ + ["checkupdates", "Check for updates", + function() { checkForUpdates(); }], + /*["cleardata", "Clear private data", + function() { Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); }],*/ + ["console", "JavaScript console", + function() { toJavaScriptConsole(); }], + /*["customizetoolbar", "Customize the Toolbar", + function() { BrowserCustomizeToolbar(); }],*/ + ["dominspector", "DOM Inspector", + function() { inspectDOMDocument(content.document); }], + ["downloads", "Manage Downloads", + function() { toOpenWindowByType('Download:Manager', 'chrome://mozapps/content/downloads/downloads.xul', 'chrome,dialog=no,resizable'); }], + /*["import", "Import Preferences, Bookmarks, History, etc. from other browsers", + function() { BrowserImport(); }], + ["openfile", "Open the file selector dialog", + function() { BrowserOpenFileWindow(); }], + ["pageinfo", "Show information about the current page", + function() { BrowserPageInfo(); }], + ["pagesource", "View page source", + function() { BrowserViewSourceOfDocument(content.document); }], + ["preferences", "Show Firefox preferences dialog", + function() { openPreferences(); }], + ["printpreview", "Preview the page before printing", + function() { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }],*/ + ["printsetup", "Setup the page size and orientation before printing", + function() { PrintUtils.showPageSetup(); }], + ["print", "Show print dialog", + function() { PrintUtils.print(); }], + ["saveframe", "Save frame to disk", + function() { saveFrameDocument(); }], + ["savepage", "Save page to disk", + function() { saveDocument(window.content.document); }], + /*["searchengines", "Manage installed search engines", + function() { openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], + ["selectionsource", "View selection source", + function() { vimperator.buffer.viewSelectionSource(); }]*/ + ], init: function() { diff --git a/content/options.js b/content/options.js index 796c4487..c4c25d10 100644 --- a/content/options.js +++ b/content/options.js @@ -196,6 +196,286 @@ vimperator.Options = function () //{{{ // start with saved session storePreference("browser.startup.page", 3); + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["pref[erences]", "prefs"], + "Show " + vimperator.config.appName + " Preferences", + function (args, special, count, modifiers) + { + if (!args) + { + // TODO: copy these snippets to more function which should work with :tab xxx + if (modifiers && modifiers.inTab) + { + vimperator.open(special ? "about:config" : + "chrome://browser/content/preferences/preferences.xul", vimperator.NEW_TAB); + } + else + { + if (special) // open firefox settings gui dialog + vimperator.open("about:config", vimperator.CURRENT_TAB); + else + openPreferences(); + } + } + else + { + vimperator.echoerr("E488: Trailing characters"); + } + }); + + // TODO: support setting multiple options at once + vimperator.commands.add(["se[t]"], + "Set an option", + function (args, special, count, modifiers) + { + if (special) + { + var onlyNonDefault = false; + if (!args) + { + args = "all"; + onlyNonDefault = true; + } + // 1 2 3 4 5 + var matches = args.match(/^\s*?([a-zA-Z0-9\.\-_{}]+)([?&!])?\s*(([+-^]?)=(.*))?\s*$/); + var name = matches[1]; + var reset = false; + var invertBoolean = false; + + if (matches[2] == "&") + reset = true; + else if (matches[2] == "!") + invertBoolean = true; + + if (name == "all" && reset) + vimperator.echoerr("You can't reset all the firefox options, it could make your browser unusable."); + else if (name == "all") + vimperator.options.listPrefs(onlyNonDefault, ""); + else if (reset) + vimperator.options.resetPref(name); + else if (invertBoolean) + vimperator.options.invertPref(name); + else if (matches[3]) + { + var value = matches[5]; + switch (value) + { + case undefined: + value = ""; + break; + case "true": + value = true; + break; + case "false": + value = false; + break; + default: + var valueInt = parseInt(value, 10); + if (!isNaN(valueInt)) + value = valueInt; + } + vimperator.options.setPref(name, value); + } + else + { + vimperator.options.listPrefs(onlyNonDefault, name); + } + return; + } + + var onlyNonDefault = false; // used for :set to print non-default options + if (!args) + { + args = "all"; + onlyNonDefault = true; + } + + // 1 2 3 4 5 6 + var matches = args.match(/^\s*(no|inv)?([a-z]+)([?&!])?\s*(([+-^]?)=(.*))?\s*$/); + if (!matches) + { + vimperator.echoerr("E518: Unknown option: " + args); + return; + } + + var unsetBoolean = false; + if (matches[1] == "no") + unsetBoolean = true; + + var name = matches[2]; + var all = false; + if (name == "all") + all = true; + + var option = vimperator.options.get(name); + if (!option && !all) + { + vimperator.echoerr("E518: Unknown option: " + args); + return; + } + + var valueGiven = !!matches[4]; + + var get = false; + if (all || matches[3] == "?" || (option.type != "boolean" && !valueGiven)) + get = true; + + var reset = false; + if (matches[3] == "&") + reset = true; + + var invertBoolean = false; + if (matches[1] == "inv" || matches[3] == "!") + invertBoolean = true; + + var operator = matches[5]; + + var value = matches[6]; + if (value === undefined) + value = ""; + + // reset a variable to its default value + if (reset) + { + if (all) + { + for (let option in vimperator.options) + option.reset(); + } + else + { + option.reset(); + } + } + // read access + else if (get) + { + if (all) + { + vimperator.options.list(onlyNonDefault); + } + else + { + if (option.type == "boolean") + vimperator.echo((option.value ? " " : "no") + option.name); + else + vimperator.echo(" " + option.name + "=" + option.value); + } + } + // write access + // NOTE: the behaviour is generally Vim compatible but could be + // improved. i.e. Vim's behaviour is pretty sloppy to no real + // benefit + else + { + var currentValue = option.value; + var newValue; + + switch (option.type) + { + case "boolean": + if (valueGiven) + { + vimperator.echoerr("E474: Invalid argument: " + args); + return; + } + + if (invertBoolean) + newValue = !option.value; + else + newValue = !unsetBoolean; + + break; + + case "number": + value = parseInt(value); + + if (isNaN(value)) + { + vimperator.echoerr("E521: Number required after =: " + args); + return; + } + + if (operator == "+") + newValue = currentValue + value; + else if (operator == "-") + newValue = currentValue - value; + else if (operator == "^") + newValue = currentValue * value; + else + newValue = value; + + break; + + case "charlist": + if (operator == "+") + newValue = currentValue.replace(new RegExp("[" + value + "]", "g"), "") + value; + else if (operator == "-") + newValue = currentValue.replace(value, ""); + else if (operator == "^") + // NOTE: Vim doesn't prepend if there's a match in the current value + newValue = value + currentValue.replace(new RegExp("[" + value + "]", "g"), ""); + else + newValue = value; + + break; + + case "stringlist": + if (operator == "+") + { + if (!currentValue.match(value)) + newValue = (currentValue ? currentValue + "," : "") + value; + else + newValue = currentValue; + } + else if (operator == "-") + { + newValue = currentValue.replace(new RegExp("^" + value + ",?|," + value), ""); + } + else if (operator == "^") + { + if (!currentValue.match(value)) + newValue = value + (currentValue ? "," : "") + currentValue; + else + newValue = currentValue; + } + else + { + newValue = value; + } + + break; + + case "string": + if (operator == "+") + newValue = currentValue + value; + else if (operator == "-") + newValue = currentValue.replace(value, ""); + else if (operator == "^") + newValue = value + currentValue; + else + newValue = value; + + break; + + default: + vimperator.echoerr("E685: Internal error: option type `" + option.type + "' not supported"); + } + + if (option.isValidValue(newValue)) + option.value = newValue; + else + // FIXME: need to be able to specify more specific errors + vimperator.echoerr("E474: Invalid argument: " + args); + } + }, + { + completer: function (filter, special) { return vimperator.completion.option(filter, special); } + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ diff --git a/content/vim.js b/content/vim.js index b02ece67..72d885a8 100644 --- a/content/vim.js +++ b/content/vim.js @@ -116,7 +116,144 @@ const vimperator = (function () //{{{ function addCommands() { + vimperator.commands.add(["addo[ns]"], + "Manage available Extensions and Themes", + function () { vimperator.open("chrome://mozapps/content/extensions/extensions.xul", vimperator.NEW_TAB); }); + vimperator.commands.add(["beep"], + "Play a system beep", + function () { vimperator.beep(); }); + + vimperator.commands.add(["dia[log]"], + "Open a " + vimperator.config.appName + " dialog", + function (args, special) + { + try + { + var dialogs = vimperator.config.dialogs || []; + for (let i = 0; i < dialogs.length; i++) + { + if (dialogs[i][0] == args) + return dialogs[i][2](); + } + vimperator.echoerr(args ? "Dialog \"" + args + "\" not available" : "E474: Invalid argument"); + } + catch (e) + { + vimperator.echoerr("Error opening '" + args + "': " + e); + } + }, + { + completer: function (filter) { return vimperator.completion.dialog(filter); } + }); + + vimperator.commands.add(["exe[cute]"], + "Execute the argument as an Ex command", + function (args) { vimperator.execute(args); }); + + vimperator.commands.add(["exu[sage]"], + "List all Ex commands with a short description", + function () + { + var usage = ""; + for (let command in vimperator.commands) + { + usage += ""; + } + usage += "
:" + + vimperator.util.escapeHTML(command.name) + "" + + vimperator.util.escapeHTML(command.shortHelp) + "
"; + + vimperator.echo(usage, vimperator.commandline.FORCE_MULTILINE); + }); + + vimperator.commands.add(["h[elp]"], + "Display help", + function (args, special, count, modifiers) + { + function jumpToTag(file, tag) + { + vimperator.open("chrome://" + vimperator.config.name.toLowerCase() + "/locale/" + file); + setTimeout(function() { + var elem = vimperator.buffer.getElement('@class="tag" and text()="' + tag + '"'); + if (elem) + window.content.scrollTo(0, elem.getBoundingClientRect().top - 10); // 10px context + else + dump('no element: ' + '@class="tag" and text()="' + tag + '"\n' ); + }, 200); + } + + if (!args) + { + vimperator.open("chrome://" + vimperator.config.name.toLowerCase() + "/locale/intro.html"); + return; + } + + var [, items] = vimperator.completion.help(args); + var partialMatch = -1; + for (var i = 0; i < items.length; i++) + { + if (items[i][0] == args) + { + jumpToTag(items[i][1], items[i][0]); + return; + } + else if (partialMatch == -1 && items[i][0].indexOf(args) > -1) + { + partialMatch = i; + } + } + + if (partialMatch > -1) + jumpToTag(items[partialMatch][1], items[partialMatch][0]); + else + vimperator.echoerr("E149: Sorry, no help for " + args); + }, + { + completer: function (filter) { return vimperator.completion.help(filter); } + }); + + vimperator.commands.add(["javas[cript]", "js"], + "Run a JavaScript command through eval()", + function (args, special) + { + if (special) // open javascript console + vimperator.open("chrome://global/content/console.xul", vimperator.NEW_TAB); + else + { + // check for a heredoc + var matches = args.match(/(.*)<<\s*([^\s]+)$/); + if (matches && matches[2]) + { + vimperator.commandline.inputMultiline(new RegExp("^" + matches[2] + "$", "m"), + function (code) + { + try + { + eval(matches[1] + "\n" + code); + } + catch (e) + { + vimperator.echoerr(e.name + ": " + e.message); + } + }); + } + else // single line javascript code + { + try + { + eval("with(vimperator){" + args + "}"); + } + catch (e) + { + vimperator.echoerr(e.name + ": " + e.message); + } + } + } + }, + { + completer: function (filter) { return vimperator.completion.javascript(filter); } + }); } // initially hide all GUI, it is later restored unless the user has :set go= or something @@ -487,8 +624,9 @@ const vimperator = (function () //{{{ function log(module) { vimperator.log("Loading module " + module + "...", 3); }; vimperator.log("Initializing vimperator object...", 1); + // commands must always be the first module to be initialized + log("commands"); vimperator.commands = vimperator.Commands(); addCommands(); log("options"); vimperator.options = vimperator.Options(); addOptions(); - log("commands"); vimperator.commands = vimperator.Commands(); log("mappings"); vimperator.mappings = vimperator.Mappings(); addMappings(); log("events"); vimperator.events = vimperator.Events(); log("commandline"); vimperator.commandline = vimperator.CommandLine(); diff --git a/content/vimperator.js b/content/vimperator.js index 8a43d5f7..2c8c425e 100644 --- a/content/vimperator.js +++ b/content/vimperator.js @@ -33,8 +33,57 @@ vimperator.config = { /*** optional options, there are checked for existance and a fallback provided ***/ features: ["bookmarks", "history", "marks", "quickmarks", "hints", "tabs", "windows"], - dialogs: [], guioptions: { m: ["toolbar-menubar"], T: ["nav-bar"], b: ["PersonalToolbar"] }, + dialogs: [ + ["about", "About Firefox", + function() { openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], + ["addbookmark", "Add bookmark for the current page", + function() { PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksRootId); }], + ["addons", "Manage Add-ons", + function() { BrowserOpenAddonsMgr(); }], + ["bookmarks", "List your bookmarks", + function() { openDialog("chrome://browser/content/bookmarks/bookmarksPanel.xul", "Bookmarks", "dialog,centerscreen,width=600,height=600"); }], + ["checkupdates", "Check for updates", + function() { checkForUpdates(); }], + ["cleardata", "Clear private data", + function() { Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); }], + ["console", "JavaScript console", + function() { toJavaScriptConsole(); }], + ["customizetoolbar", "Customize the Toolbar", + function() { BrowserCustomizeToolbar(); }], + ["dominspector", "DOM Inspector", + function() { inspectDOMDocument(content.document); }], + ["downloads", "Manage Downloads", + function() { toOpenWindowByType('Download:Manager', 'chrome://mozapps/content/downloads/downloads.xul', 'chrome,dialog=no,resizable'); }], + ["history", "List your history", + function() { openDialog("chrome://browser/content/history/history-panel.xul", "History", "dialog,centerscreen,width=600,height=600"); }], + ["import", "Import Preferences, Bookmarks, History, etc. from other browsers", + function() { BrowserImport(); }], + ["openfile", "Open the file selector dialog", + function() { BrowserOpenFileWindow(); }], + ["pageinfo", "Show information about the current page", + function() { BrowserPageInfo(); }], + ["pagesource", "View page source", + function() { BrowserViewSourceOfDocument(content.document); }], + ["places", "Places Organizer: Manage your bookmarks and history", + function() { PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); }], + ["preferences", "Show Firefox preferences dialog", + function() { openPreferences(); }], + ["printpreview", "Preview the page before printing", + function() { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }], + ["printsetup", "Setup the page size and orientation before printing", + function() { PrintUtils.showPageSetup(); }], + ["print", "Show print dialog", + function() { PrintUtils.print(); }], + ["saveframe", "Save frame to disk", + function() { saveFrameDocument(); }], + ["savepage", "Save page to disk", + function() { saveDocument(window.content.document); }], + ["searchengines", "Manage installed search engines", + function() { openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], + ["selectionsource", "View selection source", + function() { vimperator.buffer.viewSelectionSource(); }] + ], init: function() {