summaryrefslogtreecommitdiff
path: root/common/content/editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'common/content/editor.js')
-rw-r--r--common/content/editor.js1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/common/content/editor.js b/common/content/editor.js
new file mode 100644
index 00000000..af52c089
--- /dev/null
+++ b/common/content/editor.js
@@ -0,0 +1,1128 @@
+/***** BEGIN LICENSE BLOCK ***** {{{
+Version: MPL 1.1/GPL 2.0/LGPL 2.1
+
+The contents of this file are subject to the Mozilla Public License Version
+1.1 (the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+http://www.mozilla.org/MPL/
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the
+License.
+
+(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
+
+Alternatively, the contents of this file may be used under the terms of
+either the GNU General Public License Version 2 or later (the "GPL"), or
+the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+in which case the provisions of the GPL or the LGPL are applicable instead
+of those above. If you wish to allow use of your version of this file only
+under the terms of either the GPL or the LGPL, and not to allow others to
+use your version of this file under the terms of the MPL, indicate your
+decision by deleting the provisions above and replace them with the notice
+and other provisions required by the GPL or the LGPL. If you do not delete
+the provisions above, a recipient may use your version of this file under
+the terms of any one of the MPL, the GPL or the LGPL.
+}}} ***** END LICENSE BLOCK *****/
+
+// command names taken from:
+// http://developer.mozilla.org/en/docs/Editor_Embedding_Guide
+
+function Editor() //{{{
+{
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////// PRIVATE SECTION /////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ // store our last search with f, F, t or T
+ var lastFindChar = null;
+ var lastFindCharFunc = null;
+ var abbrev = {}; // abbrev["lhr"][0]["{i,c,!}","rhs"]
+
+ function getEditor()
+ {
+ return window.document.commandDispatcher.focusedElement;
+ }
+
+ function getController()
+ {
+ var ed = getEditor();
+ if (!ed || !ed.controllers)
+ return null;
+
+ return ed.controllers.getControllerForCommand("cmd_beginLine");
+ }
+
+ function selectPreviousLine()
+ {
+ editor.executeCommand("cmd_selectLinePrevious");
+ if ((modes.extended & modes.LINE) && !editor.selectedText())
+ editor.executeCommand("cmd_selectLinePrevious");
+ }
+ function selectNextLine()
+ {
+ editor.executeCommand("cmd_selectLineNext");
+ if ((modes.extended & modes.LINE) && !editor.selectedText())
+ editor.executeCommand("cmd_selectLineNext");
+ }
+
+ // add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXTAREA mode
+ function addMovementMap(keys, hasCount, caretModeMethod, caretModeArg, textareaCommand, visualTextareaCommand)
+ {
+ var extraInfo = {};
+ if (hasCount)
+ extraInfo.flags = Mappings.flags.COUNT;
+
+ mappings.add([modes.CARET], keys, "",
+ function (count)
+ {
+ if (typeof count != "number" || count < 1)
+ count = 1;
+
+ var controller = getBrowser().docShell
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsISelectionDisplay)
+ .QueryInterface(Components.interfaces.nsISelectionController);
+
+ while (count--)
+ controller[caretModeMethod](caretModeArg, false);
+ },
+ extraInfo);
+
+ mappings.add([modes.VISUAL], keys, "",
+ function (count)
+ {
+ if (typeof count != "number" || count < 1 || !hasCount)
+ count = 1;
+
+ var controller = getBrowser().docShell
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsISelectionDisplay)
+ .QueryInterface(Components.interfaces.nsISelectionController);
+
+ while (count--)
+ {
+ if (modes.extended & modes.TEXTAREA)
+ {
+ if (typeof visualTextareaCommand == "function")
+ visualTextareaCommand();
+ else
+ editor.executeCommand(visualTextareaCommand);
+ }
+ else
+ controller[caretModeMethod](caretModeArg, true);
+ }
+ },
+ extraInfo);
+
+ mappings.add([modes.TEXTAREA], keys, "",
+ function (count)
+ {
+ if (typeof count != "number" || count < 1)
+ count = 1;
+
+ editor.executeCommand(textareaCommand, count);
+ },
+ extraInfo);
+ }
+
+ // add mappings for commands like i,a,s,c,etc. in TEXTAREA mode
+ function addBeginInsertModeMap(keys, commands)
+ {
+ mappings.add([modes.TEXTAREA], keys, "",
+ function (count)
+ {
+ commands.forEach(function (cmd)
+ editor.executeCommand(cmd, 1));
+ modes.set(modes.INSERT, modes.TEXTAREA);
+ });
+ }
+
+ function addMotionMap(key)
+ {
+ mappings.add([modes.TEXTAREA], [key],
+ "Motion command",
+ function (motion, count) { editor.executeCommandWithMotion(key, motion, count); },
+ { flags: Mappings.flags.MOTION | Mappings.flags.COUNT });
+ }
+
+ // For the record, some of this code I've just finished throwing
+ // away makes me want to pull someone else's hair out. --Kris
+ function abbrevs()
+ {
+ for (let [lhs, abbr] in Iterator(abbrev))
+ for (let [,rhs] in Iterator(abbr))
+ yield [lhs, rhs];
+ }
+
+ // mode = "i" -> add :iabbrev, :iabclear and :iunabbrev commands
+ function addAbbreviationCommands(ch, modeDescription)
+ {
+ var mode = ch || "!";
+ modeDescription = modeDescription ? " in " + modeDescription + " mode" : "";
+
+ commands.add([ch ? ch + "a[bbrev]" : "ab[breviate]"],
+ "Abbreviate a key sequence" + modeDescription,
+ function (args)
+ {
+ let [lhs, rhs] = args;
+ if (rhs)
+ editor.addAbbreviation(mode, lhs, rhs);
+ else
+ editor.listAbbreviations(mode, lhs || "");
+ },
+ {
+ literal: 1,
+ serial: function () [
+ {
+ command: this.name,
+ arguments: [lhs],
+ literalArg: abbr[1]
+ }
+ for ([lhs, abbr] in abbrevs())
+ if (abbr[0] == mode)
+ ]
+ });
+
+ commands.add([ch ? ch + "una[bbrev]" : "una[bbreviate]"],
+ "Remove an abbreviation" + modeDescription,
+ function (args) { editor.removeAbbreviation(mode, args.string); });
+
+ commands.add([ch + "abc[lear]"],
+ "Remove all abbreviations" + modeDescription,
+ function () { editor.removeAllAbbreviations(mode); },
+ { argCount: "0" });
+ }
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// OPTIONS /////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ options.add(["editor"],
+ "Set the external text editor",
+ "string", "gvim -f");
+
+ options.add(["insertmode", "im"],
+ "Use Insert mode as the default for text areas",
+ "boolean", true);
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// MAPPINGS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ var myModes = [modes.INSERT, modes.COMMAND_LINE];
+
+ /* KEYS COUNT CARET TEXTAREA VISUAL_TEXTAREA */
+ addMovementMap(["k", "<Up>"], true, "lineMove", false, "cmd_linePrevious", selectPreviousLine);
+ addMovementMap(["j", "<Down>", "<Return>"], true, "lineMove", true, "cmd_lineNext", selectNextLine);
+ addMovementMap(["h", "<Left>", "<BS>"], true, "characterMove", false, "cmd_charPrevious", "cmd_selectCharPrevious");
+ addMovementMap(["l", "<Right>", "<Space>"], true, "characterMove", true, "cmd_charNext", "cmd_selectCharNext");
+ addMovementMap(["b", "B", "<C-Left>"], true, "wordMove", false, "cmd_wordPrevious", "cmd_selectWordPrevious");
+ addMovementMap(["w", "W", "e", "<C-Right>"], true, "wordMove", true, "cmd_wordNext", "cmd_selectWordNext");
+ addMovementMap(["<C-f>", "<PageDown>"], true, "pageMove", true, "cmd_movePageDown", "cmd_selectNextPage");
+ addMovementMap(["<C-b>", "<PageUp>"], true, "pageMove", false, "cmd_movePageUp", "cmd_selectPreviousPage");
+ addMovementMap(["gg", "<C-Home>"], false, "completeMove", false, "cmd_moveTop", "cmd_selectTop");
+ addMovementMap(["G", "<C-End>"], false, "completeMove", true, "cmd_moveBottom", "cmd_selectBottom");
+ addMovementMap(["0", "^", "<Home>"], false, "intraLineMove", false, "cmd_beginLine", "cmd_selectBeginLine");
+ addMovementMap(["$", "<End>"], false, "intraLineMove", true, "cmd_endLine" , "cmd_selectEndLine" );
+
+ addBeginInsertModeMap(["i", "<Insert"], []);
+ addBeginInsertModeMap(["a"], ["cmd_charNext"]);
+ addBeginInsertModeMap(["I", "gI"], ["cmd_beginLine"]);
+ addBeginInsertModeMap(["A"], ["cmd_endLine"]);
+ addBeginInsertModeMap(["s"], ["cmd_deleteCharForward"]);
+ addBeginInsertModeMap(["S"], ["cmd_deleteToEndOfLine", "cmd_deleteToBeginningOfLine"]);
+ addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"]);
+
+ addMotionMap("d"); // delete
+ addMotionMap("c"); // change
+ addMotionMap("y"); // yank
+
+ // insert mode mappings
+ mappings.add(myModes,
+ ["<C-o>", "<C-f>", "<C-g>", "<C-n>", "<C-p>"],
+ "Ignore certain " + config.hostApplication + " key bindings",
+ function () { /*liberator.beep();*/ });
+
+ mappings.add(myModes,
+ ["<C-w>"], "Delete previous word",
+ function () { editor.executeCommand("cmd_deleteWordBackward", 1); });
+
+ mappings.add(myModes,
+ ["<C-u>"], "Delete until beginning of current line",
+ function ()
+ {
+ // broken in FF3, deletes the whole line:
+ // editor.executeCommand("cmd_deleteToBeginningOfLine", 1);
+ editor.executeCommand("cmd_selectBeginLine", 1);
+ if (getController().isCommandEnabled("cmd_delete"))
+ editor.executeCommand("cmd_delete", 1);
+ });
+
+ mappings.add(myModes,
+ ["<C-k>"], "Delete until end of current line",
+ function () { editor.executeCommand("cmd_deleteToEndOfLine", 1); });
+
+ mappings.add(myModes,
+ ["<C-a>"], "Move cursor to beginning of current line",
+ function () { editor.executeCommand("cmd_beginLine", 1); });
+
+ mappings.add(myModes,
+ ["<C-e>"], "Move cursor to end of current line",
+ function () { editor.executeCommand("cmd_endLine", 1); });
+
+ mappings.add(myModes,
+ ["<C-h>"], "Delete character to the left",
+ function () { editor.executeCommand("cmd_deleteCharBackward", 1); });
+
+ mappings.add(myModes,
+ ["<C-d>"], "Delete character to the right",
+ function () { editor.executeCommand("cmd_deleteCharForward", 1); });
+
+ /*mappings.add(myModes,
+ ["<C-Home>"], "Move cursor to beginning of text field",
+ function () { editor.executeCommand("cmd_moveTop", 1); });
+
+ mappings.add(myModes,
+ ["<C-End>"], "Move cursor to end of text field",
+ function () { editor.executeCommand("cmd_moveBottom", 1); });*/
+
+ mappings.add(myModes,
+ ["<S-Insert>"], "Insert clipboard/selection",
+ function () { editor.pasteClipboard(); });
+
+ mappings.add([modes.INSERT, modes.TEXTAREA, modes.COMPOSE],
+ ["<C-i>"], "Edit text field with an external editor",
+ function () { editor.editFieldExternally(); });
+
+ // FIXME: <esc> does not work correctly
+ mappings.add([modes.INSERT],
+ ["<C-t>"], "Edit text field in vi mode",
+ function () { liberator.mode = modes.TEXTAREA; });
+
+ mappings.add([modes.INSERT],
+ ["<Space>", "<Return>"], "Expand insert mode abbreviation",
+ function () { editor.expandAbbreviation("i"); },
+ { flags: Mappings.flags.ALLOW_EVENT_ROUTING });
+
+ mappings.add([modes.INSERT],
+ ["<Tab>"], "Expand insert mode abbreviation",
+ function () { editor.expandAbbreviation("i"); document.commandDispatcher.advanceFocus(); });
+
+ mappings.add([modes.INSERT],
+ ["<C-]>", "<C-5>"], "Expand insert mode abbreviation",
+ function () { editor.expandAbbreviation("i"); });
+
+ // textarea mode
+ mappings.add([modes.TEXTAREA],
+ ["u"], "Undo",
+ function (count)
+ {
+ editor.executeCommand("cmd_undo", count);
+ liberator.mode = modes.TEXTAREA;
+ },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA],
+ ["<C-r>"], "Redo",
+ function (count)
+ {
+ editor.executeCommand("cmd_redo", count);
+ liberator.mode = modes.TEXTAREA;
+ },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA],
+ ["D"], "Delete the characters under the cursor until the end of the line",
+ function () { editor.executeCommand("cmd_deleteToEndOfLine"); });
+
+ mappings.add([modes.TEXTAREA],
+ ["o"], "Open line below current",
+ function (count)
+ {
+ editor.executeCommand("cmd_endLine", 1);
+ modes.set(modes.INSERT, modes.TEXTAREA);
+ events.feedkeys("<Return>");
+ });
+
+ mappings.add([modes.TEXTAREA],
+ ["O"], "Open line above current",
+ function (count)
+ {
+ editor.executeCommand("cmd_beginLine", 1);
+ modes.set(modes.INSERT, modes.TEXTAREA);
+ events.feedkeys("<Return>");
+ editor.executeCommand("cmd_linePrevious", 1);
+ });
+
+ mappings.add([modes.TEXTAREA],
+ ["X"], "Delete character to the left",
+ function (count) { editor.executeCommand("cmd_deleteCharBackward", count); },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA],
+ ["x"], "Delete character to the right",
+ function (count) { editor.executeCommand("cmd_deleteCharForward", count); },
+ { flags: Mappings.flags.COUNT });
+
+ // visual mode
+ mappings.add([modes.CARET, modes.TEXTAREA, modes.VISUAL],
+ ["v"], "Start visual mode",
+ function (count) { modes.set(modes.VISUAL, liberator.mode); });
+
+ mappings.add([modes.TEXTAREA],
+ ["V"], "Start visual line mode",
+ function (count)
+ {
+ modes.set(modes.VISUAL, modes.TEXTAREA | modes.LINE);
+ editor.executeCommand("cmd_beginLine", 1);
+ editor.executeCommand("cmd_selectLineNext", 1);
+ });
+
+ mappings.add([modes.VISUAL],
+ ["c", "s"], "Change selected text",
+ function (count)
+ {
+ if (modes.extended & modes.TEXTAREA)
+ {
+ editor.executeCommand("cmd_cut");
+ modes.set(modes.INSERT, modes.TEXTAREA);
+ }
+ else
+ liberator.beep();
+ });
+
+ mappings.add([modes.VISUAL],
+ ["d"], "Delete selected text",
+ function (count)
+ {
+ if (modes.extended & modes.TEXTAREA)
+ {
+ editor.executeCommand("cmd_cut");
+ modes.set(modes.TEXTAREA);
+ }
+ else
+ liberator.beep();
+ });
+
+ mappings.add([modes.VISUAL],
+ ["y"], "Yank selected text",
+ function (count)
+ {
+ if (modes.extended & modes.TEXTAREA)
+ {
+ editor.executeCommand("cmd_copy");
+ modes.set(modes.TEXTAREA);
+ }
+ else
+ {
+ var sel = window.content.document.getSelection();
+ if (sel)
+ util.copyToClipboard(sel, true);
+ else
+ liberator.beep();
+ }
+ });
+
+ mappings.add([modes.VISUAL, modes.TEXTAREA],
+ ["p"], "Paste clipboard contents",
+ function (count)
+ {
+ if (!(modes.extended & modes.CARET))
+ {
+ if (!count) count = 1;
+ while (count--)
+ editor.executeCommand("cmd_paste");
+ liberator.mode = modes.TEXTAREA;
+ }
+ else
+ liberator.beep();
+ });
+
+ // finding characters
+ mappings.add([modes.TEXTAREA, modes.VISUAL],
+ ["f"], "Move to a character on the current line after the cursor",
+ function (count, arg)
+ {
+ var pos = editor.findCharForward(arg, count);
+ if (pos >= 0)
+ editor.moveToPosition(pos, true, liberator.mode == modes.VISUAL);
+ },
+ { flags: Mappings.flags.ARGUMENT | Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA, modes.VISUAL],
+ ["F"], "Move to a charater on the current line before the cursor",
+ function (count, arg)
+ {
+ var pos = editor.findCharBackward(arg, count);
+ if (pos >= 0)
+ editor.moveToPosition(pos, false, liberator.mode == modes.VISUAL);
+ },
+ { flags: Mappings.flags.ARGUMENT | Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA, modes.VISUAL],
+ ["t"], "Move before a character on the current line",
+ function (count, arg)
+ {
+ var pos = editor.findCharForward(arg, count);
+ if (pos >= 0)
+ editor.moveToPosition(pos - 1, true, liberator.mode == modes.VISUAL);
+ },
+ { flags: Mappings.flags.ARGUMENT | Mappings.flags.COUNT });
+
+ mappings.add([modes.TEXTAREA, modes.VISUAL],
+ ["T"], "Move before a character on the current line, backwards",
+ function (count, arg)
+ {
+ var pos = editor.findCharBackward(arg, count);
+ if (pos >= 0)
+ editor.moveToPosition(pos + 1, false, liberator.mode == modes.VISUAL);
+ },
+ { flags: Mappings.flags.ARGUMENT | Mappings.flags.COUNT });
+
+ // textarea and visual mode
+ mappings.add([modes.TEXTAREA, modes.VISUAL],
+ ["~"], "Switch case of the character under the cursor and move the cursor to the right",
+ function (count)
+ {
+ if (modes.main == modes.VISUAL)
+ {
+ count = getEditor().selectionEnd - getEditor().selectionStart;
+ }
+ if (typeof count != "number" || count < 1)
+ {
+ count = 1;
+ }
+
+ while (count-- > 0)
+ {
+ var text = getEditor().value;
+ var pos = getEditor().selectionStart;
+ if (pos >= text.length)
+ {
+ liberator.beep();
+ return;
+ }
+ var chr = text[pos];
+ getEditor().value = text.substring(0, pos) +
+ (chr == chr.toLocaleLowerCase() ? chr.toLocaleUpperCase() : chr.toLocaleLowerCase()) +
+ text.substring(pos + 1);
+ editor.moveToPosition(pos + 1, true, false);
+ }
+ modes.set(modes.TEXTAREA);
+ },
+ { flags: Mappings.flags.COUNT });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMMANDS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ addAbbreviationCommands("", "");
+ addAbbreviationCommands("i", "insert");
+ addAbbreviationCommands("c", "command line");
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ return {
+
+ line: function ()
+ {
+ var line = 1;
+ var text = getEditor().value;
+ for (let i = 0; i < getEditor().selectionStart; i++)
+ if (text[i] == "\n")
+ line++;
+ return line;
+ },
+
+ col: function ()
+ {
+ var col = 1;
+ var text = getEditor().value;
+ for (let i = 0; i < getEditor().selectionStart; i++)
+ {
+ col++;
+ if (text[i] == "\n")
+ col = 1;
+ }
+ return col;
+ },
+
+ unselectText: function ()
+ {
+ var elt = window.document.commandDispatcher.focusedElement;
+ if (elt && elt.selectionEnd)
+ elt.selectionEnd = elt.selectionStart;
+ },
+
+ selectedText: function ()
+ {
+ var text = getEditor().value;
+ return text.substring(getEditor().selectionStart, getEditor().selectionEnd);
+ },
+
+ pasteClipboard: function ()
+ {
+ var elt = window.document.commandDispatcher.focusedElement;
+
+ if (elt.setSelectionRange && util.readFromClipboard())
+ // readFromClipboard would return 'undefined' if not checked
+ // dunno about .setSelectionRange
+ {
+ var rangeStart = elt.selectionStart; // caret position
+ var rangeEnd = elt.selectionEnd;
+ var tempStr1 = elt.value.substring(0, rangeStart);
+ var tempStr2 = util.readFromClipboard();
+ var tempStr3 = elt.value.substring(rangeEnd);
+ elt.value = tempStr1 + tempStr2 + tempStr3;
+ elt.selectionStart = rangeStart + tempStr2.length;
+ elt.selectionEnd = elt.selectionStart;
+ }
+ },
+
+ // count is optional, defaults to 1
+ executeCommand: function (cmd, count)
+ {
+ var controller = getController();
+ if (!controller || !controller.supportsCommand(cmd) || !controller.isCommandEnabled(cmd))
+ {
+ liberator.beep();
+ return false;
+ }
+
+ if (typeof count != "number" || count < 1)
+ count = 1;
+
+ var didCommand = false;
+ while (count--)
+ {
+ // some commands need this try/catch workaround, because a cmd_charPrevious triggered
+ // at the beginning of the textarea, would hang the doCommand()
+ // good thing is, we need this code anyway for proper beeping
+ try
+ {
+ controller.doCommand(cmd);
+ didCommand = true;
+ }
+ catch (e)
+ {
+ if (!didCommand)
+ liberator.beep();
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ // cmd = y, d, c
+ // motion = b, 0, gg, G, etc.
+ executeCommandWithMotion: function (cmd, motion, count)
+ {
+ if (!typeof count == "number" || count < 1)
+ count = 1;
+
+ if (cmd == motion)
+ {
+ motion = "j";
+ count--;
+ }
+
+ modes.set(modes.VISUAL, modes.TEXTAREA);
+
+ switch (motion)
+ {
+ case "j":
+ this.executeCommand("cmd_beginLine", 1);
+ this.executeCommand("cmd_selectLineNext", count+1);
+ break;
+ case "k":
+ this.executeCommand("cmd_beginLine", 1);
+ this.executeCommand("cmd_lineNext", 1);
+ this.executeCommand("cmd_selectLinePrevious", count+1);
+ break;
+ case "h":
+ this.executeCommand("cmd_selectCharPrevious", count);
+ break;
+ case "l":
+ this.executeCommand("cmd_selectCharNext", count);
+ break;
+ case "e":
+ case "w":
+ this.executeCommand("cmd_selectWordNext", count);
+ break;
+ case "b":
+ this.executeCommand("cmd_selectWordPrevious", count);
+ break;
+ case "0":
+ case "^":
+ this.executeCommand("cmd_selectBeginLine", 1);
+ break;
+ case "$":
+ this.executeCommand("cmd_selectEndLine", 1);
+ break;
+ case "gg":
+ this.executeCommand("cmd_endLine", 1);
+ this.executeCommand("cmd_selectTop", 1);
+ this.executeCommand("cmd_selectBeginLine", 1);
+ break;
+ case "G":
+ this.executeCommand("cmd_beginLine", 1);
+ this.executeCommand("cmd_selectBottom", 1);
+ this.executeCommand("cmd_selectEndLine", 1);
+ break;
+
+ default:
+ liberator.beep();
+ return false;
+ }
+
+ switch (cmd)
+ {
+ case "d":
+ this.executeCommand("cmd_delete", 1);
+ // need to reset the mode as the visual selection changes it
+ modes.main = modes.TEXTAREA;
+ break;
+ case "c":
+ this.executeCommand("cmd_delete", 1);
+ modes.set(modes.INSERT, modes.TEXTAREA);
+ break;
+ case "y":
+ this.executeCommand("cmd_copy", 1);
+ this.unselectText();
+ break;
+
+ default:
+ liberator.beep();
+ return false;
+ }
+ return true;
+ },
+
+ // This function will move/select up to given "pos"
+ // Simple setSelectionRange() would be better, but we want to maintain the correct
+ // order of selectionStart/End (a firefox bug always makes selectionStart <= selectionEnd)
+ // Use only for small movements!
+ moveToPosition: function (pos, forward, select)
+ {
+ if (!select)
+ {
+ getEditor().setSelectionRange(pos, pos);
+ return;
+ }
+
+ if (forward)
+ {
+ if (pos <= getEditor().selectionEnd || pos > getEditor().value.length)
+ return false;
+
+ do // TODO: test code for endless loops
+ {
+ this.executeCommand("cmd_selectCharNext", 1);
+ }
+ while (getEditor().selectionEnd != pos);
+ }
+ else
+ {
+ if (pos >= getEditor().selectionStart || pos < 0)
+ return false;
+
+ do // TODO: test code for endless loops
+ {
+ this.executeCommand("cmd_selectCharPrevious", 1);
+ }
+ while (getEditor().selectionStart != pos);
+ }
+ },
+
+ // returns the position of char
+ findCharForward: function (ch, count)
+ {
+ if (!getEditor())
+ return -1;
+
+ lastFindChar = ch;
+ lastFindCharFunc = this.findCharForward;
+
+ var text = getEditor().value;
+ if (!typeof count == "number" || count < 1)
+ count = 1;
+
+ for (let i = getEditor().selectionEnd + 1; i < text.length; i++)
+ {
+ if (text[i] == "\n")
+ break;
+ if (text[i] == ch)
+ count--;
+ if (count == 0)
+ return i + 1; // always position the cursor after the char
+ }
+
+ liberator.beep();
+ return -1;
+ },
+
+ // returns the position of char
+ findCharBackward: function (ch, count)
+ {
+ if (!getEditor())
+ return -1;
+
+ lastFindChar = ch;
+ lastFindCharFunc = this.findCharBackward;
+
+ var text = getEditor().value;
+ if (!typeof count == "number" || count < 1)
+ count = 1;
+
+ for (let i = getEditor().selectionStart - 1; i >= 0; i--)
+ {
+ if (text[i] == "\n")
+ break;
+ if (text[i] == ch)
+ count--;
+ if (count == 0)
+ return i;
+ }
+
+ liberator.beep();
+ return -1;
+ },
+
+ editFileExternally: function (path)
+ {
+ // TODO: save return value in v:shell_error
+ let args = commands.parseArgs(options["editor"], [], "*", true);
+
+ if (args.length < 1)
+ {
+ liberator.echoerr("No editor specified");
+ return;
+ }
+
+ args.push(path);
+ liberator.callFunctionInThread(null, io.run, args.shift(), args, true);
+ },
+
+ // TODO: clean up with 2 functions for textboxes and currentEditor?
+ editFieldExternally: function ()
+ {
+ if (!options["editor"])
+ return false;
+
+ var textBox = null;
+ if (!(config.isComposeWindow))
+ textBox = document.commandDispatcher.focusedElement;
+
+ var text = ""; // XXX
+ if (textBox)
+ text = textBox.value;
+ else if (typeof GetCurrentEditor == "function") // Thunderbird composer
+ text = GetCurrentEditor().outputToString("text/plain", 2);
+ else
+ return false;
+
+ try
+ {
+ var tmpfile = io.createTempFile();
+ }
+ catch (e)
+ {
+ liberator.echoerr("Could not create temporary file: " + e.message);
+ return false;
+ }
+ try
+ {
+ io.writeFile(tmpfile, text);
+ }
+ catch (e)
+ {
+ liberator.echoerr("Could not write to temporary file " + tmpfile.path + ": " + e.message);
+ return false;
+ }
+
+ if (textBox)
+ {
+ textBox.setAttribute("readonly", "true");
+ var oldBg = textBox.style.backgroundColor;
+ var tmpBg = "yellow";
+ textBox.style.backgroundColor = "#bbbbbb";
+ }
+
+ this.editFileExternally(tmpfile.path);
+
+ if (textBox)
+ textBox.removeAttribute("readonly");
+
+ // if (v:shell_error != 0)
+ // {
+ // tmpBg = "red";
+ // liberator.echoerr("External editor returned with exit code " + retcode);
+ // }
+ // else
+ // {
+ try
+ {
+ var val = io.readFile(tmpfile);
+ if (textBox)
+ textBox.value = val;
+ else
+ {
+ //document.getElementById("content-frame").contentDocument.designMode = "on";
+ var editor = GetCurrentEditor();
+ var wholeDocRange = editor.document.createRange();
+ var rootNode = editor.rootElement.QueryInterface(Components.interfaces.nsIDOMNode);
+ wholeDocRange.selectNodeContents(rootNode);
+ editor.selection.addRange(wholeDocRange);
+ editor.selection.deleteFromDocument();
+ editor.insertText(val);
+ //setTimeout(function () {
+ // document.getElementById("content-frame").contentDocument.designMode = "off";
+ //}, 100);
+ }
+ }
+ catch (e)
+ {
+ tmpBg = "red";
+ liberator.echoerr("Could not read from temporary file " + tmpfile.path + ": " + e.message);
+ }
+ // }
+
+ // blink the textbox after returning
+ if (textBox)
+ {
+ var timeout = 100;
+ var colors = [tmpBg, oldBg, tmpBg, oldBg];
+ (function () {
+ textBox.style.backgroundColor = colors.shift();
+ if (colors.length > 0)
+ setTimeout(arguments.callee, timeout);
+ })();
+ }
+
+ tmpfile.remove(false);
+ return true;
+ },
+
+ // Abbreviations {{{
+
+ // filter is i, c or "!" (insert or command abbreviations or both)
+ listAbbreviations: function (filter, lhs)
+ {
+ if (lhs) // list only that one
+ {
+ if (abbrev[lhs])
+ {
+ for (let [,abbr] in Iterator(abbrev[lhs]))
+ {
+ if (abbr[0] == filter)
+ liberator.echo(abbr[0] + " " + lhs + " " + abbr[1]);
+ // Is it me, or is this clearly very wrong? --Kris
+ return true;
+ }
+ }
+ liberator.echoerr("No abbreviations found");
+ return false;
+ }
+ else // list all (for that filter {i,c,!})
+ {
+ var searchFilter = (filter == "!") ? "!ci"
+ : filter + "!"; // ! -> list all, on c or i ! matches too)
+
+ let list = [[rhs[0], lhs, rhs[1]] for ([lhs, rhs] in abbrevs()) if (searchFilter.indexOf(rhs[0]) > -1)];
+
+ if (!list.length)
+ return liberator.echoerr("No abbreviations found");
+ list = template.tabular(["", "LHS", "RHS"], [], list);
+ commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ }
+ },
+
+ // System for adding abbreviations:
+ //
+ // filter == ! delete all, and set first (END)
+ //
+ // if filter == ! remove all and add it as only END
+ //
+ // variant 1: rhs matches anywere in loop
+ //
+ // 1 mod matches anywhere in loop
+ // a) simple replace and
+ // I) (maybe there's another rhs that matches? not possible)
+ // (when there's another item, it's opposite mod with different rhs)
+ // (so do nothing further but END)
+ //
+ // 2 mod does not match
+ // a) the opposite is there -> make a ! and put it as only and END
+ // (b) a ! is there. do nothing END)
+ //
+ // variant 2: rhs matches *no*were in loop and filter is c or i
+ // everykind of current combo is possible to 1 {c,i,!} or two {c and i}
+ //
+ // 1 mod is ! split into two i + c END
+ // 1 not !: opposite mode (first), add/change 'second' and END
+ // 1 not !: same mode (first), overwrite first this END
+ //
+ addAbbreviation: function (filter, lhs, rhs)
+ {
+ if (!abbrev[lhs])
+ {
+ abbrev[lhs] = [];
+ abbrev[lhs][0] = [filter, rhs];
+ return;
+ }
+
+ if (filter == "!")
+ {
+ if (abbrev[lhs][1])
+ abbrev[lhs][1] = "";
+ abbrev[lhs][0] = [filter, rhs];
+ return;
+ }
+
+ for (let i = 0; i < abbrev[lhs].length; i++)
+ {
+ if (abbrev[lhs][i][1] == rhs)
+ {
+ if (abbrev[lhs][i][0] == filter)
+ {
+ abbrev[lhs][i] = [filter, rhs];
+ return;
+ }
+ else
+ {
+ if (abbrev[lhs][i][0] != "!")
+ {
+ if (abbrev[lhs][1])
+ abbrev[lhs][1] = "";
+ abbrev[lhs][0] = ["!", rhs];
+ return;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ if (abbrev[lhs][0][0] == "!")
+ {
+ var tmpOpp = ("i" == filter) ? "c" : "i";
+ abbrev[lhs][1] = [tmpOpp, abbrev[lhs][0][1]];
+ abbrev[lhs][0] = [filter, rhs];
+ return;
+ }
+
+ if (abbrev[lhs][0][0] != filter)
+ abbrev[lhs][1] = [filter, rhs];
+ else
+ abbrev[lhs][0] = [filter, rhs];
+ },
+
+ removeAbbreviation: function (filter, lhs)
+ {
+ if (!lhs)
+ {
+ liberator.echoerr("E474: Invalid argument");
+ return false;
+ }
+
+ if (abbrev[lhs]) // abbrev exists
+ {
+ if (filter == "!")
+ {
+ abbrev[lhs] = "";
+ return true;
+ }
+ else
+ {
+ if (!abbrev[lhs][1]) // only one exists
+ {
+ if (abbrev[lhs][0][0] == "!") // exists as ! -> no 'full' delete
+ {
+ abbrev[lhs][0][0] = (filter == "i") ? "c" : "i"; // ! - i = c; ! - c = i
+ return true;
+ }
+ else if (abbrev[lhs][0][0] == filter)
+ {
+ abbrev[lhs] = "";
+ return true;
+ }
+ }
+ else // two abbrev's exists ( 'i' or 'c' (filter as well))
+ {
+ if (abbrev[lhs][0][0] == "c" && filter == "c")
+ abbrev[lhs][0] = abbrev[lhs][1];
+
+ abbrev[lhs][1] = "";
+
+ return true;
+ }
+ }
+ }
+
+ liberator.echoerr("E24: No such abbreviation");
+ return false;
+ },
+
+ removeAllAbbreviations: function (filter)
+ {
+ if (filter == "!")
+ {
+ abbrev = {};
+ }
+ else
+ {
+ for (let lhs in abbrev)
+ {
+ for (let i = 0; i < abbrev[lhs].length; i++)
+ {
+ if (abbrev[lhs][i][0] == "!" || abbrev[lhs][i][0] == filter)
+ this.removeAbbreviation(filter, lhs);
+ }
+ }
+ }
+ },
+
+ expandAbbreviation: function (filter) // try to find an candidate and replace accordingly
+ {
+ var textbox = getEditor();
+ if (!textbox)
+ return;
+ var text = textbox.value;
+ var currStart = textbox.selectionStart;
+ var currEnd = textbox.selectionEnd;
+ var foundWord = text.substring(0, currStart).replace(/^(.|\n)*?(\S+)$/m, "$2"); // get last word \b word boundary
+ if (!foundWord)
+ return true;
+
+ for (let lhs in abbrev)
+ {
+ for (let i = 0; i < abbrev[lhs].length; i++)
+ {
+ if (lhs == foundWord && (abbrev[lhs][i][0] == filter || abbrev[lhs][i][0] == "!"))
+ {
+ // if found, replace accordingly
+ var len = foundWord.length;
+ var abbrText = abbrev[lhs][i][1];
+ text = text.substring(0, currStart - len) + abbrText + text.substring(currStart);
+ textbox.value = text;
+ textbox.selectionStart = currStart - len + abbrText.length;
+ textbox.selectionEnd = currEnd - len + abbrText.length;
+ break;
+ }
+ }
+ }
+ return true;
+ }
+ //}}}
+ };
+ //}}}
+}; //}}}
+
+// vim: set fdm=marker sw=4 ts=4 et: