diff --git a/scripts/script.js b/scripts/script.js
index c097c0ba4a..e82021e4fd 100644
--- a/scripts/script.js
+++ b/scripts/script.js
@@ -8024,7 +8024,7 @@ function suggest() {
repl += " ";
spacestate = hintinfo.smart_punctuation ? outPos : -1;
}
- if (endPos < 0) {
+ if (startPos < 0 || endPos < 0 || startPos > endPos || endPos > val.length) {
let x = Object.assign({}, hintinfo);
delete x.items;
log_jserror(JSON.stringify({value:val,hint:x,repl:repl,startPos:startPos,endPos:endPos}));
diff --git a/scripts/settings.js b/scripts/settings.js
index fb35da4a75..650f71a125 100644
--- a/scripts/settings.js
+++ b/scripts/settings.js
@@ -1226,15 +1226,21 @@ function canonical_input_type(it) {
// Return an object `wsel` representing the current Selection, specialized for
// element `el`.
-// * Move selection endpoints within `el` down into Text nodes if possible.
-// * `nsel.transfer_text(dst, src, src_min_offset, delta)` shifts pending
-// selection endpoints from `src` to `dst`, depending on offset. Useful when
-// splitting or combining Text nodes.
-// * `nsel.trim_newline(el)` trims trailing newlines from `el`, changing the
-// pending selection as appropriate.
-// * `nsel.refresh()` installs the pending selection.
+//
+// When called, this shifts selection endpoints into `el` Text nodes when
+// possible.
+//
+// Methods on `wsel` allow callers to modify the DOM within `el` while tracking
+// what should happen to the selection in response. After completing their
+// DOM modifications, the caller should call `wsel.refresh()` to install
+// the modified selection.
+// * `wsel.splitTextNode(ch, pos)` is like `ch.splitTextNode(pos)`.
+// * `wsel.mergeTextNodeRight(ch)` is like `ch.appendData(ch.nextSibling.data);
+// ch.nextSibling.remove()`.
+// * `wsel.removeNode(n)` is like `n.remove()`.
+// * `wsel.trimNewlines(ch)` trims trailing newlines from `ch`.
function window_selection_inside(el) {
- var sel = window.getSelection(),
+ let sel = window.getSelection(),
selm = [el.contains(sel.anchorNode), el.contains(sel.focusNode)],
selmx = selm[0] || selm[1],
selx = [sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset];
@@ -1261,20 +1267,51 @@ function window_selection_inside(el) {
}
return changed;
}
- function refresh() {
- selmx && sel.setBaseAndExtent(selx[0], selx[1], selx[2], selx[3]);
- }
- if (normalize_edge(0) || normalize_edge(2)) {
- refresh();
- }
function transfer_text(dst, src, src_min_offset, offset_delta) {
- for (var i = selmx ? 0 : 4; i !== 4; i += 2) {
+ for (let i = selmx ? 0 : 4; i !== 4; i += 2) {
if (selx[i] === src && selx[i + 1] >= src_min_offset) {
selx[i] = dst;
selx[i + 1] += offset_delta;
}
}
}
+ function splitTextNode(ch, pos) {
+ const split = ch.splitText(pos);
+ selmx && transfer_text(split, ch, pos, -pos);
+ return split;
+ }
+ function mergeTextNodeRight(ch) {
+ const next = ch.nextSibling;
+ selmx && transfer_text(ch, next, 0, ch.length);
+ ch.appendData(next.data);
+ removeNode(next);
+ }
+ function positionWithinParent(n) {
+ let i = 0, ch = n.parentNode.firstChild;
+ while (ch && ch !== n) {
+ ++i;
+ ch = ch.nextSibling;
+ }
+ return ch ? i : null;
+ }
+ function removeNode(n) {
+ let npos = null;
+ for (let i = selmx ? 0 : 4; i !== 4; i += 2) {
+ if (selx[i] === n.parentNode) {
+ npos === null && (npos = positionWithinParent(n));
+ if (npos !== null && selx[i + 1] > npos) {
+ --selx[i + 1];
+ }
+ }
+ }
+ n.remove();
+ }
+ function refresh() {
+ selmx && sel.setBaseAndExtent(selx[0], selx[1], selx[2], selx[3]);
+ }
+ if (normalize_edge(0) || normalize_edge(2)) {
+ refresh();
+ }
function render_sel(el, offset) {
if (!el) {
return "";
@@ -1297,16 +1334,18 @@ function window_selection_inside(el) {
}
return {
modified: selmx,
- transfer_text: transfer_text,
- trim_newline: function (el) {
- var i = el.length - 1;
- while (i >= 0 && el.data[i] === "\n") {
- el.deleteData(i, 1);
- transfer_text(el, el, i + 1, -1);
+ splitTextNode: splitTextNode,
+ mergeTextNodeRight: mergeTextNodeRight,
+ removeNode: removeNode,
+ trimNewlines: function (ch) {
+ let i = ch.length - 1;
+ while (i >= 0 && ch.data[i] === "\n") {
+ ch.deleteData(i, 1);
+ transfer_text(ch, ch, i + 1, -1);
--i;
}
},
- reset_modified: function (el, offset) {
+ setContainedPositions: function (el, offset) {
selm[0] && reset(0, el, offset);
selm[1] && reset(2, el, offset);
},
@@ -1329,27 +1368,26 @@ function make_content_editable(mainel) {
// * No trailing newlines within these
s.
// * Blank
lines contain
.
function normalizer(firstel, lastel) {
- var ch, next, fix1, fixfresh, nsel = window_selection_inside(mainel);
+ let ch, next, fix1, fixfresh, nsel = window_selection_inside(mainel);
function append_line() {
- var line = document.createElement("div");
+ const line = document.createElement("div");
mainel.insertBefore(line, fix1.nextSibling);
fix1 = line;
fixfresh = true;
}
function fix_div(ch) {
- var next, nl;
+ let next, nl;
while (ch) {
next = ch.nextSibling;
if (ch.nodeType !== 1 && ch.nodeType !== 3) {
ch.remove();
} else if (ch.nodeType === 3 && (nl = ch.data.indexOf("\n")) !== -1) {
if (nl !== ch.length - 1) {
- next = ch.splitText(nl + 1);
- nsel.transfer_text(next, ch, nl + 1, -nl - 1);
+ next = nsel.splitTextNode(ch, nl + 1);
}
- nsel.trim_newline(ch);
+ nsel.trimNewlines(ch);
if (fix1 !== ch.parentElement) {
fix1.appendChild(ch);
}
@@ -1361,14 +1399,14 @@ function make_content_editable(mainel) {
}
} else if (is_br(ch)) {
if (fix1.firstChild && fix1.firstChild !== ch) {
- ch.remove();
+ nsel.removeNode(ch);
} else if (fix1 !== ch.parentElement) {
fix1.appendChild(ch);
}
append_line();
} else {
fixfresh || append_line();
- ch.remove();
+ nsel.removeNode(ch);
fix_div(ch.firstChild);
fixfresh || append_line();
}
@@ -1377,12 +1415,13 @@ function make_content_editable(mainel) {
}
ch = firstel = firstel || mainel.firstChild;
+ const prev = firstel ? firstel.previousSibling : null;
while (ch && ch !== lastel) {
if (ch.nodeType !== 1
|| ch.tagName !== "DIV"
|| ch.hasAttribute("style")) {
- var line = document.createElement("div"),
- line1 = ch === firstel, child1 = true;
+ const line = document.createElement("div");
+ let child1 = true;
mainel.insertBefore(line, ch);
while (ch && (child1 || is_text_or_inline(ch))) {
line.appendChild(ch);
@@ -1390,22 +1429,21 @@ function make_content_editable(mainel) {
child1 = false;
}
if (ch && is_br(ch)) {
- line.firstChild ? ch.remove() : line.appendChild(ch);
+ line.firstChild ? nsel.removeNode(ch) : line.appendChild(ch);
}
- line1 && (firstel = line);
ch = line;
}
next = ch.nextSibling;
fix1 = ch;
fixfresh = false;
fix_div(ch.firstChild);
- ch.firstChild || ch.remove();
- fixfresh && fix1.remove();
+ ch.firstChild || nsel.removeNode(ch);
+ fixfresh && nsel.removeNode(fix1);
ch = next;
}
nsel.refresh();
- return firstel;
+ return prev ? prev.nextSibling : mainel.firstChild;
}
function length() {
@@ -1764,21 +1802,19 @@ function jsonhl_install(lineel, errors, nsel) {
if (ch.nodeType !== 3) {
jsonhl_move_text(lineel, ch, ch.nextSibling);
sib = ch.nextSibling;
- ch.remove();
+ nsel.removeNode(ch);
ch = sib;
}
while (ch && (sib = ch.nextSibling) && ch.length < len) {
- if (sib.nodeType !== 3) {
- jsonhl_move_text(lineel, sib, sib.nextSibling);
+ if (sib.nodeType === 3) {
+ nsel.mergeTextNodeRight(ch);
} else {
- nsel.transfer_text(ch, sib, 0, ch.length);
- ch.appendData(sib.data);
+ jsonhl_move_text(lineel, sib, sib.nextSibling);
+ nsel.removeNode(sib);
}
- sib.remove();
}
if (ch && ch.length > len) {
- sib = ch.splitText(len);
- nsel.transfer_text(sib, ch, len, -len);
+ sib = nsel.splitTextNode(ch, len);
}
}
@@ -1814,33 +1850,10 @@ function jsonhl_install(lineel, errors, nsel) {
if (lineel.firstChild === null) {
lineel.appendChild(document.createElement("br"));
- nsel.reset_modified(lineel, 0);
+ nsel.setContainedPositions(lineel, 0);
}
}
-function log_line_summary(lineno, lineel) {
- var x = [], i = 0, ch,
- sel = window.getSelection(), sub = lineel.contains(sel.anchorNode);
- for (ch = lineel.firstChild; ch; ch = ch.nextSibling, ++i) {
- if (sel.anchorNode === lineel && sel.anchorOffset === i) {
- x.push("*");
- }
- if (ch.nodeType === 3 && sel.anchorNode === ch) {
- x.push("T".concat(ch.length, "@", sel.anchorOffset));
- } else if (ch.nodeType === 3) {
- x.push("T".concat(ch.length));
- } else if (sub && ch.contains(sel.anchorNode)) {
- x.push(ch.tagName.concat(ch.childNodes.length, "@", sel.anchorOffset));
- } else {
- x.push(ch.tagName.concat(ch.childNodes.length));
- }
- }
- if (sel.anchorNode === lineel && sel.anchorOffset === i) {
- x.push("*");
- }
- console.log("".concat(lineno, ". ", x.join(" ")));
-}
-
function make_utf16tf(text) {
var ipos = 0, opos = 0, delta = 0, map = [0, 0],
re = /[\u0080-\uDBFF\uE000-\uFFFF]/g, m, ch;
@@ -2419,29 +2432,27 @@ function json_path_position(s, path) {
}
function make_json_validate() {
- var mainel = this, reflectel = null,
+ let mainel = this, reflectel = null,
maince = make_content_editable(mainel),
states = [""], lineels = [null],
redisplay_ln0 = 0, redisplay_el1 = null,
normalization, rehighlight_queued,
api_timer = null, api_value = null, msgbub = null,
- commands = [], commandPos = 0,
+ commands = [], commandPos = 0, command_fix = false,
undo_time, redo_time;
function rehighlight() {
try {
- var i, saw_line1 = false, lineel, k, st, x, cmd, nsel;
- if (normalization !== 0) {
+ var i, saw_line1 = false, lineel, k, st, x, nsel;
+ if (normalization < 0) {
maince.normalize();
- if (normalization < 0
- && mainel.hasAttribute("data-highlight-ranges")) {
+ if (mainel.hasAttribute("data-highlight-ranges")) {
jsonhl_transfer_ranges(mainel);
}
}
i = redisplay_ln0;
lineel = mainel.childNodes[i];
st = states[i];
- cmd = commands[commandPos - 1];
nsel = window_selection_inside(mainel);
while (lineel !== null
&& (!saw_line1
@@ -2450,7 +2461,7 @@ function make_json_validate() {
if (msgbub && msgbub.span.parentElement === lineel) {
clear_msgbub();
}
- if (lineel.nodeType !== 1 || lineel.tagName !== "DIV") {
+ if (lineel.nodeType !== 1 || lineel.tagName !== "DIV") { // XXX unsure ever happens
lineel = maince.normalize(lineel, lineel.nextSibling);
}
if (normalization >= 0) {
@@ -2459,7 +2470,6 @@ function make_json_validate() {
}
if (lineel === redisplay_el1) {
saw_line1 = true;
- cmd && cmd.afterRange[1] < 0 && (cmd.afterRange[1] = i);
}
if (lineels[i] !== lineel && lineels[i]) {
// incremental line insertions and deletions
@@ -2549,9 +2559,9 @@ function make_json_validate() {
function union_range(r1, r2) {
r1 = r1 || [Infinity, 0];
- if (r2[0] >= 0 && r1[0] > r2[0])
+ if (r2[0] >= 0 && r2[0] < r1[0])
r1 = [r2[0], r1[1]];
- if (r2[1] >= 0 && r1[1] < r2[1])
+ if (r2[1] >= 0 && r2[1] > r1[1])
r1 = [r1[0], r2[1]];
return r1;
}
@@ -2570,11 +2580,12 @@ function make_json_validate() {
if (commandPos < commands.length) {
commands.splice(commandPos);
}
- var c = commands[commandPos - 1], t = canonical_input_type(evt.inputType);
+ let c = commands[commandPos - 1], t = canonical_input_type(evt.inputType);
if (!(c
&& c.type === t
&& evt.timeStamp <= c.recentTime + 250
- && evt.timeStamp <= c.startTime + 3000)) {
+ && evt.timeStamp <= c.startTime + 3000
+ && !c.afterLines)) {
if (commandPos > 2000) {
commands.splice(0, 1000);
}
@@ -2588,40 +2599,46 @@ function make_json_validate() {
}
if (editRange[0] < c.beforeRange[0]) {
c.beforeLines.splice(0, 0, ...maince.slice(editRange[0], c.beforeRange[0]));
- c.beforeRange[0] = editRange[0];
+ c.beforeRange[0] = c.afterRange[0] = editRange[0];
}
- if (c.afterRange[1] < editRange[1]) {
+ if (editRange[1] > c.afterRange[1]) {
c.beforeLines.splice(c.beforeLines.length, 0, ...maince.slice(c.afterRange[1], editRange[1]));
c.beforeRange[1] += editRange[1] - c.afterRange[1];
}
- if (c.afterRange[1] <= editRange[1]) {
- c.afterRange[1] = -1; // recompute end of range
- }
+ redisplay_ln0 = c.beforeRange[0];
+ redisplay_el1 = mainel.childNodes[Math.max(c.afterRange[1], editRange[1])];
+ command_fix = true;
c.recentTime = evt.timeStamp;
}
function handle_swap(dstRange, dstTexts, srcRange, srcTexts) {
- var i, n = srcRange[1], elx = mainel.childNodes[n], el;
- while (n > dstRange[1]) {
- --n;
- el = elx ? elx.previousSibling : mainel.lastChild;
- el.remove();
- }
- while (n < dstRange[1]) {
- el = document.createElement("div");
- el.append(document.createElement("br"));
- mainel.insertBefore(el, elx);
- ++n;
- }
- el = mainel.childNodes[dstRange[0]];
- for (i = 0; el !== elx; el = el.nextSibling, ++i) {
+ if (srcRange[0] !== dstRange[0]
+ || srcTexts.length !== srcRange[1] - srcRange[0]
+ || dstTexts.length !== dstRange[1] - dstRange[0]) {
+ throw new Error("bad handle_swap " + JSON.stringify([srcRange, srcTexts.length, dstRange, dstTexts.length]));
+ }
+ let el = mainel.childNodes[dstRange[0]], i = 0;
+ while (i < dstTexts.length) {
+ if (i >= srcTexts.length) {
+ const nel = document.createElement("div");
+ mainel.insertBefore(nel, el);
+ el = nel;
+ }
el.replaceChildren(dstTexts[i].length <= 1 ? document.createElement("br") : dstTexts[i].substring(0, dstTexts[i].length - 1));
+ ++i;
+ el = el.nextSibling;
+ }
+ while (i < srcTexts.length) {
+ const nel = el.nextSibling;
+ el.remove();
+ ++i;
+ el = nel;
}
redisplay_ln0 = dstRange[0];
- redisplay_el1 = elx;
+ redisplay_el1 = el;
queue_rehighlight();
// place selection at end of difference
- var dt = dstTexts[dstTexts.length - 1] || "",
+ const dt = dstTexts[dstTexts.length - 1] || "",
dl = dt.length,
st = srcTexts[srcTexts.length - 1] || "",
sl = st.length;
@@ -2630,11 +2647,12 @@ function make_json_validate() {
--i;
}
let [b, off] = maince.lp2boff(dstRange[1] - 1, i + 1);
+ $(b).scrollIntoView({marginTop: 24, atTop: true});
window.getSelection().setBaseAndExtent(b, off, b, off);
}
function handle_undo(time) {
- var c = commands[commandPos - 1];
+ const c = commands[commandPos - 1];
if (c && (undo_time === null || time - undo_time > 3)) {
undo_time = undo_time || time;
c.recentTime = 0;
@@ -2646,7 +2664,7 @@ function make_json_validate() {
}
function handle_redo(time) {
- var c = commands[commandPos];
+ const c = commands[commandPos];
if (c && (redo_time === null || time - redo_time > 3)) {
redo_time = redo_time || time;
handle_swap(c.afterRange, c.afterLines, c.beforeRange, c.beforeLines);
@@ -2655,7 +2673,7 @@ function make_json_validate() {
}
function beforeinput(evt) {
- var t = evt.inputType;
+ const t = evt.inputType;
if (t !== "historyUndo") {
undo_time = null;
}
@@ -2671,14 +2689,25 @@ function make_json_validate() {
evt.preventDefault();
handle_redo(evt.timeStamp);
} else {
- var r = event_range(evt);
- redisplay_ln0 = r[0];
- redisplay_el1 = r[1] >= 0 ? mainel.childNodes[r[1]] : null;
- prepare_undo(r, evt);
+ prepare_undo(event_range(evt), evt);
evt.dataTransfer && (normalization = 1);
}
}
+ function afterinput(evt) {
+ if (command_fix) {
+ let i = redisplay_ln0,
+ lineel = maince.normalize(mainel.childNodes[i], redisplay_el1);
+ while (lineel !== null && lineel !== redisplay_el1) {
+ ++i;
+ lineel = lineel.nextSibling;
+ }
+ commands[commandPos - 1].afterRange[1] = i;
+ command_fix = false;
+ }
+ queue_rehighlight(evt);
+ }
+
function clear_msgbub() {
if (msgbub) {
msgbub.remove();
@@ -2785,7 +2814,7 @@ function make_json_validate() {
}
mainel.addEventListener("beforeinput", beforeinput);
- mainel.addEventListener("input", queue_rehighlight);
+ mainel.addEventListener("input", afterinput);
document.addEventListener("selectionchange", selectionchange);
if (mainel.hasAttribute("data-reflect-text")
&& (reflectel = document.getElementById(mainel.getAttribute("data-reflect-text")))) {