1 // Copyright (c) 2008-2011 Kris Maglione <maglione.k at Gmail>
2 // Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
4 // This work is licensed for reuse under an MIT license. Details are
5 // given in the LICENSE.txt file included with this file.
10 // command names taken from:
11 // http://developer.mozilla.org/en/docs/Editor_Embedding_Guide
13 /** @instance editor */
14 var Editor = Module("editor", {
15 get isCaret() modes.getStack(1).main == modes.CARET,
16 get isTextEdit() modes.getStack(1).main == modes.TEXT_EDIT,
18 unselectText: function (toEnd) {
20 Editor.getEditor(null).selection[toEnd ? "collapseToEnd" : "collapseToStart"]();
25 selectedText: function () String(Editor.getEditor(null).selection),
27 pasteClipboard: function (clipboard, toStart) {
28 let elem = dactyl.focusedElement;
30 elem = elem.inputField;
32 if (elem.setSelectionRange) {
33 let text = dactyl.clipboardRead(clipboard);
36 if (isinstance(elem, [HTMLInputElement, XULTextBoxElement]))
37 text = text.replace(/\n+/g, "");
39 // This is a hacky fix - but it works.
40 // <s-insert> in the bottom of a long textarea bounces up
41 let top = elem.scrollTop;
42 let left = elem.scrollLeft;
44 let start = elem.selectionStart; // caret position
45 let end = elem.selectionEnd;
46 let value = elem.value.substring(0, start) + text + elem.value.substring(end);
49 if (/^(search|text)$/.test(elem.type))
50 Editor.getEditor(elem).rootElement.firstChild.textContent = value;
52 elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length);
53 elem.selectionEnd = elem.selectionStart;
56 elem.scrollLeft = left;
58 events.dispatch(elem, events.create(elem.ownerDocument, "input"));
62 // count is optional, defaults to 1
63 executeCommand: function (cmd, count) {
64 let editor = Editor.getEditor(null);
65 let controller = Editor.getController();
66 dactyl.assert(callable(cmd) ||
68 controller.supportsCommand(cmd) &&
69 controller.isCommandEnabled(cmd));
71 // XXX: better as a precondition
75 let didCommand = false;
77 // some commands need this try/catch workaround, because a cmd_charPrevious triggered
78 // at the beginning of the textarea, would hang the doCommand()
79 // good thing is, we need this code anyway for proper beeping
82 cmd(editor, controller);
84 controller.doCommand(cmd);
89 dactyl.assert(didCommand);
96 // motion = b, 0, gg, G, etc.
97 selectMotion: function selectMotion(cmd, motion, count) {
98 // XXX: better as a precondition
107 if (modes.main != modes.VISUAL)
108 modes.push(modes.VISUAL);
112 this.executeCommand("cmd_beginLine", 1);
113 this.executeCommand("cmd_selectLineNext", count + 1);
116 this.executeCommand("cmd_beginLine", 1);
117 this.executeCommand("cmd_lineNext", 1);
118 this.executeCommand("cmd_selectLinePrevious", count + 1);
121 this.executeCommand("cmd_selectCharPrevious", count);
124 this.executeCommand("cmd_selectCharNext", count);
128 this.executeCommand("cmd_selectWordNext", count);
131 this.executeCommand("cmd_selectWordPrevious", count);
135 this.executeCommand("cmd_selectBeginLine", 1);
138 this.executeCommand("cmd_selectEndLine", 1);
141 this.executeCommand("cmd_endLine", 1);
142 this.executeCommand("cmd_selectTop", 1);
143 this.executeCommand("cmd_selectBeginLine", 1);
146 this.executeCommand("cmd_beginLine", 1);
147 this.executeCommand("cmd_selectBottom", 1);
148 this.executeCommand("cmd_selectEndLine", 1);
157 // This function will move/select up to given "pos"
158 // Simple setSelectionRange() would be better, but we want to maintain the correct
159 // order of selectionStart/End (a Gecko bug always makes selectionStart <= selectionEnd)
160 // Use only for small movements!
161 moveToPosition: function (pos, forward, select) {
163 Editor.getEditor().setSelectionRange(pos, pos);
168 if (pos <= Editor.getEditor().selectionEnd || pos > Editor.getEditor().value.length)
171 do { // TODO: test code for endless loops
172 this.executeCommand("cmd_selectCharNext", 1);
174 while (Editor.getEditor().selectionEnd != pos);
177 if (pos >= Editor.getEditor().selectionStart || pos < 0)
180 do { // TODO: test code for endless loops
181 this.executeCommand("cmd_selectCharPrevious", 1);
183 while (Editor.getEditor().selectionStart != pos);
187 findChar: function (key, count, backward) {
189 let editor = Editor.getEditor();
197 let code = events.fromString(key)[0].charCode;
199 let char = String.fromCharCode(code);
201 let text = editor.value;
202 let caret = editor.selectionEnd;
204 let end = text.lastIndexOf("\n", caret);
205 while (caret > end && caret >= 0 && count--)
206 caret = text.lastIndexOf(char, caret - 1);
209 let end = text.indexOf("\n", caret);
213 while (caret < end && caret >= 0 && count--)
214 caret = text.indexOf(char, caret + 1);
225 * Edits the given file in the external editor as specified by the
228 * @param {object|File|string} args An object specifying the file, line,
229 * and column to edit. If a non-object is specified, it is treated as
230 * the file parameter of the object.
231 * @param {boolean} blocking If true, this function does not return
232 * until the editor exits.
234 editFileExternally: function (args, blocking) {
235 if (!isObject(args) || args instanceof File)
236 args = { file: args };
237 args.file = args.file.path || args.file;
239 let args = options.get("editor").format(args);
241 dactyl.assert(args.length >= 1, _("option.notSet", "editor"));
243 io.run(args.shift(), args, blocking);
246 // TODO: clean up with 2 functions for textboxes and currentEditor?
247 editFieldExternally: function editFieldExternally(forceEditing) {
248 if (!options["editor"])
251 let textBox = config.isComposeWindow ? null : dactyl.focusedElement;
254 if (!forceEditing && textBox && textBox.type == "password") {
255 commandline.input(_("editor.prompt.editPassword") + " ",
257 if (resp && resp.match(/^y(es)?$/i))
258 editor.editFieldExternally(true);
264 var text = textBox.value;
265 let pre = text.substr(0, textBox.selectionStart);
266 line = 1 + pre.replace(/[^\n]/g, "").length;
267 column = 1 + pre.replace(/[^]*\n/, "").length;
270 var editor_ = window.GetCurrentEditor ? GetCurrentEditor()
271 : Editor.getEditor(document.commandDispatcher.focusedWindow);
272 dactyl.assert(editor_);
273 text = Array.map(editor_.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
276 let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
277 let cleanup = util.yieldable(function cleanup(error) {
281 let blink = ["EditorBlink1", "EditorBlink2"];
283 dactyl.reportError(error, true);
284 blink[1] = "EditorError";
287 dactyl.trapErrors(update, null, true);
289 if (tmpfile && tmpfile.exists())
290 tmpfile.remove(false);
293 dactyl.focus(textBox);
294 for (let group in values(blink.concat(blink, ""))) {
295 highlight.highlightNode(textBox, origGroup + " " + group);
301 function update(force) {
302 if (force !== true && tmpfile.lastModifiedTime <= lastUpdate)
304 lastUpdate = Date.now();
306 let val = tmpfile.read();
310 textBox.setAttributeNS(NS, "modifiable", true);
311 util.computedStyle(textBox).MozUserInput;
312 events.dispatch(textBox, events.create(textBox.ownerDocument, "input", {}));
313 textBox.removeAttributeNS(NS, "modifiable");
316 while (editor_.rootElement.firstChild)
317 editor_.rootElement.removeChild(editor_.rootElement.firstChild);
318 editor_.rootElement.innerHTML = val;
323 var tmpfile = io.createTempFile();
325 throw Error(_("io.cantCreateTempFile"));
328 highlight.highlightNode(textBox, origGroup + " EditorEditing");
332 if (!tmpfile.write(text))
333 throw Error(_("io.cantEncode"));
335 var lastUpdate = Date.now();
337 var timer = services.Timer(update, 100, services.Timer.TYPE_REPEATING_SLACK);
338 this.editFileExternally({ file: tmpfile.path, line: line, column: column }, cleanup);
346 * Expands an abbreviation in the currently active textbox.
348 * @param {string} mode The mode filter.
349 * @see Abbreviation#expand
351 expandAbbreviation: function (mode) {
352 let elem = dactyl.focusedElement;
353 if (!(elem && elem.value))
356 let text = elem.value;
357 let start = elem.selectionStart;
358 let end = elem.selectionEnd;
359 let abbrev = abbreviations.match(mode, text.substring(0, start).replace(/.*\s/g, ""));
361 let len = abbrev.lhs.length;
362 let rhs = abbrev.expand(elem);
363 elem.value = text.substring(0, start - len) + rhs + text.substring(start);
364 elem.selectionStart = start - len + rhs.length;
365 elem.selectionEnd = end - len + rhs.length;
369 extendRange: function extendRange(range, forward, re, sameWord) {
370 function advance(positive) {
371 let idx = range.endOffset;
372 while (idx < text.length && re.test(text[idx++]) == positive)
373 range.setEnd(range.endContainer, idx);
375 function retreat(positive) {
376 let idx = range.startOffset;
377 while (idx > 0 && re.test(text[--idx]) == positive)
378 range.setStart(range.startContainer, idx);
381 let nodeRange = range.cloneRange();
382 nodeRange.selectNodeContents(range.startContainer);
383 let text = String(nodeRange);
398 getEditor: function (elem) {
399 if (arguments.length === 0) {
400 dactyl.assert(dactyl.focusedElement);
401 return dactyl.focusedElement;
405 elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
409 if (elem instanceof Element)
410 return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
411 return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
412 .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession)
413 .getEditorForWindow(elem);
420 getController: function () {
421 let ed = dactyl.focusedElement;
422 if (!ed || !ed.controllers)
425 return ed.controllers.getControllerForCommand("cmd_beginLine");
428 mappings: function () {
430 // add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXT_EDIT mode
431 function addMovementMap(keys, description, hasCount, caretModeMethod, caretModeArg, textEditCommand, visualTextEditCommand) {
434 extraInfo.count = true;
436 function caretExecute(arg, again) {
437 function fixSelection() {
438 sel.removeAllRanges();
439 sel.addRange(RangeFind.endpoint(
440 RangeFind.nodeRange(buffer.focusedFrame.document.documentElement),
444 let controller = buffer.selectionController;
445 let sel = controller.getSelection(controller.SELECTION_NORMAL);
446 if (!sel.rangeCount) // Hack.
450 controller[caretModeMethod](caretModeArg, arg);
453 dactyl.assert(again && e.result === Cr.NS_ERROR_FAILURE);
455 caretExecute(arg, false);
459 mappings.add([modes.CARET], keys, description,
460 function ({ count }) {
465 caretExecute(false, true);
469 mappings.add([modes.VISUAL], keys, description,
470 function ({ count }) {
474 let editor_ = Editor.getEditor(null);
475 let controller = buffer.selectionController;
476 while (count-- && modes.main == modes.VISUAL) {
477 if (editor.isTextEdit) {
478 if (callable(visualTextEditCommand))
479 visualTextEditCommand(editor_);
481 editor.executeCommand(visualTextEditCommand);
484 caretExecute(true, true);
489 mappings.add([modes.TEXT_EDIT], keys, description,
490 function ({ count }) {
494 editor.executeCommand(textEditCommand, count);
499 // add mappings for commands like i,a,s,c,etc. in TEXT_EDIT mode
500 function addBeginInsertModeMap(keys, commands, description) {
501 mappings.add([modes.TEXT_EDIT], keys, description || "",
503 commands.forEach(function (cmd)
504 editor.executeCommand(cmd, 1));
505 modes.push(modes.INSERT);
509 function selectPreviousLine() {
510 editor.executeCommand("cmd_selectLinePrevious");
511 if ((modes.extended & modes.LINE) && !editor.selectedText())
512 editor.executeCommand("cmd_selectLinePrevious");
515 function selectNextLine() {
516 editor.executeCommand("cmd_selectLineNext");
517 if ((modes.extended & modes.LINE) && !editor.selectedText())
518 editor.executeCommand("cmd_selectLineNext");
521 function updateRange(editor, forward, re, modify) {
522 let range = Editor.extendRange(editor.selection.getRangeAt(0),
525 editor.selection.removeAllRanges();
526 editor.selection.addRange(range);
528 function move(forward, re)
529 function _move(editor) {
530 updateRange(editor, forward, re, function (range) { range.collapse(!forward); });
532 function select(forward, re)
533 function _select(editor) {
534 updateRange(editor, forward, re, function (range) {});
536 function beginLine(editor_) {
537 editor.executeCommand("cmd_beginLine");
538 move(true, /\S/)(editor_);
541 // COUNT CARET TEXT_EDIT VISUAL_TEXT_EDIT
542 addMovementMap(["k", "<Up>"], "Move up one line",
543 true, "lineMove", false, "cmd_linePrevious", selectPreviousLine);
544 addMovementMap(["j", "<Down>", "<Return>"], "Move down one line",
545 true, "lineMove", true, "cmd_lineNext", selectNextLine);
546 addMovementMap(["h", "<Left>", "<BS>"], "Move left one character",
547 true, "characterMove", false, "cmd_charPrevious", "cmd_selectCharPrevious");
548 addMovementMap(["l", "<Right>", "<Space>"], "Move right one character",
549 true, "characterMove", true, "cmd_charNext", "cmd_selectCharNext");
550 addMovementMap(["b", "<C-Left>"], "Move left one word",
551 true, "wordMove", false, "cmd_wordPrevious", "cmd_selectWordPrevious");
552 addMovementMap(["w", "<C-Right>"], "Move right one word",
553 true, "wordMove", true, "cmd_wordNext", "cmd_selectWordNext");
554 addMovementMap(["B"], "Move left to the previous white space",
555 true, "wordMove", false, move(false, /\S/), select(false, /\S/));
556 addMovementMap(["W"], "Move right to just beyond the next white space",
557 true, "wordMove", true, move(true, /\S/), select(true, /\S/));
558 addMovementMap(["e"], "Move to the end of the current word",
559 true, "wordMove", true, move(true, /\W/), select(true, /\W/));
560 addMovementMap(["E"], "Move right to the next white space",
561 true, "wordMove", true, move(true, /\s/), select(true, /\s/));
562 addMovementMap(["<C-f>", "<PageDown>"], "Move down one page",
563 true, "pageMove", true, "cmd_movePageDown", "cmd_selectNextPage");
564 addMovementMap(["<C-b>", "<PageUp>"], "Move up one page",
565 true, "pageMove", false, "cmd_movePageUp", "cmd_selectPreviousPage");
566 addMovementMap(["gg", "<C-Home>"], "Move to the start of text",
567 false, "completeMove", false, "cmd_moveTop", "cmd_selectTop");
568 addMovementMap(["G", "<C-End>"], "Move to the end of text",
569 false, "completeMove", true, "cmd_moveBottom", "cmd_selectBottom");
570 addMovementMap(["0", "<Home>"], "Move to the beginning of the line",
571 false, "intraLineMove", false, "cmd_beginLine", "cmd_selectBeginLine");
572 addMovementMap(["^"], "Move to the first non-whitespace character of the line",
573 false, "intraLineMove", false, beginLine, "cmd_selectBeginLine");
574 addMovementMap(["$", "<End>"], "Move to the end of the current line",
575 false, "intraLineMove", true, "cmd_endLine" , "cmd_selectEndLine");
577 addBeginInsertModeMap(["i", "<Insert>"], [], "Insert text before the cursor");
578 addBeginInsertModeMap(["a"], ["cmd_charNext"], "Append text after the cursor");
579 addBeginInsertModeMap(["I"], ["cmd_beginLine"], "Insert text at the beginning of the line");
580 addBeginInsertModeMap(["A"], ["cmd_endLine"], "Append text at the end of the line");
581 addBeginInsertModeMap(["s"], ["cmd_deleteCharForward"], "Delete the character in front of the cursor and start insert");
582 addBeginInsertModeMap(["S"], ["cmd_deleteToEndOfLine", "cmd_deleteToBeginningOfLine"], "Delete the current line and start insert");
583 addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"], "Delete from the cursor to the end of the line and start insert");
585 function addMotionMap(key, desc, cmd, mode) {
586 mappings.add([modes.TEXT_EDIT], [key],
588 function ({ count, motion }) {
589 editor.selectMotion(key, motion, Math.max(count, 1));
591 cmd.call(events, Editor.getEditor(null));
593 editor.executeCommand(cmd, 1);
594 modes.pop(modes.TEXT_EDIT);
599 { count: true, motion: true });
602 addMotionMap("d", "Delete motion", "cmd_delete");
603 addMotionMap("c", "Change motion", "cmd_delete", modes.INSERT);
604 addMotionMap("y", "Yank motion", "cmd_copy");
606 mappings.add([modes.INPUT],
607 ["<C-w>"], "Delete previous word",
608 function () { editor.executeCommand("cmd_deleteWordBackward", 1); });
610 mappings.add([modes.INPUT],
611 ["<C-u>"], "Delete until beginning of current line",
613 // Deletes the whole line. What the hell.
614 // editor.executeCommand("cmd_deleteToBeginningOfLine", 1);
616 editor.executeCommand("cmd_selectBeginLine", 1);
617 if (Editor.getController().isCommandEnabled("cmd_delete"))
618 editor.executeCommand("cmd_delete", 1);
621 mappings.add([modes.INPUT],
622 ["<C-k>"], "Delete until end of current line",
623 function () { editor.executeCommand("cmd_deleteToEndOfLine", 1); });
625 mappings.add([modes.INPUT],
626 ["<C-a>"], "Move cursor to beginning of current line",
627 function () { editor.executeCommand("cmd_beginLine", 1); });
629 mappings.add([modes.INPUT],
630 ["<C-e>"], "Move cursor to end of current line",
631 function () { editor.executeCommand("cmd_endLine", 1); });
633 mappings.add([modes.INPUT],
634 ["<C-h>"], "Delete character to the left",
635 function () { events.feedkeys("<BS>", true); });
637 mappings.add([modes.INPUT],
638 ["<C-d>"], "Delete character to the right",
639 function () { editor.executeCommand("cmd_deleteCharForward", 1); });
641 mappings.add([modes.INPUT],
642 ["<S-Insert>"], "Insert clipboard/selection",
643 function () { editor.pasteClipboard(); });
645 mappings.add([modes.INPUT, modes.TEXT_EDIT],
646 ["<C-i>"], "Edit text field with an external editor",
647 function () { editor.editFieldExternally(); });
649 mappings.add([modes.INPUT],
650 ["<C-t>"], "Edit text field in Vi mode",
652 dactyl.assert(dactyl.focusedElement);
653 dactyl.assert(!editor.isTextEdit);
654 modes.push(modes.TEXT_EDIT);
658 mappings.add([modes.INPUT, modes.CARET],
659 ["<*-CR>", "<*-BS>", "<*-Del>", "<*-Left>", "<*-Right>", "<*-Up>", "<*-Down>",
660 "<*-Home>", "<*-End>", "<*-PageUp>", "<*-PageDown>",
661 "<M-c>", "<M-v>", "<*-Tab>"],
662 "Handled by " + config.host,
663 function () Events.PASS_THROUGH);
665 mappings.add([modes.INSERT],
666 ["<Space>", "<Return>"], "Expand Insert mode abbreviation",
668 editor.expandAbbreviation(modes.INSERT);
669 return Events.PASS_THROUGH;
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.findChar(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.findChar(arg, Math.max(count, 1), true);
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.findChar(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.findChar(arg, Math.max(count, 1), true);
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> +"sil! call cursor(0, <column>)" <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: