1 // Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
9 // command names taken from:
10 // http://developer.mozilla.org/en/docs/Editor_Embedding_Guide
12 /** @instance editor */
13 var Editor = Module("editor", {
14 get isCaret() modes.getStack(1).main == modes.CARET,
15 get isTextEdit() modes.getStack(1).main == modes.TEXT_EDIT,
17 unselectText: function (toEnd) {
19 Editor.getEditor(null).selection[toEnd ? "collapseToEnd" : "collapseToStart"]();
24 selectedText: function () String(Editor.getEditor(null).selection),
26 pasteClipboard: function (clipboard, toStart) {
27 // TODO: I don't think this is needed anymore? --djk
28 if (util.OS.isWindows) {
29 this.executeCommand("cmd_paste");
33 let elem = dactyl.focusedElement;
35 elem = elem.inputField;
37 if (elem.setSelectionRange) {
38 let text = dactyl.clipboardRead(clipboard);
41 if (isinstance(elem, [HTMLInputElement, XULTextBoxElement]))
42 text = text.replace(/\n+/g, "");
44 // This is a hacky fix - but it works.
45 // <s-insert> in the bottom of a long textarea bounces up
46 let top = elem.scrollTop;
47 let left = elem.scrollLeft;
49 let start = elem.selectionStart; // caret position
50 let end = elem.selectionEnd;
51 let value = elem.value.substring(0, start) + text + elem.value.substring(end);
54 if (/^(search|text)$/.test(elem.type))
55 Editor.getEditor(elem).rootElement.firstChild.textContent = value;
57 elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length);
58 elem.selectionEnd = elem.selectionStart;
61 elem.scrollLeft = left;
63 events.dispatch(elem, events.create(elem.ownerDocument, "input"));
67 // count is optional, defaults to 1
68 executeCommand: function (cmd, count) {
69 let editor = Editor.getEditor(null);
70 let controller = Editor.getController();
71 dactyl.assert(callable(cmd) ||
73 controller.supportsCommand(cmd) &&
74 controller.isCommandEnabled(cmd));
76 // XXX: better as a precondition
80 let didCommand = false;
82 // some commands need this try/catch workaround, because a cmd_charPrevious triggered
83 // at the beginning of the textarea, would hang the doCommand()
84 // good thing is, we need this code anyway for proper beeping
87 cmd(editor, controller);
89 controller.doCommand(cmd);
94 dactyl.assert(didCommand);
101 // motion = b, 0, gg, G, etc.
102 selectMotion: function selectMotion(cmd, motion, count) {
103 // XXX: better as a precondition
112 if (modes.main != modes.VISUAL)
113 modes.push(modes.VISUAL);
117 this.executeCommand("cmd_beginLine", 1);
118 this.executeCommand("cmd_selectLineNext", count + 1);
121 this.executeCommand("cmd_beginLine", 1);
122 this.executeCommand("cmd_lineNext", 1);
123 this.executeCommand("cmd_selectLinePrevious", count + 1);
126 this.executeCommand("cmd_selectCharPrevious", count);
129 this.executeCommand("cmd_selectCharNext", count);
133 this.executeCommand("cmd_selectWordNext", count);
136 this.executeCommand("cmd_selectWordPrevious", count);
140 this.executeCommand("cmd_selectBeginLine", 1);
143 this.executeCommand("cmd_selectEndLine", 1);
146 this.executeCommand("cmd_endLine", 1);
147 this.executeCommand("cmd_selectTop", 1);
148 this.executeCommand("cmd_selectBeginLine", 1);
151 this.executeCommand("cmd_beginLine", 1);
152 this.executeCommand("cmd_selectBottom", 1);
153 this.executeCommand("cmd_selectEndLine", 1);
162 // This function will move/select up to given "pos"
163 // Simple setSelectionRange() would be better, but we want to maintain the correct
164 // order of selectionStart/End (a Gecko bug always makes selectionStart <= selectionEnd)
165 // Use only for small movements!
166 moveToPosition: function (pos, forward, select) {
168 Editor.getEditor().setSelectionRange(pos, pos);
173 if (pos <= Editor.getEditor().selectionEnd || pos > Editor.getEditor().value.length)
176 do { // TODO: test code for endless loops
177 this.executeCommand("cmd_selectCharNext", 1);
179 while (Editor.getEditor().selectionEnd != pos);
182 if (pos >= Editor.getEditor().selectionStart || pos < 0)
185 do { // TODO: test code for endless loops
186 this.executeCommand("cmd_selectCharPrevious", 1);
188 while (Editor.getEditor().selectionStart != pos);
192 // returns the position of char
193 findCharForward: function (ch, count) {
194 if (!Editor.getEditor())
197 let text = Editor.getEditor().value;
202 for (let i = Editor.getEditor().selectionEnd + 1; i < text.length; i++) {
208 return i + 1; // always position the cursor after the char
215 // returns the position of char
216 findCharBackward: function (ch, count) {
217 if (!Editor.getEditor())
220 let text = Editor.getEditor().value;
225 for (let i = Editor.getEditor().selectionStart - 1; i >= 0; i--) {
239 * Edits the given file in the external editor as specified by the
242 * @param {object|File|string} args An object specifying the file,
243 * line, and column to edit. If a non-object is specified, it is
244 * treated as the file parameter of the object.
245 * @param {boolean} blocking If true, this function does not return
246 * until the editor exits.
248 editFileExternally: function (args, blocking) {
249 if (!isObject(args) || args instanceof File)
250 args = { file: args };
251 args.file = args.file.path || args.file;
253 let args = options.get("editor").format(args);
255 dactyl.assert(args.length >= 1, _("editor.noEditor"));
257 io.run(args.shift(), args, blocking);
260 // TODO: clean up with 2 functions for textboxes and currentEditor?
261 editFieldExternally: function editFieldExternally(forceEditing) {
262 if (!options["editor"])
265 let textBox = config.isComposeWindow ? null : dactyl.focusedElement;
268 if (!forceEditing && textBox && textBox.type == "password") {
269 commandline.input("Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]): ",
271 if (resp && resp.match(/^y(es)?$/i))
272 editor.editFieldExternally(true);
278 var text = textBox.value;
279 let pre = text.substr(0, textBox.selectionStart);
280 line = 1 + pre.replace(/[^\n]/g, "").length;
281 column = 1 + pre.replace(/[^]*\n/, "").length;
284 var editor = window.GetCurrentEditor ? GetCurrentEditor()
285 : Editor.getEditor(document.commandDispatcher.focusedWindow);
286 dactyl.assert(editor);
287 text = Array.map(editor.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
290 let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
291 let cleanup = util.yieldable(function cleanup(error) {
295 let blink = ["EditorBlink1", "EditorBlink2"];
297 dactyl.reportError(error, true);
298 blink[1] = "EditorError";
301 dactyl.trapErrors(update, null, true);
303 if (tmpfile && tmpfile.exists())
304 tmpfile.remove(false);
307 dactyl.focus(textBox);
308 for (let group in values(blink.concat(blink, ""))) {
309 highlight.highlightNode(textBox, origGroup + " " + group);
315 function update(force) {
316 if (force !== true && tmpfile.lastModifiedTime <= lastUpdate)
318 lastUpdate = Date.now();
320 let val = tmpfile.read();
324 while (editor.rootElement.firstChild)
325 editor.rootElement.removeChild(editor.rootElement.firstChild);
326 editor.rootElement.innerHTML = val;
331 var tmpfile = io.createTempFile();
333 throw Error("Couldn't create temporary file");
336 highlight.highlightNode(textBox, origGroup + " EditorEditing");
340 if (!tmpfile.write(text))
341 throw Error("Input contains characters not valid in the current " +
344 var lastUpdate = Date.now();
346 var timer = services.Timer(update, 100, services.Timer.TYPE_REPEATING_SLACK);
347 this.editFileExternally({ file: tmpfile.path, line: line, column: column }, cleanup);
355 * Expands an abbreviation in the currently active textbox.
357 * @param {string} mode The mode filter.
358 * @see Abbreviation#expand
360 expandAbbreviation: function (mode) {
361 let elem = dactyl.focusedElement;
362 if (!(elem && elem.value))
365 let text = elem.value;
366 let start = elem.selectionStart;
367 let end = elem.selectionEnd;
368 let abbrev = abbreviations.match(mode, text.substring(0, start).replace(/.*\s/g, ""));
370 let len = abbrev.lhs.length;
371 let rhs = abbrev.expand(elem);
372 elem.value = text.substring(0, start - len) + rhs + text.substring(start);
373 elem.selectionStart = start - len + rhs.length;
374 elem.selectionEnd = end - len + rhs.length;
378 extendRange: function extendRange(range, forward, re, sameWord) {
379 function advance(positive) {
380 let idx = range.endOffset;
381 while (idx < text.length && re.test(text[idx++]) == positive)
382 range.setEnd(range.endContainer, idx);
384 function retreat(positive) {
385 let idx = range.startOffset;
386 while (idx > 0 && re.test(text[--idx]) == positive)
387 range.setStart(range.startContainer, idx);
390 let nodeRange = range.cloneRange();
391 nodeRange.selectNodeContents(range.startContainer);
392 let text = String(nodeRange);
407 getEditor: function (elem) {
408 if (arguments.length === 0) {
409 dactyl.assert(dactyl.focusedElement);
410 return dactyl.focusedElement;
414 elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
417 if (elem instanceof Element)
418 return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
420 return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
421 .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession)
422 .getEditorForWindow(elem);
429 getController: function () {
430 let ed = dactyl.focusedElement;
431 if (!ed || !ed.controllers)
434 return ed.controllers.getControllerForCommand("cmd_beginLine");
437 mappings: function () {
439 // add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXT_EDIT mode
440 function addMovementMap(keys, description, hasCount, caretModeMethod, caretModeArg, textEditCommand, visualTextEditCommand) {
443 extraInfo.count = true;
445 function caretExecute(arg, again) {
446 function fixSelection() {
447 sel.removeAllRanges();
448 sel.addRange(RangeFind.endpoint(
449 RangeFind.nodeRange(buffer.focusedFrame.document.documentElement),
453 let controller = buffer.selectionController;
454 let sel = controller.getSelection(controller.SELECTION_NORMAL);
455 if (!sel.rangeCount) // Hack.
459 controller[caretModeMethod](caretModeArg, arg);
462 dactyl.assert(again && e.result === Cr.NS_ERROR_FAILURE);
464 caretExecute(arg, false);
468 mappings.add([modes.CARET], keys, description,
469 function ({ count }) {
474 caretExecute(false, true);
478 mappings.add([modes.VISUAL], keys, description,
479 function ({ count }) {
483 let editor_ = Editor.getEditor(null);
484 let controller = buffer.selectionController;
485 while (count-- && modes.main == modes.VISUAL) {
486 if (editor.isTextEdit) {
487 if (callable(visualTextEditCommand))
488 visualTextEditCommand(editor_);
490 editor.executeCommand(visualTextEditCommand);
493 caretExecute(true, true);
498 mappings.add([modes.TEXT_EDIT], keys, description,
499 function ({ count }) {
503 editor.executeCommand(textEditCommand, count);
508 // add mappings for commands like i,a,s,c,etc. in TEXT_EDIT mode
509 function addBeginInsertModeMap(keys, commands, description) {
510 mappings.add([modes.TEXT_EDIT], keys, description || "",
512 commands.forEach(function (cmd)
513 editor.executeCommand(cmd, 1));
514 modes.push(modes.INSERT);
518 function selectPreviousLine() {
519 editor.executeCommand("cmd_selectLinePrevious");
520 if ((modes.extended & modes.LINE) && !editor.selectedText())
521 editor.executeCommand("cmd_selectLinePrevious");
524 function selectNextLine() {
525 editor.executeCommand("cmd_selectLineNext");
526 if ((modes.extended & modes.LINE) && !editor.selectedText())
527 editor.executeCommand("cmd_selectLineNext");
530 function updateRange(editor, forward, re, modify) {
531 let range = Editor.extendRange(editor.selection.getRangeAt(0),
534 editor.selection.removeAllRanges();
535 editor.selection.addRange(range);
537 function move(forward, re)
538 function _move(editor) {
539 updateRange(editor, forward, re, function (range) { range.collapse(!forward); });
541 function select(forward, re)
542 function _select(editor) {
543 updateRange(editor, forward, re, function (range) {});
545 function beginLine(editor_) {
546 editor.executeCommand("cmd_beginLine");
547 move(true, /\S/)(editor_);
550 // COUNT CARET TEXT_EDIT VISUAL_TEXT_EDIT
551 addMovementMap(["k", "<Up>"], "Move up one line",
552 true, "lineMove", false, "cmd_linePrevious", selectPreviousLine);
553 addMovementMap(["j", "<Down>", "<Return>"], "Move down one line",
554 true, "lineMove", true, "cmd_lineNext", selectNextLine);
555 addMovementMap(["h", "<Left>", "<BS>"], "Move left one character",
556 true, "characterMove", false, "cmd_charPrevious", "cmd_selectCharPrevious");
557 addMovementMap(["l", "<Right>", "<Space>"], "Move right one character",
558 true, "characterMove", true, "cmd_charNext", "cmd_selectCharNext");
559 addMovementMap(["b", "<C-Left>"], "Move left one word",
560 true, "wordMove", false, "cmd_wordPrevious", "cmd_selectWordPrevious");
561 addMovementMap(["w", "<C-Right>"], "Move right one word",
562 true, "wordMove", true, "cmd_wordNext", "cmd_selectWordNext");
563 addMovementMap(["B"], "Move left to the previous white space",
564 true, "wordMove", false, move(false, /\S/), select(false, /\S/));
565 addMovementMap(["W"], "Move right to just beyond the next white space",
566 true, "wordMove", true, move(true, /\S/), select(true, /\S/));
567 addMovementMap(["e"], "Move to the end of the current word",
568 true, "wordMove", true, move(true, /\W/), select(true, /\W/));
569 addMovementMap(["E"], "Move right to the next white space",
570 true, "wordMove", true, move(true, /\s/), select(true, /\s/));
571 addMovementMap(["<C-f>", "<PageDown>"], "Move down one page",
572 true, "pageMove", true, "cmd_movePageDown", "cmd_selectNextPage");
573 addMovementMap(["<C-b>", "<PageUp>"], "Move up one page",
574 true, "pageMove", false, "cmd_movePageUp", "cmd_selectPreviousPage");
575 addMovementMap(["gg", "<C-Home>"], "Move to the start of text",
576 false, "completeMove", false, "cmd_moveTop", "cmd_selectTop");
577 addMovementMap(["G", "<C-End>"], "Move to the end of text",
578 false, "completeMove", true, "cmd_moveBottom", "cmd_selectBottom");
579 addMovementMap(["0", "<Home>"], "Move to the beginning of the line",
580 false, "intraLineMove", false, "cmd_beginLine", "cmd_selectBeginLine");
581 addMovementMap(["^"], "Move to the first non-whitespace character of the line",
582 false, "intraLineMove", false, beginLine, "cmd_selectBeginLine");
583 addMovementMap(["$", "<End>"], "Move to the end of the current line",
584 false, "intraLineMove", true, "cmd_endLine" , "cmd_selectEndLine");
586 addBeginInsertModeMap(["i", "<Insert>"], [], "Insert text before the cursor");
587 addBeginInsertModeMap(["a"], ["cmd_charNext"], "Append text after the cursor");
588 addBeginInsertModeMap(["I"], ["cmd_beginLine"], "Insert text at the beginning of the line");
589 addBeginInsertModeMap(["A"], ["cmd_endLine"], "Append text at the end of the line");
590 addBeginInsertModeMap(["s"], ["cmd_deleteCharForward"], "Delete the character in front of the cursor and start insert");
591 addBeginInsertModeMap(["S"], ["cmd_deleteToEndOfLine", "cmd_deleteToBeginningOfLine"], "Delete the current line and start insert");
592 addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"], "Delete from the cursor to the end of the line and start insert");
594 function addMotionMap(key, desc, cmd, mode) {
595 mappings.add([modes.TEXT_EDIT], [key],
597 function ({ count, motion }) {
598 editor.selectMotion(key, motion, Math.max(count, 1));
600 cmd.call(events, Editor.getEditor(null));
602 editor.executeCommand(cmd, 1);
603 modes.pop(modes.TEXT_EDIT);
608 { count: true, motion: true });
611 addMotionMap("d", "Delete motion", "cmd_delete");
612 addMotionMap("c", "Change motion", "cmd_delete", modes.INSERT);
613 addMotionMap("y", "Yank motion", "cmd_copy");
615 mappings.add([modes.INPUT],
616 ["<C-w>"], "Delete previous word",
617 function () { editor.executeCommand("cmd_deleteWordBackward", 1); });
619 mappings.add([modes.INPUT],
620 ["<C-u>"], "Delete until beginning of current line",
622 // Deletes the whole line. What the hell.
623 // editor.executeCommand("cmd_deleteToBeginningOfLine", 1);
625 editor.executeCommand("cmd_selectBeginLine", 1);
626 if (Editor.getController().isCommandEnabled("cmd_delete"))
627 editor.executeCommand("cmd_delete", 1);
630 mappings.add([modes.INPUT],
631 ["<C-k>"], "Delete until end of current line",
632 function () { editor.executeCommand("cmd_deleteToEndOfLine", 1); });
634 mappings.add([modes.INPUT],
635 ["<C-a>"], "Move cursor to beginning of current line",
636 function () { editor.executeCommand("cmd_beginLine", 1); });
638 mappings.add([modes.INPUT],
639 ["<C-e>"], "Move cursor to end of current line",
640 function () { editor.executeCommand("cmd_endLine", 1); });
642 mappings.add([modes.INPUT],
643 ["<C-h>"], "Delete character to the left",
644 function () { events.feedkeys("<BS>", true); });
646 mappings.add([modes.INPUT],
647 ["<C-d>"], "Delete character to the right",
648 function () { editor.executeCommand("cmd_deleteCharForward", 1); });
650 mappings.add([modes.INPUT],
651 ["<S-Insert>"], "Insert clipboard/selection",
652 function () { editor.pasteClipboard(); });
654 mappings.add([modes.INPUT, modes.TEXT_EDIT],
655 ["<C-i>"], "Edit text field with an external editor",
656 function () { editor.editFieldExternally(); });
658 mappings.add([modes.INPUT],
659 ["<C-t>"], "Edit text field in Vi mode",
661 dactyl.assert(!editor.isTextEdit);
662 modes.push(modes.TEXT_EDIT);
665 mappings.add([modes.INSERT],
666 ["<Space>", "<Return>"], "Expand insert mode abbreviation",
668 editor.expandAbbreviation(modes.INSERT);
672 mappings.add([modes.INSERT],
673 ["<C-]>", "<C-5>"], "Expand insert mode abbreviation",
674 function () { editor.expandAbbreviation(modes.INSERT); });
677 mappings.add([modes.TEXT_EDIT],
678 ["u"], "Undo changes",
680 editor.executeCommand("cmd_undo", Math.max(args.count, 1));
681 editor.unselectText();
685 mappings.add([modes.TEXT_EDIT],
686 ["<C-r>"], "Redo undone changes",
688 editor.executeCommand("cmd_redo", Math.max(args.count, 1));
689 editor.unselectText();
693 mappings.add([modes.TEXT_EDIT],
694 ["D"], "Delete the characters under the cursor until the end of the line",
695 function () { editor.executeCommand("cmd_deleteToEndOfLine"); });
697 mappings.add([modes.TEXT_EDIT],
698 ["o"], "Open line below current",
700 editor.executeCommand("cmd_endLine", 1);
701 modes.push(modes.INSERT);
702 events.feedkeys("<Return>");
705 mappings.add([modes.TEXT_EDIT],
706 ["O"], "Open line above current",
708 editor.executeCommand("cmd_beginLine", 1);
709 modes.push(modes.INSERT);
710 events.feedkeys("<Return>");
711 editor.executeCommand("cmd_linePrevious", 1);
714 mappings.add([modes.TEXT_EDIT],
715 ["X"], "Delete character to the left",
716 function (args) { editor.executeCommand("cmd_deleteCharBackward", Math.max(args.count, 1)); },
719 mappings.add([modes.TEXT_EDIT],
720 ["x"], "Delete character to the right",
721 function (args) { editor.executeCommand("cmd_deleteCharForward", Math.max(args.count, 1)); },
725 mappings.add([modes.CARET, modes.TEXT_EDIT],
726 ["v"], "Start visual mode",
727 function () { modes.push(modes.VISUAL); });
729 mappings.add([modes.VISUAL],
730 ["v", "V"], "End visual mode",
731 function () { modes.pop(); });
733 mappings.add([modes.TEXT_EDIT],
734 ["V"], "Start visual line mode",
736 modes.push(modes.VISUAL, modes.LINE);
737 editor.executeCommand("cmd_beginLine", 1);
738 editor.executeCommand("cmd_selectLineNext", 1);
741 mappings.add([modes.VISUAL],
742 ["c", "s"], "Change selected text",
744 dactyl.assert(editor.isTextEdit);
745 editor.executeCommand("cmd_cut");
746 modes.push(modes.INSERT);
749 mappings.add([modes.VISUAL],
750 ["d", "x"], "Delete selected text",
752 dactyl.assert(editor.isTextEdit);
753 editor.executeCommand("cmd_cut");
756 mappings.add([modes.VISUAL],
757 ["y"], "Yank selected text",
759 if (editor.isTextEdit) {
760 editor.executeCommand("cmd_copy");
764 dactyl.clipboardWrite(buffer.currentWord, true);
767 mappings.add([modes.VISUAL, modes.TEXT_EDIT],
768 ["p"], "Paste clipboard contents",
769 function ({ count }) {
770 dactyl.assert(!editor.isCaret);
771 editor.executeCommand("cmd_paste", count || 1);
772 modes.pop(modes.TEXT_EDIT);
776 // finding characters
777 mappings.add([modes.TEXT_EDIT, modes.VISUAL],
778 ["f"], "Move to a character on the current line after the cursor",
779 function ({ arg, count }) {
780 let pos = editor.findCharForward(arg, Math.max(count, 1));
782 editor.moveToPosition(pos, true, modes.main == modes.VISUAL);
784 { arg: true, count: true });
786 mappings.add([modes.TEXT_EDIT, modes.VISUAL],
787 ["F"], "Move to a character on the current line before the cursor",
788 function ({ arg, count }) {
789 let pos = editor.findCharBackward(arg, Math.max(count, 1));
791 editor.moveToPosition(pos, false, modes.main == modes.VISUAL);
793 { arg: true, count: true });
795 mappings.add([modes.TEXT_EDIT, modes.VISUAL],
796 ["t"], "Move before a character on the current line",
797 function ({ arg, count }) {
798 let pos = editor.findCharForward(arg, Math.max(count, 1));
800 editor.moveToPosition(pos - 1, true, modes.main == modes.VISUAL);
802 { arg: true, count: true });
804 mappings.add([modes.TEXT_EDIT, modes.VISUAL],
805 ["T"], "Move before a character on the current line, backwards",
806 function ({ arg, count }) {
807 let pos = editor.findCharBackward(arg, Math.max(count, 1));
809 editor.moveToPosition(pos + 1, false, modes.main == modes.VISUAL);
811 { arg: true, count: true });
813 // text edit and visual mode
814 mappings.add([modes.TEXT_EDIT, modes.VISUAL],
815 ["~"], "Switch case of the character under the cursor and move the cursor to the right",
816 function ({ count }) {
817 if (modes.main == modes.VISUAL)
818 count = Editor.getEditor().selectionEnd - Editor.getEditor().selectionStart;
819 count = Math.max(count, 1);
821 // FIXME: do this in one pass?
822 while (count-- > 0) {
823 let text = Editor.getEditor().value;
824 let pos = Editor.getEditor().selectionStart;
825 dactyl.assert(pos < text.length);
828 Editor.getEditor().value = text.substring(0, pos) +
829 (chr == chr.toLocaleLowerCase() ? chr.toLocaleUpperCase() : chr.toLocaleLowerCase()) +
830 text.substring(pos + 1);
831 editor.moveToPosition(pos + 1, true, false);
833 modes.pop(modes.TEXT_EDIT);
837 function bind() mappings.add.apply(mappings,
838 [[modes.AUTOCOMPLETE]].concat(Array.slice(arguments)))
840 bind(["<Esc>"], "Return to INSERT mode",
841 function () Events.PASS_THROUGH);
843 bind(["<C-[>"], "Return to INSERT mode",
844 function () { events.feedkeys("<Esc>", { skipmap: true }); });
846 bind(["<Up>"], "Select the previous autocomplete result",
847 function () Events.PASS_THROUGH);
849 bind(["<C-p>"], "Select the previous autocomplete result",
850 function () { events.feedkeys("<Up>", { skipmap: true }); });
852 bind(["<Down>"], "Select the next autocomplete result",
853 function () Events.PASS_THROUGH);
855 bind(["<C-n>"], "Select the next autocomplete result",
856 function () { events.feedkeys("<Down>", { skipmap: true }); });
859 options: function () {
860 options.add(["editor"],
861 "The external text editor",
862 "string", "gvim -f +<line> <file>", {
863 format: function (obj, value) {
864 let args = commands.parseArgs(value || this.value, { argCount: "*", allowUnknownOptions: true })
865 .map(util.compileMacro).filter(function (fmt) fmt.valid(obj))
866 .map(function (fmt) fmt(obj));
867 if (obj["file"] && !this.has("file"))
868 args.push(obj["file"]);
871 has: function (key) set.has(util.compileMacro(this.value).seen, key),
872 validator: function (value) {
873 this.format({}, value);
874 return Object.keys(util.compileMacro(value).seen).every(function (k) ["column", "file", "line"].indexOf(k) >= 0);
878 options.add(["insertmode", "im"],
879 "Enter Insert mode rather than Text Edit mode when focusing text areas",
884 // vim: set fdm=marker sw=4 ts=4 et: