1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-01-06 21:04:12 +01:00

Add gu, gU. Fix some editor issues and annoyances. Better undo support. Don't push TEXT_EDIT mode multiple times with noinsertmode set. Update NEWS.

This commit is contained in:
Kris Maglione
2011-10-04 22:45:59 -04:00
parent 3296675dcc
commit 603b05ce18
4 changed files with 218 additions and 120 deletions

View File

@@ -46,8 +46,28 @@ var Editor = Module("editor", {
this.selection.focusOffset); this.selection.focusOffset);
}, },
get selectionRange() {
if (!this.selection.rangeCount) {
let range = RangeFind.nodeContents(this.editor.rootElement.ownerDocument);
range.collapse(true);
this.selectionRange = range;
}
return this.selection.getRangeAt(0);
},
set selectionRange(range) {
this.selection.removeAllRanges();
if (range != null)
this.selection.addRange(range);
},
get selectedText() String(this.selection), get selectedText() String(this.selection),
get preserveSelection() this.editor && !this.editor.shouldTxnSetSelection,
set preserveSelection(val) {
if (this.editor)
this.editor.setShouldTxnSetSelection(!val);
},
pasteClipboard: function (clipboard, toStart) { pasteClipboard: function (clipboard, toStart) {
let elem = this.element; let elem = this.element;
@@ -69,7 +89,7 @@ var Editor = Module("editor", {
elem.value = value; elem.value = value;
if (/^(search|text)$/.test(elem.type)) if (/^(search|text)$/.test(elem.type))
DOM(elem).rootElement.firstChild.textContent = value; DOM(elem).editor.rootElement.firstChild.textContent = value;
elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length); elem.selectionStart = Math.min(start + (toStart ? 0 : text.length), elem.value.length);
elem.selectionEnd = elem.selectionStart; elem.selectionEnd = elem.selectionStart;
@@ -118,6 +138,51 @@ var Editor = Module("editor", {
this.selection[select ? "extend" : "collapse"](node, pos); this.selection[select ? "extend" : "collapse"](node, pos);
}, },
mungeRange: function mungeRange(range, munger, selectEnd) {
let { editor } = this;
editor.beginPlaceHolderTransaction(null);
let [container, offset] = ["startContainer", "startOffset"];
if (selectEnd)
[container, offset] = ["endContainer", "endOffset"];
try {
// :(
let idx = range[offset];
let parent = range[container].parentNode;
let parentIdx = Array.indexOf(parent.childNodes,
range[container]);
for (let node in Editor.TextsIterator(range)) {
let text = node.textContent;
let start = 0, end = text.length;
if (node == range.startContainer)
start = range.startOffset;
if (node == range.endContainer)
end = range.endOffset;
if (start != 0 || end != text.length)
text = text.slice(0, start)
+ munger(text.slice(start, end))
+ text.slice(end);
else
text = munger(text);
if (editor instanceof Ci.nsIPlaintextEditor) {
this.selectionRange = RangeFind.nodeContents(node);
editor.insertText(text);
}
else
node.textContent = text;
}
this.selection.collapse(parent.childNodes[parentIdx], idx);
}
finally {
editor.endPlaceHolderTransaction();
}
},
findChar: function (key, count, backward) { findChar: function (key, count, backward) {
util.assert(DOM(this.element).isInput); util.assert(DOM(this.element).isInput);
@@ -322,35 +387,58 @@ var Editor = Module("editor", {
}, },
}, { }, {
TextsIterator: Class("TextsIterator", { TextsIterator: Class("TextsIterator", {
init: function init(root, context) { init: function init(range, context, after) {
this.context = context; this.after = after;
this.root = root; this.start = context || range[after ? "endContainer" : "startContainer"];
if (after)
this.context = this.start;
this.range = range;
},
__iterator__: function __iterator__() {
while (this.nextNode())
yield this.context;
}, },
prevNode: function prevNode() { prevNode: function prevNode() {
if (this.context == this.root) if (!this.context)
return null; return this.context = this.start;
var node = this.context;
if (!this.after)
node = node.previousSibling;
var node = this.context.previousSibling;
if (!node) if (!node)
node = this.context.parentNode; node = this.context.parentNode;
else else
while (node.lastChild) while (node.lastChild)
node = node.lastChild; node = node.lastChild;
if (!node || !RangeFind.containsNode(this.range, node, true))
return null;
this.after = false;
return this.context = node; return this.context = node;
}, },
nextNode: function nextNode() { nextNode: function nextNode() {
var node = this.context.firstChild; if (!this.context)
return this.context = this.start;
if (!this.after)
var node = this.context.firstChild;
if (!node) { if (!node) {
node = this.context; node = this.context;
while (node != this.root && !node.nextSibling) while (node.parentNode && node != this.range.endContainer
&& !node.nextSibling)
node = node.parentNode; node = node.parentNode;
node = node.nextSibling; node = node.nextSibling;
} }
if (node == this.root || node == null)
if (!node || !RangeFind.containsNode(this.range, node, true))
return null; return null;
this.after = false;
return this.context = node; return this.context = node;
}, },
@@ -379,9 +467,12 @@ var Editor = Module("editor", {
function advance(positive) { function advance(positive) {
while (true) { while (true) {
while (idx == text.length && (node = iterator.getNext())) { while (idx == text.length && (node = iterator.getNext())) {
if (node == iterator.start)
idx = range.endOffset;
offset = text.length; offset = text.length;
text += node.textContent; text += node.textContent;
range.setEnd(node, 0); range.setEnd(node, idx - offset);
} }
if (idx >= text.length || re.test(text[idx]) != positive) if (idx >= text.length || re.test(text[idx]) != positive)
@@ -393,9 +484,13 @@ var Editor = Module("editor", {
while (true) { while (true) {
while (idx == 0 && (node = iterator.getPrev())) { while (idx == 0 && (node = iterator.getPrev())) {
let str = node.textContent; let str = node.textContent;
idx += str.length; if (node == iterator.start)
idx = range.endOffset;
else
idx = str.length;
text = str + text; text = str + text;
range.setStart(node, str.length); range.setStart(node, idx);
} }
if (idx == 0 || re.test(text[idx - 1]) != positive) if (idx == 0 || re.test(text[idx - 1]) != positive)
break; break;
@@ -403,19 +498,22 @@ var Editor = Module("editor", {
} }
} }
if (!root)
for (root = range.startContainer;
root.parentNode instanceof Element && !DOM(root).isEditable;
root = root.parentNode)
;
if (root instanceof Ci.nsIDOMNSEditableElement)
root = root.editor;
if (root instanceof Ci.nsIEditor)
root = root.rootElement;
let node = range[forward ? "endContainer" : "startContainer"]; let node = range[forward ? "endContainer" : "startContainer"];
let iterator = Editor.TextsIterator(root || node.ownerDocument.documentElement, let iterator = Editor.TextsIterator(RangeFind.nodeContents(root),
node); node, !forward);
if (!(node instanceof Ci.nsIDOMText)) { let text = "";
node = iterator[forward ? "getNext" : "getPrev"](); let idx = 0;
dactyl.assert(node);
range[forward ? "setEnd" : "setStart"](node, forward ? 0 : node.textContent.length);
}
let text = range[forward ? "endContainer" : "startContainer"].textContent;
let idx = range[forward ? "endOffset" : "startOffset"];
let offset = 0; let offset = 0;
if (forward) { if (forward) {
@@ -621,9 +719,21 @@ var Editor = Module("editor", {
addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"], "Delete from the cursor to the end of the line and start insert"); addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"], "Delete from the cursor to the end of the line and start insert");
function addMotionMap(key, desc, select, cmd, mode) { function addMotionMap(key, desc, select, cmd, mode) {
mappings.add([modes.TEXT_EDIT], [key], function doTxn(range, editor) {
try {
editor.editor.beginTransaction();
cmd(editor, range, editor.editor);
}
finally {
editor.editor.endTransaction();
}
}
mappings.add([modes.TEXT_EDIT], key,
desc, desc,
function ({ count, motion }) { function ({ count, motion }) {
let start = editor.selectionRange.cloneRange();
modes.push(modes.OPERATOR, null, { modes.push(modes.OPERATOR, null, {
count: count, count: count,
@@ -631,17 +741,9 @@ var Editor = Module("editor", {
if (stack.push || stack.fromEscape) if (stack.push || stack.fromEscape)
return; return;
try { let range = RangeFind.union(start, editor.selectionRange);
editor_.beginTransaction(); editor.selectionRange = select ? range : start;
doTxn(range, editor);
let range = RangeFind.union(start, sel.getRangeAt(0));
sel.removeAllRanges();
sel.addRange(select ? range : start);
cmd(editor_, range);
}
finally {
editor_.endTransaction();
}
modes.delay(function () { modes.delay(function () {
if (mode) if (mode)
@@ -649,17 +751,31 @@ var Editor = Module("editor", {
}); });
} }
}); });
let editor_ = editor.editor;
let sel = editor.selection;
let start = sel.getRangeAt(0).cloneRange();
}, },
{ count: true, type: "motion" }); { count: true, type: "motion" });
mappings.add([modes.VISUAL], key,
desc,
function ({ count, motion }) {
dactyl.assert(editor.isTextEdit);
doTxn(editor.selectionRange, editor);
},
{ count: true, type: "motion" });
} }
addMotionMap("d", "Delete motion", true, function (editor) { editor.cut(); }); addMotionMap(["d", "x"], "Delete text", true, function (editor) { editor.editor.cut(); });
addMotionMap("c", "Change motion", true, function (editor) { editor.cut(); }, modes.INSERT); addMotionMap(["c"], "Change text", true, function (editor) { editor.editor.cut(); }, modes.INSERT);
addMotionMap("y", "Yank motion", false, function (editor, range) { dactyl.clipboardWrite(DOM.stringify(range)) }); addMotionMap(["y"], "Yank text", false, function (editor, range) { dactyl.clipboardWrite(DOM.stringify(range)) });
addMotionMap(["gu"], "Lowercase text", false,
function (editor, range) {
editor.mungeRange(range, String.toLocaleLowerCase);
});
addMotionMap(["gU"], "Uppercase text", false,
function (editor, range) {
editor.mungeRange(range, String.toLocaleUpperCase);
});
let bind = function bind(names, description, action, params) let bind = function bind(names, description, action, params)
mappings.add([modes.INPUT], names, description, mappings.add([modes.INPUT], names, description,
@@ -811,31 +927,13 @@ var Editor = Module("editor", {
}); });
mappings.add([modes.VISUAL], mappings.add([modes.VISUAL],
["c", "s"], "Change selected text", ["s"], "Change selected text",
function () { function () {
dactyl.assert(editor.isTextEdit); dactyl.assert(editor.isTextEdit);
editor.executeCommand("cmd_cut"); editor.executeCommand("cmd_cut");
modes.push(modes.INSERT); modes.push(modes.INSERT);
}); });
mappings.add([modes.VISUAL],
["d", "x"], "Delete selected text",
function () {
dactyl.assert(editor.isTextEdit);
editor.executeCommand("cmd_cut");
});
mappings.add([modes.VISUAL],
["y"], "Yank selected text",
function () {
if (editor.isTextEdit) {
editor.executeCommand("cmd_copy");
modes.pop();
}
else
dactyl.clipboardWrite(buffer.currentWord, true);
});
bind(["p"], "Paste clipboard contents", bind(["p"], "Paste clipboard contents",
function ({ count }) { function ({ count }) {
dactyl.assert(!editor.isCaret); dactyl.assert(!editor.isCaret);
@@ -894,19 +992,6 @@ var Editor = Module("editor", {
}, },
{ arg: true, count: true, type: "operator" }); { arg: true, count: true, type: "operator" });
function mungeRange(range, munger) {
let text = munger(range);
let { startOffset, endOffset } = range;
let root = range.startContainer.parentNode;
range.deleteContents();
range.insertNode(range.startContainer.ownerDocument
.createTextNode(text));
root.normalize();
range.setStart(root.firstChild, startOffset);
range.setEnd(root.firstChild, endOffset);
}
// text edit and visual mode // text edit and visual mode
mappings.add([modes.TEXT_EDIT, modes.VISUAL], mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["~"], "Switch case of the character under the cursor and move the cursor to the right", ["~"], "Switch case of the character under the cursor and move the cursor to the right",
@@ -917,21 +1002,14 @@ var Editor = Module("editor", {
return c == lc ? c.toLocaleUpperCase() : lc; return c == lc ? c.toLocaleUpperCase() : lc;
}); });
var range = editor.selection.getRangeAt(0); var range = editor.selectionRange;
// Ugh. TODO: Utility.
if (!(range.startContainer instanceof Ci.nsIDOMText)) {
range = RangeFind.nodeContants(node.startContainer);
range.collapse(false);
}
if (range.collapsed) { if (range.collapsed) {
count = count || 1; count = count || 1;
range.setEnd(range.startContainer, range.setEnd(range.startContainer,
range.startOffset + count); range.startOffset + count);
} }
mungeRange(range, munger); editor.mungeRange(range, munger, count != null);
editor.selection.collapse(range.startContainer, range.endOffset);
modes.pop(modes.TEXT_EDIT); modes.pop(modes.TEXT_EDIT);
}, },

View File

@@ -867,7 +867,7 @@ var Events = Module("events", {
if (!haveInput) if (!haveInput)
if (options["insertmode"]) if (options["insertmode"])
modes.push(modes.INSERT); modes.push(modes.INSERT);
else { else if (!isinstance(modes.main, [modes.TEXT_EDIT, modes.VISUAL])) {
modes.push(modes.TEXT_EDIT); modes.push(modes.TEXT_EDIT);
if (elem.selectionEnd - elem.selectionStart > 0) if (elem.selectionEnd - elem.selectionStart > 0)
modes.push(modes.VISUAL); modes.push(modes.VISUAL);

View File

@@ -476,7 +476,7 @@ var RangeFind = Class("RangeFind", {
} }
}, },
indexIter: function (private_) { indexIter: function indexIter(private_) {
let idx = this.range.index; let idx = this.range.index;
if (this.backward) if (this.backward)
var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)]; var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
@@ -494,7 +494,7 @@ var RangeFind = Class("RangeFind", {
} }
}, },
iter: function (word) { iter: function iter(word) {
let saved = ["lastRange", "lastString", "range", "regexp"].map(function (s) [s, this[s]], this); let saved = ["lastRange", "lastString", "range", "regexp"].map(function (s) [s, this[s]], this);
let res; let res;
try { try {
@@ -525,7 +525,7 @@ var RangeFind = Class("RangeFind", {
} }
}, },
makeFrameList: function (win) { makeFrameList: function makeFrameList(win) {
const self = this; const self = this;
win = win.top; win = win.top;
let frames = []; let frames = [];
@@ -581,7 +581,7 @@ var RangeFind = Class("RangeFind", {
return frames; return frames;
}, },
reset: function () { reset: function reset() {
this.ranges = this.makeFrameList(this.content); this.ranges = this.makeFrameList(this.content);
this.startRange = this.selectedRange; this.startRange = this.selectedRange;
@@ -594,7 +594,7 @@ var RangeFind = Class("RangeFind", {
this.found = false; this.found = false;
}, },
find: function (pattern, reverse, private_) { find: function find(pattern, reverse, private_) {
if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange)) if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange))
this.reset(); this.reset();
@@ -685,18 +685,18 @@ var RangeFind = Class("RangeFind", {
get stale() this._stale || this.baseDocument.get() != this.content.document, get stale() this._stale || this.baseDocument.get() != this.content.document,
set stale(val) this._stale = val, set stale(val) this._stale = val,
addListeners: function () { addListeners: function addListeners() {
for (let range in array.iterValues(this.ranges)) for (let range in array.iterValues(this.ranges))
range.window.addEventListener("unload", this.closure.onUnload, true); range.window.addEventListener("unload", this.closure.onUnload, true);
}, },
purgeListeners: function () { purgeListeners: function purgeListeners() {
for (let range in array.iterValues(this.ranges)) for (let range in array.iterValues(this.ranges))
try { try {
range.window.removeEventListener("unload", this.closure.onUnload, true); range.window.removeEventListener("unload", this.closure.onUnload, true);
} }
catch (e if e.result === Cr.NS_ERROR_FAILURE) {} catch (e if e.result === Cr.NS_ERROR_FAILURE) {}
}, },
onUnload: function (event) { onUnload: function onUnload(event) {
this.purgeListeners(); this.purgeListeners();
if (this.highlighted) if (this.highlighted)
this.highlight(true); this.highlight(true);
@@ -704,7 +704,7 @@ var RangeFind = Class("RangeFind", {
} }
}, { }, {
Range: Class("RangeFind.Range", { Range: Class("RangeFind.Range", {
init: function (range, index) { init: function init(range, index) {
this.index = index; this.index = index;
this.range = range; this.range = range;
@@ -720,7 +720,7 @@ var RangeFind = Class("RangeFind", {
intersects: function (range) RangeFind.intersects(this.range, range), intersects: function (range) RangeFind.intersects(this.range, range),
save: function () { save: function save() {
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset); this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
this.initialSelection = null; this.initialSelection = null;
@@ -728,11 +728,11 @@ var RangeFind = Class("RangeFind", {
this.initialSelection = this.selection.getRangeAt(0); this.initialSelection = this.selection.getRangeAt(0);
}, },
descroll: function () { descroll: function descroll() {
this.window.scrollTo(this.scroll.x, this.scroll.y); this.window.scrollTo(this.scroll.x, this.scroll.y);
}, },
deselect: function () { deselect: function deselect() {
if (this.selection) { if (this.selection) {
this.selection.removeAllRanges(); this.selection.removeAllRanges();
if (this.initialSelection) if (this.initialSelection)
@@ -752,17 +752,19 @@ var RangeFind = Class("RangeFind", {
} }
} }
}), }),
contains: function (range, r) { contains: function contains(range, r, quiet) {
try { try {
return range.compareBoundaryPoints(range.START_TO_END, r) >= 0 && return range.compareBoundaryPoints(range.START_TO_END, r) >= 0 &&
range.compareBoundaryPoints(range.END_TO_START, r) <= 0; range.compareBoundaryPoints(range.END_TO_START, r) <= 0;
} }
catch (e) { catch (e) {
util.reportError(e, true); if (e.result != Cr.NS_ERROR_DOM_WRONG_DOCUMENT_ERR && !quiet)
util.reportError(e, true);
return false; return false;
} }
}, },
intersects: function (range, r) { containsNode: function containsNode(range, n, quiet) n.ownerDocument && this.contains(range, RangeFind.nodeRange(n), quiet),
intersects: function intersects(range, r) {
try { try {
return r.compareBoundaryPoints(range.START_TO_END, range) >= 0 && return r.compareBoundaryPoints(range.START_TO_END, range) >= 0 &&
r.compareBoundaryPoints(range.END_TO_START, range) <= 0; r.compareBoundaryPoints(range.END_TO_START, range) <= 0;
@@ -772,12 +774,12 @@ var RangeFind = Class("RangeFind", {
return false; return false;
} }
}, },
endpoint: function (range, before) { endpoint: function endpoint(range, before) {
range = range.cloneRange(); range = range.cloneRange();
range.collapse(before); range.collapse(before);
return range; return range;
}, },
equal: function (r1, r2) { equal: function equal(r1, r2) {
try { try {
return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2); return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2);
} }
@@ -785,7 +787,7 @@ var RangeFind = Class("RangeFind", {
return false; return false;
} }
}, },
nodeContents: function (node) { nodeContents: function nodeContents(node) {
let range = node.ownerDocument.createRange(); let range = node.ownerDocument.createRange();
try { try {
range.selectNodeContents(node); range.selectNodeContents(node);
@@ -793,7 +795,7 @@ var RangeFind = Class("RangeFind", {
catch (e) {} catch (e) {}
return range; return range;
}, },
nodeRange: function (node) { nodeRange: function nodeRange(node) {
let range = node.ownerDocument.createRange(); let range = node.ownerDocument.createRange();
try { try {
range.selectNode(node); range.selectNode(node);
@@ -801,7 +803,7 @@ var RangeFind = Class("RangeFind", {
catch (e) {} catch (e) {}
return range; return range;
}, },
sameDocument: function (r1, r2) { sameDocument: function sameDocument(r1, r2) {
if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument)) if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument))
return false; return false;
try { try {

View File

@@ -30,17 +30,44 @@
everywhere from command-line and message history to option everywhere from command-line and message history to option
values and cookies. [b1] values and cookies. [b1]
• New and much more powerful incremental search implementation. • New and much more powerful incremental search implementation.
Improvements over the standard Firefox find include: [b1] Improvements over the standard Firefox find include:
- Starts at the cursor position in the currently selected - Starts at the cursor position in the currently selected
frame, unlike Firefox, which always starts at the start of frame, unlike Firefox, which always starts at the start of
the first frame. the first frame. [b1]
- Returns the cursor and viewport to their original position - Returns the cursor and viewport to their original position
on cancel. on cancel. [b1]
- Backtracks to the first successful match after pressing - Backtracks to the first successful match after pressing
backspace. backspace. [b1]
- Supports reverse incremental search. - Supports reverse incremental search. [b1]
- Input boxes are not focused when matches are highlighted. - Input boxes are not focused when matches are highlighted. [b1]
- Crude regular expression search is supported. [b8] - Crude regular expression search is supported. [b8]
- New searches now start within the current viewport where possible. [b8]
• Text editing improvements, including:
- Added t_gu and t_gU. [b8]
- Added operator modes and proper first class motion maps. [b8]
- Improved undo support for most mappings. [b8]
• General completion improvements
- Greatly improved completion rendering performance, especially
while scrolling. [b8]
- Added c_<C-f>, c_<C-b>, c_<C-Tab>, and c_<C-S-Tab> for scrolling
the completion list in increments larger than one line. [b8]
- Improved handling of asynchronous completions, including: [b8]
+ Pressing <Return> after tabbing past the end of already received
completions will execute the command after the desired result has
arrived.
+ Tabbing past the end of available completions more reliably selects
the desired completion when it is available.
+ Late arriving completion results no longer interfere with typing.
+ It is now possible to skip past the end of incomplete completion
groups via the c_<C-f> and c_<C-Tab> keys.
- JavaScript completion improvements, including: [b2]
+ The prototype of the function whose arguments are currently
being typed is displayed during completion.
+ Non-enumerable global properties are now completed for the
global object, including XMLHttpRequest and encodeURI.
- The concept of completion contexts is now exposed to the user
(see :contexts), allowing for powerful and fine-grained
completion system customization. [b1]
• Ex command parsing improvements, including: • Ex command parsing improvements, including:
- Multiple Ex commands may now be separated by | [b1] - Multiple Ex commands may now be separated by | [b1]
- Commands can continue over multiple lines in RC files by - Commands can continue over multiple lines in RC files by
@@ -61,15 +88,6 @@
- Added ;a and ;S modes for creating bookmarks and search keywords. [b4][b3] - Added ;a and ;S modes for creating bookmarks and search keywords. [b4][b3]
- Added 'hintkeys' option. [b2] - Added 'hintkeys' option. [b2]
- Added "transliterated" option to 'hintmatching'. [b1] - Added "transliterated" option to 'hintmatching'. [b1]
• General completion improvements
- JavaScript completion improvements, including: [b2]
+ The prototype of the function whose arguments are currently
being typed is displayed during completion.
+ Non-enumerable global properties are now completed for the
global object, including XMLHttpRequest and encodeURI.
- The concept of completion contexts is now exposed to the user
(see :contexts), allowing for powerful and fine-grained
completion system customization. [b1]
• The external editor can now be configured to open to a given • The external editor can now be configured to open to a given
line number and column, used for opening source links and line number and column, used for opening source links and
editing input fields with i_<C-i>. See :h 'editor'. [b4] editing input fields with i_<C-i>. See :h 'editor'. [b4]