@codemirror/view 6.26.3 → 6.26.4-edit-context
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +225 -34
- package/dist/index.js +225 -34
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2368,6 +2368,7 @@ class ScrollTarget {
|
|
|
2368
2368
|
}
|
|
2369
2369
|
}
|
|
2370
2370
|
const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|
2371
|
+
const setEditContextFormatting = state.StateEffect.define();
|
|
2371
2372
|
/**
|
|
2372
2373
|
Log or report an unhandled exception in client code. Should
|
|
2373
2374
|
probably only be used by extension code that allows client code to
|
|
@@ -2704,10 +2705,11 @@ class DocView extends ContentView {
|
|
|
2704
2705
|
super();
|
|
2705
2706
|
this.view = view;
|
|
2706
2707
|
this.decorations = [];
|
|
2707
|
-
this.dynamicDecorationMap = [];
|
|
2708
|
+
this.dynamicDecorationMap = [false];
|
|
2708
2709
|
this.domChanged = null;
|
|
2709
2710
|
this.hasComposition = null;
|
|
2710
2711
|
this.markedForComposition = new Set;
|
|
2712
|
+
this.editContextFormatting = Decoration.none;
|
|
2711
2713
|
this.lastCompositionAfterCursor = false;
|
|
2712
2714
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2713
2715
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
@@ -2746,8 +2748,9 @@ class DocView extends ContentView {
|
|
|
2746
2748
|
this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
|
|
2747
2749
|
}
|
|
2748
2750
|
}
|
|
2751
|
+
this.updateEditContextFormatting(update);
|
|
2749
2752
|
let readCompositionAt = -1;
|
|
2750
|
-
if (this.view.inputState.composing >= 0) {
|
|
2753
|
+
if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
|
|
2751
2754
|
if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
|
|
2752
2755
|
readCompositionAt = this.domChanged.newSel.head;
|
|
2753
2756
|
else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
|
|
@@ -2855,6 +2858,14 @@ class DocView extends ContentView {
|
|
|
2855
2858
|
if (composition)
|
|
2856
2859
|
this.fixCompositionDOM(composition);
|
|
2857
2860
|
}
|
|
2861
|
+
updateEditContextFormatting(update) {
|
|
2862
|
+
this.editContextFormatting = this.editContextFormatting.map(update.changes);
|
|
2863
|
+
for (let tr of update.transactions)
|
|
2864
|
+
for (let effect of tr.effects)
|
|
2865
|
+
if (effect.is(setEditContextFormatting)) {
|
|
2866
|
+
this.editContextFormatting = effect.value;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2858
2869
|
compositionView(composition) {
|
|
2859
2870
|
let cur = new TextView(composition.text.nodeValue);
|
|
2860
2871
|
cur.flags |= 8 /* ViewFlag.Composition */;
|
|
@@ -3180,7 +3191,7 @@ class DocView extends ContentView {
|
|
|
3180
3191
|
return Decoration.set(deco);
|
|
3181
3192
|
}
|
|
3182
3193
|
updateDeco() {
|
|
3183
|
-
let i =
|
|
3194
|
+
let i = 1;
|
|
3184
3195
|
let allDeco = this.view.state.facet(decorations).map(d => {
|
|
3185
3196
|
let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
|
|
3186
3197
|
return dynamic ? d(this.view) : d;
|
|
@@ -3196,6 +3207,7 @@ class DocView extends ContentView {
|
|
|
3196
3207
|
allDeco.push(state.RangeSet.join(outerDeco));
|
|
3197
3208
|
}
|
|
3198
3209
|
this.decorations = [
|
|
3210
|
+
this.editContextFormatting,
|
|
3199
3211
|
...allDeco,
|
|
3200
3212
|
this.computeBlockGapDeco(),
|
|
3201
3213
|
this.view.viewState.lineGapDeco
|
|
@@ -3882,6 +3894,7 @@ class InputState {
|
|
|
3882
3894
|
this.mouseSelection = mouseSelection;
|
|
3883
3895
|
}
|
|
3884
3896
|
update(update) {
|
|
3897
|
+
this.view.observer.update(update);
|
|
3885
3898
|
if (this.mouseSelection)
|
|
3886
3899
|
this.mouseSelection.update(update);
|
|
3887
3900
|
if (this.draggedContent && update.docChanged)
|
|
@@ -4472,6 +4485,8 @@ observers.blur = view => {
|
|
|
4472
4485
|
updateForFocusChange(view);
|
|
4473
4486
|
};
|
|
4474
4487
|
observers.compositionstart = observers.compositionupdate = view => {
|
|
4488
|
+
if (view.observer.editContext)
|
|
4489
|
+
return; // Composition handled by edit context
|
|
4475
4490
|
if (view.inputState.compositionFirstChange == null)
|
|
4476
4491
|
view.inputState.compositionFirstChange = true;
|
|
4477
4492
|
if (view.inputState.composing < 0) {
|
|
@@ -4480,6 +4495,8 @@ observers.compositionstart = observers.compositionupdate = view => {
|
|
|
4480
4495
|
}
|
|
4481
4496
|
};
|
|
4482
4497
|
observers.compositionend = view => {
|
|
4498
|
+
if (view.observer.editContext)
|
|
4499
|
+
return; // Composition handled by edit context
|
|
4483
4500
|
view.inputState.composing = -1;
|
|
4484
4501
|
view.inputState.compositionEndedAt = Date.now();
|
|
4485
4502
|
view.inputState.compositionPendingKey = true;
|
|
@@ -6356,35 +6373,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6356
6373
|
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
6357
6374
|
}
|
|
6358
6375
|
if (change) {
|
|
6359
|
-
|
|
6360
|
-
return true;
|
|
6361
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
6362
|
-
// backspace, or delete. So this detects changes that look like
|
|
6363
|
-
// they're caused by those keys, and reinterprets them as key
|
|
6364
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
6365
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
6366
|
-
// reliable in all situations.)
|
|
6367
|
-
if (browser.android &&
|
|
6368
|
-
((change.to == sel.to &&
|
|
6369
|
-
// GBoard will sometimes remove a space it just inserted
|
|
6370
|
-
// after a completion when you press enter
|
|
6371
|
-
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6372
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6373
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6374
|
-
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6375
|
-
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6376
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6377
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6378
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6379
|
-
return true;
|
|
6380
|
-
let text = change.insert.toString();
|
|
6381
|
-
if (view.inputState.composing >= 0)
|
|
6382
|
-
view.inputState.composing++;
|
|
6383
|
-
let defaultTr;
|
|
6384
|
-
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6385
|
-
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6386
|
-
view.dispatch(defaultInsert());
|
|
6387
|
-
return true;
|
|
6376
|
+
return applyDOMChangeInner(view, change, newSel, lastKey);
|
|
6388
6377
|
}
|
|
6389
6378
|
else if (newSel && !newSel.main.eq(sel)) {
|
|
6390
6379
|
let scrollIntoView = false, userEvent = "select";
|
|
@@ -6400,6 +6389,38 @@ function applyDOMChange(view, domChange) {
|
|
|
6400
6389
|
return false;
|
|
6401
6390
|
}
|
|
6402
6391
|
}
|
|
6392
|
+
function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
6393
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6394
|
+
return true;
|
|
6395
|
+
let sel = view.state.selection.main;
|
|
6396
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
6397
|
+
// backspace, or delete. So this detects changes that look like
|
|
6398
|
+
// they're caused by those keys, and reinterprets them as key
|
|
6399
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
6400
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
6401
|
+
// reliable in all situations.)
|
|
6402
|
+
if (browser.android &&
|
|
6403
|
+
((change.to == sel.to &&
|
|
6404
|
+
// GBoard will sometimes remove a space it just inserted
|
|
6405
|
+
// after a completion when you press enter
|
|
6406
|
+
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6407
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6408
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6409
|
+
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6410
|
+
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6411
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6412
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6413
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6414
|
+
return true;
|
|
6415
|
+
let text = change.insert.toString();
|
|
6416
|
+
if (view.inputState.composing >= 0)
|
|
6417
|
+
view.inputState.composing++;
|
|
6418
|
+
let defaultTr;
|
|
6419
|
+
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6420
|
+
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6421
|
+
view.dispatch(defaultInsert());
|
|
6422
|
+
return true;
|
|
6423
|
+
}
|
|
6403
6424
|
function applyDefaultInsert(view, change, newSel) {
|
|
6404
6425
|
let tr, startState = view.state, sel = startState.selection.main;
|
|
6405
6426
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
@@ -6526,6 +6547,7 @@ class DOMObserver {
|
|
|
6526
6547
|
constructor(view) {
|
|
6527
6548
|
this.view = view;
|
|
6528
6549
|
this.active = false;
|
|
6550
|
+
this.editContext = null;
|
|
6529
6551
|
// The known selection. Kept in our own object, as opposed to just
|
|
6530
6552
|
// directly accessing the selection because:
|
|
6531
6553
|
// - Safari doesn't report the right selection in shadow DOM
|
|
@@ -6570,6 +6592,10 @@ class DOMObserver {
|
|
|
6570
6592
|
else
|
|
6571
6593
|
this.flush();
|
|
6572
6594
|
});
|
|
6595
|
+
if (window.EditContext) {
|
|
6596
|
+
this.editContext = new EditContextManager(view);
|
|
6597
|
+
view.contentDOM.editContext = this.editContext.editContext;
|
|
6598
|
+
}
|
|
6573
6599
|
if (useCharData)
|
|
6574
6600
|
this.onCharData = (event) => {
|
|
6575
6601
|
this.queue.push({ target: event.target,
|
|
@@ -6620,6 +6646,8 @@ class DOMObserver {
|
|
|
6620
6646
|
onScroll(e) {
|
|
6621
6647
|
if (this.intersecting)
|
|
6622
6648
|
this.flush(false);
|
|
6649
|
+
if (this.editContext)
|
|
6650
|
+
this.view.requestMeasure(this.editContext.measureReq);
|
|
6623
6651
|
this.onScrollChanged(e);
|
|
6624
6652
|
}
|
|
6625
6653
|
onResize() {
|
|
@@ -6928,6 +6956,10 @@ class DOMObserver {
|
|
|
6928
6956
|
win.removeEventListener("beforeprint", this.onPrint);
|
|
6929
6957
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6930
6958
|
}
|
|
6959
|
+
update(update) {
|
|
6960
|
+
if (this.editContext)
|
|
6961
|
+
this.editContext.update(update);
|
|
6962
|
+
}
|
|
6931
6963
|
destroy() {
|
|
6932
6964
|
var _a, _b, _c;
|
|
6933
6965
|
this.stop();
|
|
@@ -6987,6 +7019,161 @@ function safariSelectionRangeHack(view, selection) {
|
|
|
6987
7019
|
view.contentDOM.removeEventListener("beforeinput", read, true);
|
|
6988
7020
|
return found ? buildSelectionRangeFromRange(view, found) : null;
|
|
6989
7021
|
}
|
|
7022
|
+
class EditContextManager {
|
|
7023
|
+
constructor(view) {
|
|
7024
|
+
// The document window for which the text in the context is
|
|
7025
|
+
// maintained. For large documents, this may be smaller than the
|
|
7026
|
+
// editor document. This window always includes the selection head.
|
|
7027
|
+
this.from = 0;
|
|
7028
|
+
this.to = 0;
|
|
7029
|
+
// When applying a transaction, this is used to compare the change
|
|
7030
|
+
// made to the context content to the change in the transaction in
|
|
7031
|
+
// order to make the minimal changes to the context (since touching
|
|
7032
|
+
// that sometimes breaks series of multiple edits made for a single
|
|
7033
|
+
// user action on some Android keyboards)
|
|
7034
|
+
this.pendingContextChange = null;
|
|
7035
|
+
this.resetRange(view.state);
|
|
7036
|
+
let context = this.editContext = new window.EditContext({
|
|
7037
|
+
text: view.state.doc.sliceString(this.from, this.to),
|
|
7038
|
+
selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
|
|
7039
|
+
selectionEnd: this.toContextPos(view.state.selection.main.head)
|
|
7040
|
+
});
|
|
7041
|
+
context.addEventListener("textupdate", e => {
|
|
7042
|
+
let { anchor } = view.state.selection.main;
|
|
7043
|
+
let change = { from: this.toEditorPos(e.updateRangeStart),
|
|
7044
|
+
to: this.toEditorPos(e.updateRangeEnd),
|
|
7045
|
+
insert: state.Text.of(e.text.split("\n")) };
|
|
7046
|
+
// If the window doesn't include the anchor, assume changes
|
|
7047
|
+
// adjacent to a side go up to the anchor.
|
|
7048
|
+
if (change.from == this.from && anchor < this.from)
|
|
7049
|
+
change.from = anchor;
|
|
7050
|
+
else if (change.to == this.to && anchor > this.to)
|
|
7051
|
+
change.to = anchor;
|
|
7052
|
+
// Edit context sometimes fire empty changes
|
|
7053
|
+
if (change.from == change.to && !change.insert.length)
|
|
7054
|
+
return;
|
|
7055
|
+
this.pendingContextChange = change;
|
|
7056
|
+
applyDOMChangeInner(view, change, state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
|
|
7057
|
+
// If the transaction didn't flush our change, revert it so
|
|
7058
|
+
// that the context is in sync with the editor state again.
|
|
7059
|
+
if (this.pendingContextChange)
|
|
7060
|
+
this.revertPending(view.state);
|
|
7061
|
+
});
|
|
7062
|
+
context.addEventListener("characterboundsupdate", e => {
|
|
7063
|
+
let rects = [], prev = null;
|
|
7064
|
+
for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
|
|
7065
|
+
let rect = view.coordsForChar(i);
|
|
7066
|
+
prev = (rect && new DOMRect(rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top))
|
|
7067
|
+
|| prev || new DOMRect;
|
|
7068
|
+
rects.push(prev);
|
|
7069
|
+
}
|
|
7070
|
+
context.updateCharacterBounds(e.rangeStart, rects);
|
|
7071
|
+
});
|
|
7072
|
+
context.addEventListener("textformatupdate", e => {
|
|
7073
|
+
let deco = [];
|
|
7074
|
+
for (let format of e.getTextFormats()) {
|
|
7075
|
+
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7076
|
+
if (lineStyle != "None" && thickness != "None") {
|
|
7077
|
+
let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
|
|
7078
|
+
deco.push(Decoration.mark({ attributes: { style } })
|
|
7079
|
+
.range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
|
|
7083
|
+
});
|
|
7084
|
+
context.addEventListener("compositionstart", () => {
|
|
7085
|
+
if (view.inputState.composing < 0) {
|
|
7086
|
+
view.inputState.composing = 0;
|
|
7087
|
+
view.inputState.compositionFirstChange = true;
|
|
7088
|
+
}
|
|
7089
|
+
});
|
|
7090
|
+
context.addEventListener("compositionend", () => {
|
|
7091
|
+
view.inputState.composing = -1;
|
|
7092
|
+
view.inputState.compositionFirstChange = null;
|
|
7093
|
+
});
|
|
7094
|
+
this.measureReq = { read: view => {
|
|
7095
|
+
this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
|
|
7096
|
+
let sel = getSelection(view.root);
|
|
7097
|
+
if (sel && sel.rangeCount)
|
|
7098
|
+
this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
|
|
7099
|
+
} };
|
|
7100
|
+
}
|
|
7101
|
+
applyEdits(update) {
|
|
7102
|
+
let off = 0, abort = false, pending = this.pendingContextChange;
|
|
7103
|
+
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
|
|
7104
|
+
if (abort)
|
|
7105
|
+
return;
|
|
7106
|
+
let dLen = insert.length - (toA - fromA);
|
|
7107
|
+
if (pending && toA >= pending.to) {
|
|
7108
|
+
if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
|
|
7109
|
+
pending = this.pendingContextChange = null; // Match
|
|
7110
|
+
off += dLen;
|
|
7111
|
+
return;
|
|
7112
|
+
}
|
|
7113
|
+
else { // Mismatch, revert
|
|
7114
|
+
pending = null;
|
|
7115
|
+
this.revertPending(update.state);
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
fromA += off;
|
|
7119
|
+
toA += off;
|
|
7120
|
+
if (toA <= this.from) { // Before the window
|
|
7121
|
+
this.from += dLen;
|
|
7122
|
+
this.to += dLen;
|
|
7123
|
+
}
|
|
7124
|
+
else if (fromA < this.to) { // Overlaps with window
|
|
7125
|
+
if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
|
|
7126
|
+
abort = true;
|
|
7127
|
+
return;
|
|
7128
|
+
}
|
|
7129
|
+
this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
|
|
7130
|
+
this.to += dLen;
|
|
7131
|
+
}
|
|
7132
|
+
off += dLen;
|
|
7133
|
+
});
|
|
7134
|
+
if (pending && !abort)
|
|
7135
|
+
this.revertPending(update.state);
|
|
7136
|
+
return !abort;
|
|
7137
|
+
}
|
|
7138
|
+
update(update) {
|
|
7139
|
+
if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
|
|
7140
|
+
this.pendingContextChange = null;
|
|
7141
|
+
this.resetRange(update.state);
|
|
7142
|
+
this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
|
|
7143
|
+
this.setSelection(update.state);
|
|
7144
|
+
}
|
|
7145
|
+
else if (update.docChanged || update.selectionSet) {
|
|
7146
|
+
this.setSelection(update.state);
|
|
7147
|
+
}
|
|
7148
|
+
if (update.geometryChanged || update.docChanged || update.selectionSet)
|
|
7149
|
+
update.view.requestMeasure(this.measureReq);
|
|
7150
|
+
}
|
|
7151
|
+
resetRange(state) {
|
|
7152
|
+
let { head } = state.selection.main;
|
|
7153
|
+
this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
|
|
7154
|
+
this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
|
|
7155
|
+
}
|
|
7156
|
+
revertPending(state) {
|
|
7157
|
+
let pending = this.pendingContextChange;
|
|
7158
|
+
this.pendingContextChange = null;
|
|
7159
|
+
this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.to + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
|
|
7160
|
+
}
|
|
7161
|
+
setSelection(state) {
|
|
7162
|
+
let { main } = state.selection;
|
|
7163
|
+
let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
|
|
7164
|
+
let end = this.toContextPos(main.head);
|
|
7165
|
+
if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
|
|
7166
|
+
this.editContext.updateSelection(start, end);
|
|
7167
|
+
}
|
|
7168
|
+
rangeIsValid(state) {
|
|
7169
|
+
let { head } = state.selection.main;
|
|
7170
|
+
return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
|
|
7171
|
+
this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
|
|
7172
|
+
this.to - this.from > 10000 /* CxVp.Margin */ * 3);
|
|
7173
|
+
}
|
|
7174
|
+
toEditorPos(contextPos) { return contextPos + this.from; }
|
|
7175
|
+
toContextPos(editorPos) { return editorPos - this.from; }
|
|
7176
|
+
}
|
|
6990
7177
|
|
|
6991
7178
|
// The editor's update state machine looks something like this:
|
|
6992
7179
|
//
|
|
@@ -8257,8 +8444,9 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
8257
8444
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
8258
8445
|
if (!scopeObj._any)
|
|
8259
8446
|
scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
|
|
8447
|
+
let { any } = b;
|
|
8260
8448
|
for (let key in scopeObj)
|
|
8261
|
-
scopeObj[key].run.push(
|
|
8449
|
+
scopeObj[key].run.push(view => any(view, currentKeyEvent));
|
|
8262
8450
|
}
|
|
8263
8451
|
let name = b[platform] || b.key;
|
|
8264
8452
|
if (!name)
|
|
@@ -8271,7 +8459,9 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
8271
8459
|
}
|
|
8272
8460
|
return bound;
|
|
8273
8461
|
}
|
|
8462
|
+
let currentKeyEvent = null;
|
|
8274
8463
|
function runHandlers(map, event, view, scope) {
|
|
8464
|
+
currentKeyEvent = event;
|
|
8275
8465
|
let name = w3cKeyname.keyName(event);
|
|
8276
8466
|
let charCode = state.codePointAt(name, 0), isChar = state.codePointSize(charCode) == name.length && name != " ";
|
|
8277
8467
|
let prefix = "", handled = false, prevented = false, stopPropagation = false;
|
|
@@ -8288,7 +8478,7 @@ function runHandlers(map, event, view, scope) {
|
|
|
8288
8478
|
for (let cmd of binding.run)
|
|
8289
8479
|
if (!ran.has(cmd)) {
|
|
8290
8480
|
ran.add(cmd);
|
|
8291
|
-
if (cmd(view
|
|
8481
|
+
if (cmd(view)) {
|
|
8292
8482
|
if (binding.stopPropagation)
|
|
8293
8483
|
stopPropagation = true;
|
|
8294
8484
|
return true;
|
|
@@ -8330,6 +8520,7 @@ function runHandlers(map, event, view, scope) {
|
|
|
8330
8520
|
handled = true;
|
|
8331
8521
|
if (handled && stopPropagation)
|
|
8332
8522
|
event.stopPropagation();
|
|
8523
|
+
currentKeyEvent = null;
|
|
8333
8524
|
return handled;
|
|
8334
8525
|
}
|
|
8335
8526
|
|
package/dist/index.js
CHANGED
|
@@ -2364,6 +2364,7 @@ class ScrollTarget {
|
|
|
2364
2364
|
}
|
|
2365
2365
|
}
|
|
2366
2366
|
const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|
2367
|
+
const setEditContextFormatting = /*@__PURE__*/StateEffect.define();
|
|
2367
2368
|
/**
|
|
2368
2369
|
Log or report an unhandled exception in client code. Should
|
|
2369
2370
|
probably only be used by extension code that allows client code to
|
|
@@ -2700,10 +2701,11 @@ class DocView extends ContentView {
|
|
|
2700
2701
|
super();
|
|
2701
2702
|
this.view = view;
|
|
2702
2703
|
this.decorations = [];
|
|
2703
|
-
this.dynamicDecorationMap = [];
|
|
2704
|
+
this.dynamicDecorationMap = [false];
|
|
2704
2705
|
this.domChanged = null;
|
|
2705
2706
|
this.hasComposition = null;
|
|
2706
2707
|
this.markedForComposition = new Set;
|
|
2708
|
+
this.editContextFormatting = Decoration.none;
|
|
2707
2709
|
this.lastCompositionAfterCursor = false;
|
|
2708
2710
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2709
2711
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
@@ -2742,8 +2744,9 @@ class DocView extends ContentView {
|
|
|
2742
2744
|
this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
|
|
2743
2745
|
}
|
|
2744
2746
|
}
|
|
2747
|
+
this.updateEditContextFormatting(update);
|
|
2745
2748
|
let readCompositionAt = -1;
|
|
2746
|
-
if (this.view.inputState.composing >= 0) {
|
|
2749
|
+
if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
|
|
2747
2750
|
if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
|
|
2748
2751
|
readCompositionAt = this.domChanged.newSel.head;
|
|
2749
2752
|
else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
|
|
@@ -2851,6 +2854,14 @@ class DocView extends ContentView {
|
|
|
2851
2854
|
if (composition)
|
|
2852
2855
|
this.fixCompositionDOM(composition);
|
|
2853
2856
|
}
|
|
2857
|
+
updateEditContextFormatting(update) {
|
|
2858
|
+
this.editContextFormatting = this.editContextFormatting.map(update.changes);
|
|
2859
|
+
for (let tr of update.transactions)
|
|
2860
|
+
for (let effect of tr.effects)
|
|
2861
|
+
if (effect.is(setEditContextFormatting)) {
|
|
2862
|
+
this.editContextFormatting = effect.value;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2854
2865
|
compositionView(composition) {
|
|
2855
2866
|
let cur = new TextView(composition.text.nodeValue);
|
|
2856
2867
|
cur.flags |= 8 /* ViewFlag.Composition */;
|
|
@@ -3176,7 +3187,7 @@ class DocView extends ContentView {
|
|
|
3176
3187
|
return Decoration.set(deco);
|
|
3177
3188
|
}
|
|
3178
3189
|
updateDeco() {
|
|
3179
|
-
let i =
|
|
3190
|
+
let i = 1;
|
|
3180
3191
|
let allDeco = this.view.state.facet(decorations).map(d => {
|
|
3181
3192
|
let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
|
|
3182
3193
|
return dynamic ? d(this.view) : d;
|
|
@@ -3192,6 +3203,7 @@ class DocView extends ContentView {
|
|
|
3192
3203
|
allDeco.push(RangeSet.join(outerDeco));
|
|
3193
3204
|
}
|
|
3194
3205
|
this.decorations = [
|
|
3206
|
+
this.editContextFormatting,
|
|
3195
3207
|
...allDeco,
|
|
3196
3208
|
this.computeBlockGapDeco(),
|
|
3197
3209
|
this.view.viewState.lineGapDeco
|
|
@@ -3878,6 +3890,7 @@ class InputState {
|
|
|
3878
3890
|
this.mouseSelection = mouseSelection;
|
|
3879
3891
|
}
|
|
3880
3892
|
update(update) {
|
|
3893
|
+
this.view.observer.update(update);
|
|
3881
3894
|
if (this.mouseSelection)
|
|
3882
3895
|
this.mouseSelection.update(update);
|
|
3883
3896
|
if (this.draggedContent && update.docChanged)
|
|
@@ -4468,6 +4481,8 @@ observers.blur = view => {
|
|
|
4468
4481
|
updateForFocusChange(view);
|
|
4469
4482
|
};
|
|
4470
4483
|
observers.compositionstart = observers.compositionupdate = view => {
|
|
4484
|
+
if (view.observer.editContext)
|
|
4485
|
+
return; // Composition handled by edit context
|
|
4471
4486
|
if (view.inputState.compositionFirstChange == null)
|
|
4472
4487
|
view.inputState.compositionFirstChange = true;
|
|
4473
4488
|
if (view.inputState.composing < 0) {
|
|
@@ -4476,6 +4491,8 @@ observers.compositionstart = observers.compositionupdate = view => {
|
|
|
4476
4491
|
}
|
|
4477
4492
|
};
|
|
4478
4493
|
observers.compositionend = view => {
|
|
4494
|
+
if (view.observer.editContext)
|
|
4495
|
+
return; // Composition handled by edit context
|
|
4479
4496
|
view.inputState.composing = -1;
|
|
4480
4497
|
view.inputState.compositionEndedAt = Date.now();
|
|
4481
4498
|
view.inputState.compositionPendingKey = true;
|
|
@@ -6351,35 +6368,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6351
6368
|
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
6352
6369
|
}
|
|
6353
6370
|
if (change) {
|
|
6354
|
-
|
|
6355
|
-
return true;
|
|
6356
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
6357
|
-
// backspace, or delete. So this detects changes that look like
|
|
6358
|
-
// they're caused by those keys, and reinterprets them as key
|
|
6359
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
6360
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
6361
|
-
// reliable in all situations.)
|
|
6362
|
-
if (browser.android &&
|
|
6363
|
-
((change.to == sel.to &&
|
|
6364
|
-
// GBoard will sometimes remove a space it just inserted
|
|
6365
|
-
// after a completion when you press enter
|
|
6366
|
-
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6367
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6368
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6369
|
-
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6370
|
-
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6371
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6372
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6373
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6374
|
-
return true;
|
|
6375
|
-
let text = change.insert.toString();
|
|
6376
|
-
if (view.inputState.composing >= 0)
|
|
6377
|
-
view.inputState.composing++;
|
|
6378
|
-
let defaultTr;
|
|
6379
|
-
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6380
|
-
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6381
|
-
view.dispatch(defaultInsert());
|
|
6382
|
-
return true;
|
|
6371
|
+
return applyDOMChangeInner(view, change, newSel, lastKey);
|
|
6383
6372
|
}
|
|
6384
6373
|
else if (newSel && !newSel.main.eq(sel)) {
|
|
6385
6374
|
let scrollIntoView = false, userEvent = "select";
|
|
@@ -6395,6 +6384,38 @@ function applyDOMChange(view, domChange) {
|
|
|
6395
6384
|
return false;
|
|
6396
6385
|
}
|
|
6397
6386
|
}
|
|
6387
|
+
function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
6388
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6389
|
+
return true;
|
|
6390
|
+
let sel = view.state.selection.main;
|
|
6391
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
6392
|
+
// backspace, or delete. So this detects changes that look like
|
|
6393
|
+
// they're caused by those keys, and reinterprets them as key
|
|
6394
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
6395
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
6396
|
+
// reliable in all situations.)
|
|
6397
|
+
if (browser.android &&
|
|
6398
|
+
((change.to == sel.to &&
|
|
6399
|
+
// GBoard will sometimes remove a space it just inserted
|
|
6400
|
+
// after a completion when you press enter
|
|
6401
|
+
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6402
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6403
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6404
|
+
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6405
|
+
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6406
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6407
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6408
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6409
|
+
return true;
|
|
6410
|
+
let text = change.insert.toString();
|
|
6411
|
+
if (view.inputState.composing >= 0)
|
|
6412
|
+
view.inputState.composing++;
|
|
6413
|
+
let defaultTr;
|
|
6414
|
+
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6415
|
+
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6416
|
+
view.dispatch(defaultInsert());
|
|
6417
|
+
return true;
|
|
6418
|
+
}
|
|
6398
6419
|
function applyDefaultInsert(view, change, newSel) {
|
|
6399
6420
|
let tr, startState = view.state, sel = startState.selection.main;
|
|
6400
6421
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
@@ -6521,6 +6542,7 @@ class DOMObserver {
|
|
|
6521
6542
|
constructor(view) {
|
|
6522
6543
|
this.view = view;
|
|
6523
6544
|
this.active = false;
|
|
6545
|
+
this.editContext = null;
|
|
6524
6546
|
// The known selection. Kept in our own object, as opposed to just
|
|
6525
6547
|
// directly accessing the selection because:
|
|
6526
6548
|
// - Safari doesn't report the right selection in shadow DOM
|
|
@@ -6565,6 +6587,10 @@ class DOMObserver {
|
|
|
6565
6587
|
else
|
|
6566
6588
|
this.flush();
|
|
6567
6589
|
});
|
|
6590
|
+
if (window.EditContext) {
|
|
6591
|
+
this.editContext = new EditContextManager(view);
|
|
6592
|
+
view.contentDOM.editContext = this.editContext.editContext;
|
|
6593
|
+
}
|
|
6568
6594
|
if (useCharData)
|
|
6569
6595
|
this.onCharData = (event) => {
|
|
6570
6596
|
this.queue.push({ target: event.target,
|
|
@@ -6615,6 +6641,8 @@ class DOMObserver {
|
|
|
6615
6641
|
onScroll(e) {
|
|
6616
6642
|
if (this.intersecting)
|
|
6617
6643
|
this.flush(false);
|
|
6644
|
+
if (this.editContext)
|
|
6645
|
+
this.view.requestMeasure(this.editContext.measureReq);
|
|
6618
6646
|
this.onScrollChanged(e);
|
|
6619
6647
|
}
|
|
6620
6648
|
onResize() {
|
|
@@ -6923,6 +6951,10 @@ class DOMObserver {
|
|
|
6923
6951
|
win.removeEventListener("beforeprint", this.onPrint);
|
|
6924
6952
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6925
6953
|
}
|
|
6954
|
+
update(update) {
|
|
6955
|
+
if (this.editContext)
|
|
6956
|
+
this.editContext.update(update);
|
|
6957
|
+
}
|
|
6926
6958
|
destroy() {
|
|
6927
6959
|
var _a, _b, _c;
|
|
6928
6960
|
this.stop();
|
|
@@ -6982,6 +7014,161 @@ function safariSelectionRangeHack(view, selection) {
|
|
|
6982
7014
|
view.contentDOM.removeEventListener("beforeinput", read, true);
|
|
6983
7015
|
return found ? buildSelectionRangeFromRange(view, found) : null;
|
|
6984
7016
|
}
|
|
7017
|
+
class EditContextManager {
|
|
7018
|
+
constructor(view) {
|
|
7019
|
+
// The document window for which the text in the context is
|
|
7020
|
+
// maintained. For large documents, this may be smaller than the
|
|
7021
|
+
// editor document. This window always includes the selection head.
|
|
7022
|
+
this.from = 0;
|
|
7023
|
+
this.to = 0;
|
|
7024
|
+
// When applying a transaction, this is used to compare the change
|
|
7025
|
+
// made to the context content to the change in the transaction in
|
|
7026
|
+
// order to make the minimal changes to the context (since touching
|
|
7027
|
+
// that sometimes breaks series of multiple edits made for a single
|
|
7028
|
+
// user action on some Android keyboards)
|
|
7029
|
+
this.pendingContextChange = null;
|
|
7030
|
+
this.resetRange(view.state);
|
|
7031
|
+
let context = this.editContext = new window.EditContext({
|
|
7032
|
+
text: view.state.doc.sliceString(this.from, this.to),
|
|
7033
|
+
selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
|
|
7034
|
+
selectionEnd: this.toContextPos(view.state.selection.main.head)
|
|
7035
|
+
});
|
|
7036
|
+
context.addEventListener("textupdate", e => {
|
|
7037
|
+
let { anchor } = view.state.selection.main;
|
|
7038
|
+
let change = { from: this.toEditorPos(e.updateRangeStart),
|
|
7039
|
+
to: this.toEditorPos(e.updateRangeEnd),
|
|
7040
|
+
insert: Text.of(e.text.split("\n")) };
|
|
7041
|
+
// If the window doesn't include the anchor, assume changes
|
|
7042
|
+
// adjacent to a side go up to the anchor.
|
|
7043
|
+
if (change.from == this.from && anchor < this.from)
|
|
7044
|
+
change.from = anchor;
|
|
7045
|
+
else if (change.to == this.to && anchor > this.to)
|
|
7046
|
+
change.to = anchor;
|
|
7047
|
+
// Edit context sometimes fire empty changes
|
|
7048
|
+
if (change.from == change.to && !change.insert.length)
|
|
7049
|
+
return;
|
|
7050
|
+
this.pendingContextChange = change;
|
|
7051
|
+
applyDOMChangeInner(view, change, EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
|
|
7052
|
+
// If the transaction didn't flush our change, revert it so
|
|
7053
|
+
// that the context is in sync with the editor state again.
|
|
7054
|
+
if (this.pendingContextChange)
|
|
7055
|
+
this.revertPending(view.state);
|
|
7056
|
+
});
|
|
7057
|
+
context.addEventListener("characterboundsupdate", e => {
|
|
7058
|
+
let rects = [], prev = null;
|
|
7059
|
+
for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
|
|
7060
|
+
let rect = view.coordsForChar(i);
|
|
7061
|
+
prev = (rect && new DOMRect(rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top))
|
|
7062
|
+
|| prev || new DOMRect;
|
|
7063
|
+
rects.push(prev);
|
|
7064
|
+
}
|
|
7065
|
+
context.updateCharacterBounds(e.rangeStart, rects);
|
|
7066
|
+
});
|
|
7067
|
+
context.addEventListener("textformatupdate", e => {
|
|
7068
|
+
let deco = [];
|
|
7069
|
+
for (let format of e.getTextFormats()) {
|
|
7070
|
+
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7071
|
+
if (lineStyle != "None" && thickness != "None") {
|
|
7072
|
+
let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
|
|
7073
|
+
deco.push(Decoration.mark({ attributes: { style } })
|
|
7074
|
+
.range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
|
|
7078
|
+
});
|
|
7079
|
+
context.addEventListener("compositionstart", () => {
|
|
7080
|
+
if (view.inputState.composing < 0) {
|
|
7081
|
+
view.inputState.composing = 0;
|
|
7082
|
+
view.inputState.compositionFirstChange = true;
|
|
7083
|
+
}
|
|
7084
|
+
});
|
|
7085
|
+
context.addEventListener("compositionend", () => {
|
|
7086
|
+
view.inputState.composing = -1;
|
|
7087
|
+
view.inputState.compositionFirstChange = null;
|
|
7088
|
+
});
|
|
7089
|
+
this.measureReq = { read: view => {
|
|
7090
|
+
this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
|
|
7091
|
+
let sel = getSelection(view.root);
|
|
7092
|
+
if (sel && sel.rangeCount)
|
|
7093
|
+
this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
|
|
7094
|
+
} };
|
|
7095
|
+
}
|
|
7096
|
+
applyEdits(update) {
|
|
7097
|
+
let off = 0, abort = false, pending = this.pendingContextChange;
|
|
7098
|
+
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
|
|
7099
|
+
if (abort)
|
|
7100
|
+
return;
|
|
7101
|
+
let dLen = insert.length - (toA - fromA);
|
|
7102
|
+
if (pending && toA >= pending.to) {
|
|
7103
|
+
if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
|
|
7104
|
+
pending = this.pendingContextChange = null; // Match
|
|
7105
|
+
off += dLen;
|
|
7106
|
+
return;
|
|
7107
|
+
}
|
|
7108
|
+
else { // Mismatch, revert
|
|
7109
|
+
pending = null;
|
|
7110
|
+
this.revertPending(update.state);
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
fromA += off;
|
|
7114
|
+
toA += off;
|
|
7115
|
+
if (toA <= this.from) { // Before the window
|
|
7116
|
+
this.from += dLen;
|
|
7117
|
+
this.to += dLen;
|
|
7118
|
+
}
|
|
7119
|
+
else if (fromA < this.to) { // Overlaps with window
|
|
7120
|
+
if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
|
|
7121
|
+
abort = true;
|
|
7122
|
+
return;
|
|
7123
|
+
}
|
|
7124
|
+
this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
|
|
7125
|
+
this.to += dLen;
|
|
7126
|
+
}
|
|
7127
|
+
off += dLen;
|
|
7128
|
+
});
|
|
7129
|
+
if (pending && !abort)
|
|
7130
|
+
this.revertPending(update.state);
|
|
7131
|
+
return !abort;
|
|
7132
|
+
}
|
|
7133
|
+
update(update) {
|
|
7134
|
+
if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
|
|
7135
|
+
this.pendingContextChange = null;
|
|
7136
|
+
this.resetRange(update.state);
|
|
7137
|
+
this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
|
|
7138
|
+
this.setSelection(update.state);
|
|
7139
|
+
}
|
|
7140
|
+
else if (update.docChanged || update.selectionSet) {
|
|
7141
|
+
this.setSelection(update.state);
|
|
7142
|
+
}
|
|
7143
|
+
if (update.geometryChanged || update.docChanged || update.selectionSet)
|
|
7144
|
+
update.view.requestMeasure(this.measureReq);
|
|
7145
|
+
}
|
|
7146
|
+
resetRange(state) {
|
|
7147
|
+
let { head } = state.selection.main;
|
|
7148
|
+
this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
|
|
7149
|
+
this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
|
|
7150
|
+
}
|
|
7151
|
+
revertPending(state) {
|
|
7152
|
+
let pending = this.pendingContextChange;
|
|
7153
|
+
this.pendingContextChange = null;
|
|
7154
|
+
this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.to + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
|
|
7155
|
+
}
|
|
7156
|
+
setSelection(state) {
|
|
7157
|
+
let { main } = state.selection;
|
|
7158
|
+
let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
|
|
7159
|
+
let end = this.toContextPos(main.head);
|
|
7160
|
+
if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
|
|
7161
|
+
this.editContext.updateSelection(start, end);
|
|
7162
|
+
}
|
|
7163
|
+
rangeIsValid(state) {
|
|
7164
|
+
let { head } = state.selection.main;
|
|
7165
|
+
return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
|
|
7166
|
+
this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
|
|
7167
|
+
this.to - this.from > 10000 /* CxVp.Margin */ * 3);
|
|
7168
|
+
}
|
|
7169
|
+
toEditorPos(contextPos) { return contextPos + this.from; }
|
|
7170
|
+
toContextPos(editorPos) { return editorPos - this.from; }
|
|
7171
|
+
}
|
|
6985
7172
|
|
|
6986
7173
|
// The editor's update state machine looks something like this:
|
|
6987
7174
|
//
|
|
@@ -8252,8 +8439,9 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
8252
8439
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
8253
8440
|
if (!scopeObj._any)
|
|
8254
8441
|
scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
|
|
8442
|
+
let { any } = b;
|
|
8255
8443
|
for (let key in scopeObj)
|
|
8256
|
-
scopeObj[key].run.push(
|
|
8444
|
+
scopeObj[key].run.push(view => any(view, currentKeyEvent));
|
|
8257
8445
|
}
|
|
8258
8446
|
let name = b[platform] || b.key;
|
|
8259
8447
|
if (!name)
|
|
@@ -8266,7 +8454,9 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
8266
8454
|
}
|
|
8267
8455
|
return bound;
|
|
8268
8456
|
}
|
|
8457
|
+
let currentKeyEvent = null;
|
|
8269
8458
|
function runHandlers(map, event, view, scope) {
|
|
8459
|
+
currentKeyEvent = event;
|
|
8270
8460
|
let name = keyName(event);
|
|
8271
8461
|
let charCode = codePointAt(name, 0), isChar = codePointSize(charCode) == name.length && name != " ";
|
|
8272
8462
|
let prefix = "", handled = false, prevented = false, stopPropagation = false;
|
|
@@ -8283,7 +8473,7 @@ function runHandlers(map, event, view, scope) {
|
|
|
8283
8473
|
for (let cmd of binding.run)
|
|
8284
8474
|
if (!ran.has(cmd)) {
|
|
8285
8475
|
ran.add(cmd);
|
|
8286
|
-
if (cmd(view
|
|
8476
|
+
if (cmd(view)) {
|
|
8287
8477
|
if (binding.stopPropagation)
|
|
8288
8478
|
stopPropagation = true;
|
|
8289
8479
|
return true;
|
|
@@ -8325,6 +8515,7 @@ function runHandlers(map, event, view, scope) {
|
|
|
8325
8515
|
handled = true;
|
|
8326
8516
|
if (handled && stopPropagation)
|
|
8327
8517
|
event.stopPropagation();
|
|
8518
|
+
currentKeyEvent = null;
|
|
8328
8519
|
return handled;
|
|
8329
8520
|
}
|
|
8330
8521
|
|