11030 lines
381 KiB
JavaScript
11030 lines
381 KiB
JavaScript
|
|
import {
|
|||
|
|
Annotation,
|
|||
|
|
ChangeSet,
|
|||
|
|
CharCategory,
|
|||
|
|
EditorSelection,
|
|||
|
|
EditorState,
|
|||
|
|
Facet,
|
|||
|
|
MapMode,
|
|||
|
|
Prec,
|
|||
|
|
RangeSet,
|
|||
|
|
RangeSetBuilder,
|
|||
|
|
RangeValue,
|
|||
|
|
StateEffect,
|
|||
|
|
StateField,
|
|||
|
|
Text,
|
|||
|
|
Transaction,
|
|||
|
|
codePointAt,
|
|||
|
|
codePointSize,
|
|||
|
|
combineConfig,
|
|||
|
|
countColumn,
|
|||
|
|
findClusterBreak,
|
|||
|
|
findColumn
|
|||
|
|
} from "./chunk-4MUKC4ON.js";
|
|||
|
|
|
|||
|
|
// node_modules/style-mod/src/style-mod.js
|
|||
|
|
var C = "ͼ";
|
|||
|
|
var COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C);
|
|||
|
|
var SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet");
|
|||
|
|
var top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {};
|
|||
|
|
var StyleModule = class {
|
|||
|
|
// :: (Object<Style>, ?{finish: ?(string) → string})
|
|||
|
|
// Create a style module from the given spec.
|
|||
|
|
//
|
|||
|
|
// When `finish` is given, it is called on regular (non-`@`)
|
|||
|
|
// selectors (after `&` expansion) to compute the final selector.
|
|||
|
|
constructor(spec, options) {
|
|||
|
|
this.rules = [];
|
|||
|
|
let { finish } = options || {};
|
|||
|
|
function splitSelector(selector) {
|
|||
|
|
return /^@/.test(selector) ? [selector] : selector.split(/,\s*/);
|
|||
|
|
}
|
|||
|
|
function render(selectors, spec2, target, isKeyframes) {
|
|||
|
|
let local = [], isAt = /^@(\w+)\b/.exec(selectors[0]), keyframes = isAt && isAt[1] == "keyframes";
|
|||
|
|
if (isAt && spec2 == null) return target.push(selectors[0] + ";");
|
|||
|
|
for (let prop in spec2) {
|
|||
|
|
let value = spec2[prop];
|
|||
|
|
if (/&/.test(prop)) {
|
|||
|
|
render(
|
|||
|
|
prop.split(/,\s*/).map((part) => selectors.map((sel) => part.replace(/&/, sel))).reduce((a, b) => a.concat(b)),
|
|||
|
|
value,
|
|||
|
|
target
|
|||
|
|
);
|
|||
|
|
} else if (value && typeof value == "object") {
|
|||
|
|
if (!isAt) throw new RangeError("The value of a property (" + prop + ") should be a primitive value.");
|
|||
|
|
render(splitSelector(prop), value, local, keyframes);
|
|||
|
|
} else if (value != null) {
|
|||
|
|
local.push(prop.replace(/_.*/, "").replace(/[A-Z]/g, (l) => "-" + l.toLowerCase()) + ": " + value + ";");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (local.length || keyframes) {
|
|||
|
|
target.push((finish && !isAt && !isKeyframes ? selectors.map(finish) : selectors).join(", ") + " {" + local.join(" ") + "}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let prop in spec) render(splitSelector(prop), spec[prop], this.rules);
|
|||
|
|
}
|
|||
|
|
// :: () → string
|
|||
|
|
// Returns a string containing the module's CSS rules.
|
|||
|
|
getRules() {
|
|||
|
|
return this.rules.join("\n");
|
|||
|
|
}
|
|||
|
|
// :: () → string
|
|||
|
|
// Generate a new unique CSS class name.
|
|||
|
|
static newName() {
|
|||
|
|
let id = top[COUNT] || 1;
|
|||
|
|
top[COUNT] = id + 1;
|
|||
|
|
return C + id.toString(36);
|
|||
|
|
}
|
|||
|
|
// :: (union<Document, ShadowRoot>, union<[StyleModule], StyleModule>, ?{nonce: ?string})
|
|||
|
|
//
|
|||
|
|
// Mount the given set of modules in the given DOM root, which ensures
|
|||
|
|
// that the CSS rules defined by the module are available in that
|
|||
|
|
// context.
|
|||
|
|
//
|
|||
|
|
// Rules are only added to the document once per root.
|
|||
|
|
//
|
|||
|
|
// Rule order will follow the order of the modules, so that rules from
|
|||
|
|
// modules later in the array take precedence of those from earlier
|
|||
|
|
// modules. If you call this function multiple times for the same root
|
|||
|
|
// in a way that changes the order of already mounted modules, the old
|
|||
|
|
// order will be changed.
|
|||
|
|
//
|
|||
|
|
// If a Content Security Policy nonce is provided, it is added to
|
|||
|
|
// the `<style>` tag generated by the library.
|
|||
|
|
static mount(root, modules, options) {
|
|||
|
|
let set = root[SET], nonce = options && options.nonce;
|
|||
|
|
if (!set) set = new StyleSet(root, nonce);
|
|||
|
|
else if (nonce) set.setNonce(nonce);
|
|||
|
|
set.mount(Array.isArray(modules) ? modules : [modules], root);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var adoptedSet = /* @__PURE__ */ new Map();
|
|||
|
|
var StyleSet = class {
|
|||
|
|
constructor(root, nonce) {
|
|||
|
|
let doc2 = root.ownerDocument || root, win = doc2.defaultView;
|
|||
|
|
if (!root.head && root.adoptedStyleSheets && win.CSSStyleSheet) {
|
|||
|
|
let adopted = adoptedSet.get(doc2);
|
|||
|
|
if (adopted) return root[SET] = adopted;
|
|||
|
|
this.sheet = new win.CSSStyleSheet();
|
|||
|
|
adoptedSet.set(doc2, this);
|
|||
|
|
} else {
|
|||
|
|
this.styleTag = doc2.createElement("style");
|
|||
|
|
if (nonce) this.styleTag.setAttribute("nonce", nonce);
|
|||
|
|
}
|
|||
|
|
this.modules = [];
|
|||
|
|
root[SET] = this;
|
|||
|
|
}
|
|||
|
|
mount(modules, root) {
|
|||
|
|
let sheet = this.sheet;
|
|||
|
|
let pos = 0, j = 0;
|
|||
|
|
for (let i = 0; i < modules.length; i++) {
|
|||
|
|
let mod = modules[i], index = this.modules.indexOf(mod);
|
|||
|
|
if (index < j && index > -1) {
|
|||
|
|
this.modules.splice(index, 1);
|
|||
|
|
j--;
|
|||
|
|
index = -1;
|
|||
|
|
}
|
|||
|
|
if (index == -1) {
|
|||
|
|
this.modules.splice(j++, 0, mod);
|
|||
|
|
if (sheet) for (let k = 0; k < mod.rules.length; k++)
|
|||
|
|
sheet.insertRule(mod.rules[k], pos++);
|
|||
|
|
} else {
|
|||
|
|
while (j < index) pos += this.modules[j++].rules.length;
|
|||
|
|
pos += mod.rules.length;
|
|||
|
|
j++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (sheet) {
|
|||
|
|
if (root.adoptedStyleSheets.indexOf(this.sheet) < 0)
|
|||
|
|
root.adoptedStyleSheets = [this.sheet, ...root.adoptedStyleSheets];
|
|||
|
|
} else {
|
|||
|
|
let text = "";
|
|||
|
|
for (let i = 0; i < this.modules.length; i++)
|
|||
|
|
text += this.modules[i].getRules() + "\n";
|
|||
|
|
this.styleTag.textContent = text;
|
|||
|
|
let target = root.head || root;
|
|||
|
|
if (this.styleTag.parentNode != target)
|
|||
|
|
target.insertBefore(this.styleTag, target.firstChild);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
setNonce(nonce) {
|
|||
|
|
if (this.styleTag && this.styleTag.getAttribute("nonce") != nonce)
|
|||
|
|
this.styleTag.setAttribute("nonce", nonce);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// node_modules/w3c-keyname/index.js
|
|||
|
|
var base = {
|
|||
|
|
8: "Backspace",
|
|||
|
|
9: "Tab",
|
|||
|
|
10: "Enter",
|
|||
|
|
12: "NumLock",
|
|||
|
|
13: "Enter",
|
|||
|
|
16: "Shift",
|
|||
|
|
17: "Control",
|
|||
|
|
18: "Alt",
|
|||
|
|
20: "CapsLock",
|
|||
|
|
27: "Escape",
|
|||
|
|
32: " ",
|
|||
|
|
33: "PageUp",
|
|||
|
|
34: "PageDown",
|
|||
|
|
35: "End",
|
|||
|
|
36: "Home",
|
|||
|
|
37: "ArrowLeft",
|
|||
|
|
38: "ArrowUp",
|
|||
|
|
39: "ArrowRight",
|
|||
|
|
40: "ArrowDown",
|
|||
|
|
44: "PrintScreen",
|
|||
|
|
45: "Insert",
|
|||
|
|
46: "Delete",
|
|||
|
|
59: ";",
|
|||
|
|
61: "=",
|
|||
|
|
91: "Meta",
|
|||
|
|
92: "Meta",
|
|||
|
|
106: "*",
|
|||
|
|
107: "+",
|
|||
|
|
108: ",",
|
|||
|
|
109: "-",
|
|||
|
|
110: ".",
|
|||
|
|
111: "/",
|
|||
|
|
144: "NumLock",
|
|||
|
|
145: "ScrollLock",
|
|||
|
|
160: "Shift",
|
|||
|
|
161: "Shift",
|
|||
|
|
162: "Control",
|
|||
|
|
163: "Control",
|
|||
|
|
164: "Alt",
|
|||
|
|
165: "Alt",
|
|||
|
|
173: "-",
|
|||
|
|
186: ";",
|
|||
|
|
187: "=",
|
|||
|
|
188: ",",
|
|||
|
|
189: "-",
|
|||
|
|
190: ".",
|
|||
|
|
191: "/",
|
|||
|
|
192: "`",
|
|||
|
|
219: "[",
|
|||
|
|
220: "\\",
|
|||
|
|
221: "]",
|
|||
|
|
222: "'"
|
|||
|
|
};
|
|||
|
|
var shift = {
|
|||
|
|
48: ")",
|
|||
|
|
49: "!",
|
|||
|
|
50: "@",
|
|||
|
|
51: "#",
|
|||
|
|
52: "$",
|
|||
|
|
53: "%",
|
|||
|
|
54: "^",
|
|||
|
|
55: "&",
|
|||
|
|
56: "*",
|
|||
|
|
57: "(",
|
|||
|
|
59: ":",
|
|||
|
|
61: "+",
|
|||
|
|
173: "_",
|
|||
|
|
186: ":",
|
|||
|
|
187: "+",
|
|||
|
|
188: "<",
|
|||
|
|
189: "_",
|
|||
|
|
190: ">",
|
|||
|
|
191: "?",
|
|||
|
|
192: "~",
|
|||
|
|
219: "{",
|
|||
|
|
220: "|",
|
|||
|
|
221: "}",
|
|||
|
|
222: '"'
|
|||
|
|
};
|
|||
|
|
var mac = typeof navigator != "undefined" && /Mac/.test(navigator.platform);
|
|||
|
|
var ie = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
|
|||
|
|
for (i = 0; i < 10; i++) base[48 + i] = base[96 + i] = String(i);
|
|||
|
|
var i;
|
|||
|
|
for (i = 1; i <= 24; i++) base[i + 111] = "F" + i;
|
|||
|
|
var i;
|
|||
|
|
for (i = 65; i <= 90; i++) {
|
|||
|
|
base[i] = String.fromCharCode(i + 32);
|
|||
|
|
shift[i] = String.fromCharCode(i);
|
|||
|
|
}
|
|||
|
|
var i;
|
|||
|
|
for (code in base) if (!shift.hasOwnProperty(code)) shift[code] = base[code];
|
|||
|
|
var code;
|
|||
|
|
function keyName(event) {
|
|||
|
|
var ignoreKey = mac && event.metaKey && event.shiftKey && !event.ctrlKey && !event.altKey || ie && event.shiftKey && event.key && event.key.length == 1 || event.key == "Unidentified";
|
|||
|
|
var name = !ignoreKey && event.key || (event.shiftKey ? shift : base)[event.keyCode] || event.key || "Unidentified";
|
|||
|
|
if (name == "Esc") name = "Escape";
|
|||
|
|
if (name == "Del") name = "Delete";
|
|||
|
|
if (name == "Left") name = "ArrowLeft";
|
|||
|
|
if (name == "Up") name = "ArrowUp";
|
|||
|
|
if (name == "Right") name = "ArrowRight";
|
|||
|
|
if (name == "Down") name = "ArrowDown";
|
|||
|
|
return name;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// node_modules/crelt/index.js
|
|||
|
|
function crelt() {
|
|||
|
|
var elt = arguments[0];
|
|||
|
|
if (typeof elt == "string") elt = document.createElement(elt);
|
|||
|
|
var i = 1, next = arguments[1];
|
|||
|
|
if (next && typeof next == "object" && next.nodeType == null && !Array.isArray(next)) {
|
|||
|
|
for (var name in next) if (Object.prototype.hasOwnProperty.call(next, name)) {
|
|||
|
|
var value = next[name];
|
|||
|
|
if (typeof value == "string") elt.setAttribute(name, value);
|
|||
|
|
else if (value != null) elt[name] = value;
|
|||
|
|
}
|
|||
|
|
i++;
|
|||
|
|
}
|
|||
|
|
for (; i < arguments.length; i++) add(elt, arguments[i]);
|
|||
|
|
return elt;
|
|||
|
|
}
|
|||
|
|
function add(elt, child) {
|
|||
|
|
if (typeof child == "string") {
|
|||
|
|
elt.appendChild(document.createTextNode(child));
|
|||
|
|
} else if (child == null) {
|
|||
|
|
} else if (child.nodeType != null) {
|
|||
|
|
elt.appendChild(child);
|
|||
|
|
} else if (Array.isArray(child)) {
|
|||
|
|
for (var i = 0; i < child.length; i++) add(elt, child[i]);
|
|||
|
|
} else {
|
|||
|
|
throw new RangeError("Unsupported child node: " + child);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// node_modules/@codemirror/view/dist/index.js
|
|||
|
|
var nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
|
|||
|
|
var doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
|
|||
|
|
var ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
|
|||
|
|
var ie_upto10 = /MSIE \d/.test(nav.userAgent);
|
|||
|
|
var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
|
|||
|
|
var ie2 = !!(ie_upto10 || ie_11up || ie_edge);
|
|||
|
|
var gecko = !ie2 && /gecko\/(\d+)/i.test(nav.userAgent);
|
|||
|
|
var chrome = !ie2 && /Chrome\/(\d+)/.exec(nav.userAgent);
|
|||
|
|
var webkit = "webkitFontSmoothing" in doc.documentElement.style;
|
|||
|
|
var safari = !ie2 && /Apple Computer/.test(nav.vendor);
|
|||
|
|
var ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
|
|||
|
|
var browser = {
|
|||
|
|
mac: ios || /Mac/.test(nav.platform),
|
|||
|
|
windows: /Win/.test(nav.platform),
|
|||
|
|
linux: /Linux|X11/.test(nav.platform),
|
|||
|
|
ie: ie2,
|
|||
|
|
ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
|
|||
|
|
gecko,
|
|||
|
|
gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|||
|
|
chrome: !!chrome,
|
|||
|
|
chrome_version: chrome ? +chrome[1] : 0,
|
|||
|
|
ios,
|
|||
|
|
android: /Android\b/.test(nav.userAgent),
|
|||
|
|
webkit,
|
|||
|
|
webkit_version: webkit ? +(/\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|||
|
|
safari,
|
|||
|
|
safari_version: safari ? +(/\bVersion\/(\d+(\.\d+)?)/.exec(nav.userAgent) || [0, 0])[1] : 0,
|
|||
|
|
tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
|
|||
|
|
};
|
|||
|
|
function combineAttrs(source, target) {
|
|||
|
|
for (let name in source) {
|
|||
|
|
if (name == "class" && target.class)
|
|||
|
|
target.class += " " + source.class;
|
|||
|
|
else if (name == "style" && target.style)
|
|||
|
|
target.style += ";" + source.style;
|
|||
|
|
else
|
|||
|
|
target[name] = source[name];
|
|||
|
|
}
|
|||
|
|
return target;
|
|||
|
|
}
|
|||
|
|
var noAttrs = /* @__PURE__ */ Object.create(null);
|
|||
|
|
function attrsEq(a, b, ignore) {
|
|||
|
|
if (a == b)
|
|||
|
|
return true;
|
|||
|
|
if (!a)
|
|||
|
|
a = noAttrs;
|
|||
|
|
if (!b)
|
|||
|
|
b = noAttrs;
|
|||
|
|
let keysA = Object.keys(a), keysB = Object.keys(b);
|
|||
|
|
if (keysA.length - (ignore && keysA.indexOf(ignore) > -1 ? 1 : 0) != keysB.length - (ignore && keysB.indexOf(ignore) > -1 ? 1 : 0))
|
|||
|
|
return false;
|
|||
|
|
for (let key of keysA) {
|
|||
|
|
if (key != ignore && (keysB.indexOf(key) == -1 || a[key] !== b[key]))
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
function setAttrs(dom, attrs) {
|
|||
|
|
for (let i = dom.attributes.length - 1; i >= 0; i--) {
|
|||
|
|
let name = dom.attributes[i].name;
|
|||
|
|
if (attrs[name] == null)
|
|||
|
|
dom.removeAttribute(name);
|
|||
|
|
}
|
|||
|
|
for (let name in attrs) {
|
|||
|
|
let value = attrs[name];
|
|||
|
|
if (name == "style")
|
|||
|
|
dom.style.cssText = value;
|
|||
|
|
else if (dom.getAttribute(name) != value)
|
|||
|
|
dom.setAttribute(name, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function updateAttrs(dom, prev, attrs) {
|
|||
|
|
let changed = false;
|
|||
|
|
if (prev) {
|
|||
|
|
for (let name in prev)
|
|||
|
|
if (!(attrs && name in attrs)) {
|
|||
|
|
changed = true;
|
|||
|
|
if (name == "style")
|
|||
|
|
dom.style.cssText = "";
|
|||
|
|
else
|
|||
|
|
dom.removeAttribute(name);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (attrs) {
|
|||
|
|
for (let name in attrs)
|
|||
|
|
if (!(prev && prev[name] == attrs[name])) {
|
|||
|
|
changed = true;
|
|||
|
|
if (name == "style")
|
|||
|
|
dom.style.cssText = attrs[name];
|
|||
|
|
else
|
|||
|
|
dom.setAttribute(name, attrs[name]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return changed;
|
|||
|
|
}
|
|||
|
|
function getAttrs(dom) {
|
|||
|
|
let attrs = /* @__PURE__ */ Object.create(null);
|
|||
|
|
for (let i = 0; i < dom.attributes.length; i++) {
|
|||
|
|
let attr = dom.attributes[i];
|
|||
|
|
attrs[attr.name] = attr.value;
|
|||
|
|
}
|
|||
|
|
return attrs;
|
|||
|
|
}
|
|||
|
|
var WidgetType = class {
|
|||
|
|
/**
|
|||
|
|
Compare this instance to another instance of the same type.
|
|||
|
|
(TypeScript can't express this, but only instances of the same
|
|||
|
|
specific class will be passed to this method.) This is used to
|
|||
|
|
avoid redrawing widgets when they are replaced by a new
|
|||
|
|
decoration of the same type. The default implementation just
|
|||
|
|
returns `false`, which will cause new instances of the widget to
|
|||
|
|
always be redrawn.
|
|||
|
|
*/
|
|||
|
|
eq(widget) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Update a DOM element created by a widget of the same type (but
|
|||
|
|
different, non-`eq` content) to reflect this widget. May return
|
|||
|
|
true to indicate that it could update, false to indicate it
|
|||
|
|
couldn't (in which case the widget will be redrawn). The default
|
|||
|
|
implementation just returns false.
|
|||
|
|
*/
|
|||
|
|
updateDOM(dom, view, from) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
compare(other) {
|
|||
|
|
return this == other || this.constructor == other.constructor && this.eq(other);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The estimated height this widget will have, to be used when
|
|||
|
|
estimating the height of content that hasn't been drawn. May
|
|||
|
|
return -1 to indicate you don't know. The default implementation
|
|||
|
|
returns -1.
|
|||
|
|
*/
|
|||
|
|
get estimatedHeight() {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
For inline widgets that are displayed inline (as opposed to
|
|||
|
|
`inline-block`) and introduce line breaks (through `<br>` tags
|
|||
|
|
or textual newlines), this must indicate the amount of line
|
|||
|
|
breaks they introduce. Defaults to 0.
|
|||
|
|
*/
|
|||
|
|
get lineBreaks() {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Can be used to configure which kinds of events inside the widget
|
|||
|
|
should be ignored by the editor. The default is to ignore all
|
|||
|
|
events.
|
|||
|
|
*/
|
|||
|
|
ignoreEvent(event) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Override the way screen coordinates for positions at/in the
|
|||
|
|
widget are found. `pos` will be the offset into the widget, and
|
|||
|
|
`side` the side of the position that is being queried—less than
|
|||
|
|
zero for before, greater than zero for after, and zero for
|
|||
|
|
directly at that position.
|
|||
|
|
*/
|
|||
|
|
coordsAt(dom, pos, side) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
get isHidden() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
get editable() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
This is called when the an instance of the widget is removed
|
|||
|
|
from the editor view.
|
|||
|
|
*/
|
|||
|
|
destroy(dom) {
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var BlockType = function(BlockType2) {
|
|||
|
|
BlockType2[BlockType2["Text"] = 0] = "Text";
|
|||
|
|
BlockType2[BlockType2["WidgetBefore"] = 1] = "WidgetBefore";
|
|||
|
|
BlockType2[BlockType2["WidgetAfter"] = 2] = "WidgetAfter";
|
|||
|
|
BlockType2[BlockType2["WidgetRange"] = 3] = "WidgetRange";
|
|||
|
|
return BlockType2;
|
|||
|
|
}(BlockType || (BlockType = {}));
|
|||
|
|
var Decoration = class extends RangeValue {
|
|||
|
|
constructor(startSide, endSide, widget, spec) {
|
|||
|
|
super();
|
|||
|
|
this.startSide = startSide;
|
|||
|
|
this.endSide = endSide;
|
|||
|
|
this.widget = widget;
|
|||
|
|
this.spec = spec;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
get heightRelevant() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a mark decoration, which influences the styling of the
|
|||
|
|
content in its range. Nested mark decorations will cause nested
|
|||
|
|
DOM elements to be created. Nesting order is determined by
|
|||
|
|
precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
|
|||
|
|
the higher-precedence decorations creating the inner DOM nodes.
|
|||
|
|
Such elements are split on line boundaries and on the boundaries
|
|||
|
|
of lower-precedence decorations.
|
|||
|
|
*/
|
|||
|
|
static mark(spec) {
|
|||
|
|
return new MarkDecoration(spec);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a widget decoration, which displays a DOM element at the
|
|||
|
|
given position.
|
|||
|
|
*/
|
|||
|
|
static widget(spec) {
|
|||
|
|
let side = Math.max(-1e4, Math.min(1e4, spec.side || 0)), block = !!spec.block;
|
|||
|
|
side += block && !spec.inlineOrder ? side > 0 ? 3e8 : -4e8 : side > 0 ? 1e8 : -1e8;
|
|||
|
|
return new PointDecoration(spec, side, side, block, spec.widget || null, false);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a replace decoration which replaces the given range with
|
|||
|
|
a widget, or simply hides it.
|
|||
|
|
*/
|
|||
|
|
static replace(spec) {
|
|||
|
|
let block = !!spec.block, startSide, endSide;
|
|||
|
|
if (spec.isBlockGap) {
|
|||
|
|
startSide = -5e8;
|
|||
|
|
endSide = 4e8;
|
|||
|
|
} else {
|
|||
|
|
let { start, end } = getInclusive(spec, block);
|
|||
|
|
startSide = (start ? block ? -3e8 : -1 : 5e8) - 1;
|
|||
|
|
endSide = (end ? block ? 2e8 : 1 : -6e8) + 1;
|
|||
|
|
}
|
|||
|
|
return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a line decoration, which can add DOM attributes to the
|
|||
|
|
line starting at the given position.
|
|||
|
|
*/
|
|||
|
|
static line(spec) {
|
|||
|
|
return new LineDecoration(spec);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Build a [`DecorationSet`](https://codemirror.net/6/docs/ref/#view.DecorationSet) from the given
|
|||
|
|
decorated range or ranges. If the ranges aren't already sorted,
|
|||
|
|
pass `true` for `sort` to make the library sort them for you.
|
|||
|
|
*/
|
|||
|
|
static set(of, sort = false) {
|
|||
|
|
return RangeSet.of(of, sort);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
hasHeight() {
|
|||
|
|
return this.widget ? this.widget.estimatedHeight > -1 : false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
Decoration.none = RangeSet.empty;
|
|||
|
|
var MarkDecoration = class _MarkDecoration extends Decoration {
|
|||
|
|
constructor(spec) {
|
|||
|
|
let { start, end } = getInclusive(spec);
|
|||
|
|
super(start ? -1 : 5e8, end ? 1 : -6e8, null, spec);
|
|||
|
|
this.tagName = spec.tagName || "span";
|
|||
|
|
this.attrs = spec.class && spec.attributes ? combineAttrs(spec.attributes, { class: spec.class }) : spec.class ? { class: spec.class } : spec.attributes || noAttrs;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return this == other || other instanceof _MarkDecoration && this.tagName == other.tagName && attrsEq(this.attrs, other.attrs);
|
|||
|
|
}
|
|||
|
|
range(from, to = from) {
|
|||
|
|
if (from >= to)
|
|||
|
|
throw new RangeError("Mark decorations may not be empty");
|
|||
|
|
return super.range(from, to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
MarkDecoration.prototype.point = false;
|
|||
|
|
var LineDecoration = class _LineDecoration extends Decoration {
|
|||
|
|
constructor(spec) {
|
|||
|
|
super(-2e8, -2e8, null, spec);
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other instanceof _LineDecoration && this.spec.class == other.spec.class && attrsEq(this.spec.attributes, other.spec.attributes);
|
|||
|
|
}
|
|||
|
|
range(from, to = from) {
|
|||
|
|
if (to != from)
|
|||
|
|
throw new RangeError("Line decoration ranges must be zero-length");
|
|||
|
|
return super.range(from, to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
LineDecoration.prototype.mapMode = MapMode.TrackBefore;
|
|||
|
|
LineDecoration.prototype.point = true;
|
|||
|
|
var PointDecoration = class _PointDecoration extends Decoration {
|
|||
|
|
constructor(spec, startSide, endSide, block, widget, isReplace) {
|
|||
|
|
super(startSide, endSide, widget, spec);
|
|||
|
|
this.block = block;
|
|||
|
|
this.isReplace = isReplace;
|
|||
|
|
this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
|
|||
|
|
}
|
|||
|
|
// Only relevant when this.block == true
|
|||
|
|
get type() {
|
|||
|
|
return this.startSide != this.endSide ? BlockType.WidgetRange : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
|
|||
|
|
}
|
|||
|
|
get heightRelevant() {
|
|||
|
|
return this.block || !!this.widget && (this.widget.estimatedHeight >= 5 || this.widget.lineBreaks > 0);
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other instanceof _PointDecoration && widgetsEq(this.widget, other.widget) && this.block == other.block && this.startSide == other.startSide && this.endSide == other.endSide;
|
|||
|
|
}
|
|||
|
|
range(from, to = from) {
|
|||
|
|
if (this.isReplace && (from > to || from == to && this.startSide > 0 && this.endSide <= 0))
|
|||
|
|
throw new RangeError("Invalid range for replacement decoration");
|
|||
|
|
if (!this.isReplace && to != from)
|
|||
|
|
throw new RangeError("Widget decorations can only have zero-length ranges");
|
|||
|
|
return super.range(from, to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
PointDecoration.prototype.point = true;
|
|||
|
|
function getInclusive(spec, block = false) {
|
|||
|
|
let { inclusiveStart: start, inclusiveEnd: end } = spec;
|
|||
|
|
if (start == null)
|
|||
|
|
start = spec.inclusive;
|
|||
|
|
if (end == null)
|
|||
|
|
end = spec.inclusive;
|
|||
|
|
return { start: start !== null && start !== void 0 ? start : block, end: end !== null && end !== void 0 ? end : block };
|
|||
|
|
}
|
|||
|
|
function widgetsEq(a, b) {
|
|||
|
|
return a == b || !!(a && b && a.compare(b));
|
|||
|
|
}
|
|||
|
|
function addRange(from, to, ranges, margin = 0) {
|
|||
|
|
let last = ranges.length - 1;
|
|||
|
|
if (last >= 0 && ranges[last] + margin >= from)
|
|||
|
|
ranges[last] = Math.max(ranges[last], to);
|
|||
|
|
else
|
|||
|
|
ranges.push(from, to);
|
|||
|
|
}
|
|||
|
|
var BlockWrapper = class _BlockWrapper extends RangeValue {
|
|||
|
|
constructor(tagName, attributes, rank) {
|
|||
|
|
super();
|
|||
|
|
this.tagName = tagName;
|
|||
|
|
this.attributes = attributes;
|
|||
|
|
this.rank = rank;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other == this || other instanceof _BlockWrapper && this.tagName == other.tagName && attrsEq(this.attributes, other.attributes);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a block wrapper object with the given tag name and
|
|||
|
|
attributes.
|
|||
|
|
*/
|
|||
|
|
static create(spec) {
|
|||
|
|
return new _BlockWrapper(spec.tagName, spec.attributes || noAttrs, spec.rank == null ? 50 : Math.max(0, Math.min(spec.rank, 100)));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a range set from the given block wrapper ranges.
|
|||
|
|
*/
|
|||
|
|
static set(of, sort = false) {
|
|||
|
|
return RangeSet.of(of, sort);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
BlockWrapper.prototype.startSide = BlockWrapper.prototype.endSide = -1;
|
|||
|
|
function getSelection(root) {
|
|||
|
|
let target;
|
|||
|
|
if (root.nodeType == 11) {
|
|||
|
|
target = root.getSelection ? root : root.ownerDocument;
|
|||
|
|
} else {
|
|||
|
|
target = root;
|
|||
|
|
}
|
|||
|
|
return target.getSelection();
|
|||
|
|
}
|
|||
|
|
function contains(dom, node) {
|
|||
|
|
return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
|
|||
|
|
}
|
|||
|
|
function hasSelection(dom, selection) {
|
|||
|
|
if (!selection.anchorNode)
|
|||
|
|
return false;
|
|||
|
|
try {
|
|||
|
|
return contains(dom, selection.anchorNode);
|
|||
|
|
} catch (_) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function clientRectsFor(dom) {
|
|||
|
|
if (dom.nodeType == 3)
|
|||
|
|
return textRange(dom, 0, dom.nodeValue.length).getClientRects();
|
|||
|
|
else if (dom.nodeType == 1)
|
|||
|
|
return dom.getClientRects();
|
|||
|
|
else
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
function isEquivalentPosition(node, off, targetNode, targetOff) {
|
|||
|
|
return targetNode ? scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1) : false;
|
|||
|
|
}
|
|||
|
|
function domIndex(node) {
|
|||
|
|
for (var index = 0; ; index++) {
|
|||
|
|
node = node.previousSibling;
|
|||
|
|
if (!node)
|
|||
|
|
return index;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function isBlockElement(node) {
|
|||
|
|
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|||
|
|
}
|
|||
|
|
function scanFor(node, off, targetNode, targetOff, dir) {
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (node == targetNode && off == targetOff)
|
|||
|
|
return true;
|
|||
|
|
if (off == (dir < 0 ? 0 : maxOffset(node))) {
|
|||
|
|
if (node.nodeName == "DIV")
|
|||
|
|
return false;
|
|||
|
|
let parent = node.parentNode;
|
|||
|
|
if (!parent || parent.nodeType != 1)
|
|||
|
|
return false;
|
|||
|
|
off = domIndex(node) + (dir < 0 ? 0 : 1);
|
|||
|
|
node = parent;
|
|||
|
|
} else if (node.nodeType == 1) {
|
|||
|
|
node = node.childNodes[off + (dir < 0 ? -1 : 0)];
|
|||
|
|
if (node.nodeType == 1 && node.contentEditable == "false")
|
|||
|
|
return false;
|
|||
|
|
off = dir < 0 ? maxOffset(node) : 0;
|
|||
|
|
} else {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function maxOffset(node) {
|
|||
|
|
return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
|
|||
|
|
}
|
|||
|
|
function flattenRect(rect, left) {
|
|||
|
|
let x = left ? rect.left : rect.right;
|
|||
|
|
return { left: x, right: x, top: rect.top, bottom: rect.bottom };
|
|||
|
|
}
|
|||
|
|
function windowRect(win) {
|
|||
|
|
let vp = win.visualViewport;
|
|||
|
|
if (vp)
|
|||
|
|
return {
|
|||
|
|
left: 0,
|
|||
|
|
right: vp.width,
|
|||
|
|
top: 0,
|
|||
|
|
bottom: vp.height
|
|||
|
|
};
|
|||
|
|
return {
|
|||
|
|
left: 0,
|
|||
|
|
right: win.innerWidth,
|
|||
|
|
top: 0,
|
|||
|
|
bottom: win.innerHeight
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function getScale(elt, rect) {
|
|||
|
|
let scaleX = rect.width / elt.offsetWidth;
|
|||
|
|
let scaleY = rect.height / elt.offsetHeight;
|
|||
|
|
if (scaleX > 0.995 && scaleX < 1.005 || !isFinite(scaleX) || Math.abs(rect.width - elt.offsetWidth) < 1)
|
|||
|
|
scaleX = 1;
|
|||
|
|
if (scaleY > 0.995 && scaleY < 1.005 || !isFinite(scaleY) || Math.abs(rect.height - elt.offsetHeight) < 1)
|
|||
|
|
scaleY = 1;
|
|||
|
|
return { scaleX, scaleY };
|
|||
|
|
}
|
|||
|
|
function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
|
|||
|
|
let doc2 = dom.ownerDocument, win = doc2.defaultView || window;
|
|||
|
|
for (let cur = dom, stop = false; cur && !stop; ) {
|
|||
|
|
if (cur.nodeType == 1) {
|
|||
|
|
let bounding, top2 = cur == doc2.body;
|
|||
|
|
let scaleX = 1, scaleY = 1;
|
|||
|
|
if (top2) {
|
|||
|
|
bounding = windowRect(win);
|
|||
|
|
} else {
|
|||
|
|
if (/^(fixed|sticky)$/.test(getComputedStyle(cur).position))
|
|||
|
|
stop = true;
|
|||
|
|
if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
|
|||
|
|
cur = cur.assignedSlot || cur.parentNode;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
let rect2 = cur.getBoundingClientRect();
|
|||
|
|
({ scaleX, scaleY } = getScale(cur, rect2));
|
|||
|
|
bounding = {
|
|||
|
|
left: rect2.left,
|
|||
|
|
right: rect2.left + cur.clientWidth * scaleX,
|
|||
|
|
top: rect2.top,
|
|||
|
|
bottom: rect2.top + cur.clientHeight * scaleY
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
let moveX = 0, moveY = 0;
|
|||
|
|
if (y == "nearest") {
|
|||
|
|
if (rect.top < bounding.top + yMargin) {
|
|||
|
|
moveY = rect.top - (bounding.top + yMargin);
|
|||
|
|
if (side > 0 && rect.bottom > bounding.bottom + moveY)
|
|||
|
|
moveY = rect.bottom - bounding.bottom + yMargin;
|
|||
|
|
} else if (rect.bottom > bounding.bottom - yMargin) {
|
|||
|
|
moveY = rect.bottom - bounding.bottom + yMargin;
|
|||
|
|
if (side < 0 && rect.top - moveY < bounding.top)
|
|||
|
|
moveY = rect.top - (bounding.top + yMargin);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
|
|||
|
|
let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 : y == "start" || y == "center" && side < 0 ? rect.top - yMargin : rect.bottom - boundingHeight + yMargin;
|
|||
|
|
moveY = targetTop - bounding.top;
|
|||
|
|
}
|
|||
|
|
if (x == "nearest") {
|
|||
|
|
if (rect.left < bounding.left + xMargin) {
|
|||
|
|
moveX = rect.left - (bounding.left + xMargin);
|
|||
|
|
if (side > 0 && rect.right > bounding.right + moveX)
|
|||
|
|
moveX = rect.right - bounding.right + xMargin;
|
|||
|
|
} else if (rect.right > bounding.right - xMargin) {
|
|||
|
|
moveX = rect.right - bounding.right + xMargin;
|
|||
|
|
if (side < 0 && rect.left < bounding.left + moveX)
|
|||
|
|
moveX = rect.left - (bounding.left + xMargin);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 : x == "start" == ltr ? rect.left - xMargin : rect.right - (bounding.right - bounding.left) + xMargin;
|
|||
|
|
moveX = targetLeft - bounding.left;
|
|||
|
|
}
|
|||
|
|
if (moveX || moveY) {
|
|||
|
|
if (top2) {
|
|||
|
|
win.scrollBy(moveX, moveY);
|
|||
|
|
} else {
|
|||
|
|
let movedX = 0, movedY = 0;
|
|||
|
|
if (moveY) {
|
|||
|
|
let start = cur.scrollTop;
|
|||
|
|
cur.scrollTop += moveY / scaleY;
|
|||
|
|
movedY = (cur.scrollTop - start) * scaleY;
|
|||
|
|
}
|
|||
|
|
if (moveX) {
|
|||
|
|
let start = cur.scrollLeft;
|
|||
|
|
cur.scrollLeft += moveX / scaleX;
|
|||
|
|
movedX = (cur.scrollLeft - start) * scaleX;
|
|||
|
|
}
|
|||
|
|
rect = {
|
|||
|
|
left: rect.left - movedX,
|
|||
|
|
top: rect.top - movedY,
|
|||
|
|
right: rect.right - movedX,
|
|||
|
|
bottom: rect.bottom - movedY
|
|||
|
|
};
|
|||
|
|
if (movedX && Math.abs(movedX - moveX) < 1)
|
|||
|
|
x = "nearest";
|
|||
|
|
if (movedY && Math.abs(movedY - moveY) < 1)
|
|||
|
|
y = "nearest";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (top2)
|
|||
|
|
break;
|
|||
|
|
if (rect.top < bounding.top || rect.bottom > bounding.bottom || rect.left < bounding.left || rect.right > bounding.right)
|
|||
|
|
rect = {
|
|||
|
|
left: Math.max(rect.left, bounding.left),
|
|||
|
|
right: Math.min(rect.right, bounding.right),
|
|||
|
|
top: Math.max(rect.top, bounding.top),
|
|||
|
|
bottom: Math.min(rect.bottom, bounding.bottom)
|
|||
|
|
};
|
|||
|
|
cur = cur.assignedSlot || cur.parentNode;
|
|||
|
|
} else if (cur.nodeType == 11) {
|
|||
|
|
cur = cur.host;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function scrollableParents(dom, getX = true) {
|
|||
|
|
let doc2 = dom.ownerDocument, x = null, y = null;
|
|||
|
|
for (let cur = dom.parentNode; cur; ) {
|
|||
|
|
if (cur == doc2.body || (!getX || x) && y) {
|
|||
|
|
break;
|
|||
|
|
} else if (cur.nodeType == 1) {
|
|||
|
|
if (!y && cur.scrollHeight > cur.clientHeight)
|
|||
|
|
y = cur;
|
|||
|
|
if (getX && !x && cur.scrollWidth > cur.clientWidth)
|
|||
|
|
x = cur;
|
|||
|
|
cur = cur.assignedSlot || cur.parentNode;
|
|||
|
|
} else if (cur.nodeType == 11) {
|
|||
|
|
cur = cur.host;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { x, y };
|
|||
|
|
}
|
|||
|
|
var DOMSelectionState = class {
|
|||
|
|
constructor() {
|
|||
|
|
this.anchorNode = null;
|
|||
|
|
this.anchorOffset = 0;
|
|||
|
|
this.focusNode = null;
|
|||
|
|
this.focusOffset = 0;
|
|||
|
|
}
|
|||
|
|
eq(domSel) {
|
|||
|
|
return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset && this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
|
|||
|
|
}
|
|||
|
|
setRange(range) {
|
|||
|
|
let { anchorNode, focusNode } = range;
|
|||
|
|
this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
|
|||
|
|
}
|
|||
|
|
set(anchorNode, anchorOffset, focusNode, focusOffset) {
|
|||
|
|
this.anchorNode = anchorNode;
|
|||
|
|
this.anchorOffset = anchorOffset;
|
|||
|
|
this.focusNode = focusNode;
|
|||
|
|
this.focusOffset = focusOffset;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var preventScrollSupported = null;
|
|||
|
|
if (browser.safari && browser.safari_version >= 26)
|
|||
|
|
preventScrollSupported = false;
|
|||
|
|
function focusPreventScroll(dom) {
|
|||
|
|
if (dom.setActive)
|
|||
|
|
return dom.setActive();
|
|||
|
|
if (preventScrollSupported)
|
|||
|
|
return dom.focus(preventScrollSupported);
|
|||
|
|
let stack = [];
|
|||
|
|
for (let cur = dom; cur; cur = cur.parentNode) {
|
|||
|
|
stack.push(cur, cur.scrollTop, cur.scrollLeft);
|
|||
|
|
if (cur == cur.ownerDocument)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
dom.focus(preventScrollSupported == null ? {
|
|||
|
|
get preventScroll() {
|
|||
|
|
preventScrollSupported = { preventScroll: true };
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
} : void 0);
|
|||
|
|
if (!preventScrollSupported) {
|
|||
|
|
preventScrollSupported = false;
|
|||
|
|
for (let i = 0; i < stack.length; ) {
|
|||
|
|
let elt = stack[i++], top2 = stack[i++], left = stack[i++];
|
|||
|
|
if (elt.scrollTop != top2)
|
|||
|
|
elt.scrollTop = top2;
|
|||
|
|
if (elt.scrollLeft != left)
|
|||
|
|
elt.scrollLeft = left;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var scratchRange;
|
|||
|
|
function textRange(node, from, to = from) {
|
|||
|
|
let range = scratchRange || (scratchRange = document.createRange());
|
|||
|
|
range.setEnd(node, to);
|
|||
|
|
range.setStart(node, from);
|
|||
|
|
return range;
|
|||
|
|
}
|
|||
|
|
function dispatchKey(elt, name, code, mods) {
|
|||
|
|
let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
|
|||
|
|
if (mods)
|
|||
|
|
({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
|
|||
|
|
let down = new KeyboardEvent("keydown", options);
|
|||
|
|
down.synthetic = true;
|
|||
|
|
elt.dispatchEvent(down);
|
|||
|
|
let up = new KeyboardEvent("keyup", options);
|
|||
|
|
up.synthetic = true;
|
|||
|
|
elt.dispatchEvent(up);
|
|||
|
|
return down.defaultPrevented || up.defaultPrevented;
|
|||
|
|
}
|
|||
|
|
function getRoot(node) {
|
|||
|
|
while (node) {
|
|||
|
|
if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
|
|||
|
|
return node;
|
|||
|
|
node = node.assignedSlot || node.parentNode;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
function atElementStart(doc2, selection) {
|
|||
|
|
let node = selection.focusNode, offset = selection.focusOffset;
|
|||
|
|
if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
|
|||
|
|
return false;
|
|||
|
|
offset = Math.min(offset, maxOffset(node));
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (offset) {
|
|||
|
|
if (node.nodeType != 1)
|
|||
|
|
return false;
|
|||
|
|
let prev = node.childNodes[offset - 1];
|
|||
|
|
if (prev.contentEditable == "false")
|
|||
|
|
offset--;
|
|||
|
|
else {
|
|||
|
|
node = prev;
|
|||
|
|
offset = maxOffset(node);
|
|||
|
|
}
|
|||
|
|
} else if (node == doc2) {
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
offset = domIndex(node);
|
|||
|
|
node = node.parentNode;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function isScrolledToBottom(elt) {
|
|||
|
|
if (elt instanceof Window)
|
|||
|
|
return elt.pageYOffset > Math.max(0, elt.document.documentElement.scrollHeight - elt.innerHeight - 4);
|
|||
|
|
return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
|
|||
|
|
}
|
|||
|
|
function textNodeBefore(startNode, startOffset) {
|
|||
|
|
for (let node = startNode, offset = startOffset; ; ) {
|
|||
|
|
if (node.nodeType == 3 && offset > 0) {
|
|||
|
|
return { node, offset };
|
|||
|
|
} else if (node.nodeType == 1 && offset > 0) {
|
|||
|
|
if (node.contentEditable == "false")
|
|||
|
|
return null;
|
|||
|
|
node = node.childNodes[offset - 1];
|
|||
|
|
offset = maxOffset(node);
|
|||
|
|
} else if (node.parentNode && !isBlockElement(node)) {
|
|||
|
|
offset = domIndex(node);
|
|||
|
|
node = node.parentNode;
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function textNodeAfter(startNode, startOffset) {
|
|||
|
|
for (let node = startNode, offset = startOffset; ; ) {
|
|||
|
|
if (node.nodeType == 3 && offset < node.nodeValue.length) {
|
|||
|
|
return { node, offset };
|
|||
|
|
} else if (node.nodeType == 1 && offset < node.childNodes.length) {
|
|||
|
|
if (node.contentEditable == "false")
|
|||
|
|
return null;
|
|||
|
|
node = node.childNodes[offset];
|
|||
|
|
offset = 0;
|
|||
|
|
} else if (node.parentNode && !isBlockElement(node)) {
|
|||
|
|
offset = domIndex(node) + 1;
|
|||
|
|
node = node.parentNode;
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var DOMPos = class _DOMPos {
|
|||
|
|
constructor(node, offset, precise = true) {
|
|||
|
|
this.node = node;
|
|||
|
|
this.offset = offset;
|
|||
|
|
this.precise = precise;
|
|||
|
|
}
|
|||
|
|
static before(dom, precise) {
|
|||
|
|
return new _DOMPos(dom.parentNode, domIndex(dom), precise);
|
|||
|
|
}
|
|||
|
|
static after(dom, precise) {
|
|||
|
|
return new _DOMPos(dom.parentNode, domIndex(dom) + 1, precise);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var Direction = function(Direction2) {
|
|||
|
|
Direction2[Direction2["LTR"] = 0] = "LTR";
|
|||
|
|
Direction2[Direction2["RTL"] = 1] = "RTL";
|
|||
|
|
return Direction2;
|
|||
|
|
}(Direction || (Direction = {}));
|
|||
|
|
var LTR = Direction.LTR;
|
|||
|
|
var RTL = Direction.RTL;
|
|||
|
|
function dec(str) {
|
|||
|
|
let result = [];
|
|||
|
|
for (let i = 0; i < str.length; i++)
|
|||
|
|
result.push(1 << +str[i]);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
var LowTypes = dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
|
|||
|
|
var ArabicTypes = dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
|
|||
|
|
var Brackets = /* @__PURE__ */ Object.create(null);
|
|||
|
|
var BracketStack = [];
|
|||
|
|
for (let p of ["()", "[]", "{}"]) {
|
|||
|
|
let l = p.charCodeAt(0), r = p.charCodeAt(1);
|
|||
|
|
Brackets[l] = r;
|
|||
|
|
Brackets[r] = -l;
|
|||
|
|
}
|
|||
|
|
function charType(ch) {
|
|||
|
|
return ch <= 247 ? LowTypes[ch] : 1424 <= ch && ch <= 1524 ? 2 : 1536 <= ch && ch <= 1785 ? ArabicTypes[ch - 1536] : 1774 <= ch && ch <= 2220 ? 4 : 8192 <= ch && ch <= 8204 ? 256 : 64336 <= ch && ch <= 65023 ? 4 : 1;
|
|||
|
|
}
|
|||
|
|
var BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/;
|
|||
|
|
var BidiSpan = class {
|
|||
|
|
/**
|
|||
|
|
The direction of this span.
|
|||
|
|
*/
|
|||
|
|
get dir() {
|
|||
|
|
return this.level % 2 ? RTL : LTR;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
constructor(from, to, level) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
this.level = level;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
side(end, dir) {
|
|||
|
|
return this.dir == dir == end ? this.to : this.from;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
forward(forward, dir) {
|
|||
|
|
return forward == (this.dir == dir);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
static find(order, index, level, assoc) {
|
|||
|
|
let maybe = -1;
|
|||
|
|
for (let i = 0; i < order.length; i++) {
|
|||
|
|
let span = order[i];
|
|||
|
|
if (span.from <= index && span.to >= index) {
|
|||
|
|
if (span.level == level)
|
|||
|
|
return i;
|
|||
|
|
if (maybe < 0 || (assoc != 0 ? assoc < 0 ? span.from < index : span.to > index : order[maybe].level > span.level))
|
|||
|
|
maybe = i;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (maybe < 0)
|
|||
|
|
throw new RangeError("Index out of range");
|
|||
|
|
return maybe;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function isolatesEq(a, b) {
|
|||
|
|
if (a.length != b.length)
|
|||
|
|
return false;
|
|||
|
|
for (let i = 0; i < a.length; i++) {
|
|||
|
|
let iA = a[i], iB = b[i];
|
|||
|
|
if (iA.from != iB.from || iA.to != iB.to || iA.direction != iB.direction || !isolatesEq(iA.inner, iB.inner))
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
var types = [];
|
|||
|
|
function computeCharTypes(line, rFrom, rTo, isolates, outerType) {
|
|||
|
|
for (let iI = 0; iI <= isolates.length; iI++) {
|
|||
|
|
let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
|
|||
|
|
let prevType = iI ? 256 : outerType;
|
|||
|
|
for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
|
|||
|
|
let type = charType(line.charCodeAt(i));
|
|||
|
|
if (type == 512)
|
|||
|
|
type = prev;
|
|||
|
|
else if (type == 8 && prevStrong == 4)
|
|||
|
|
type = 16;
|
|||
|
|
types[i] = type == 4 ? 2 : type;
|
|||
|
|
if (type & 7)
|
|||
|
|
prevStrong = type;
|
|||
|
|
prev = type;
|
|||
|
|
}
|
|||
|
|
for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
|
|||
|
|
let type = types[i];
|
|||
|
|
if (type == 128) {
|
|||
|
|
if (i < to - 1 && prev == types[i + 1] && prev & 24)
|
|||
|
|
type = types[i] = prev;
|
|||
|
|
else
|
|||
|
|
types[i] = 256;
|
|||
|
|
} else if (type == 64) {
|
|||
|
|
let end = i + 1;
|
|||
|
|
while (end < to && types[end] == 64)
|
|||
|
|
end++;
|
|||
|
|
let replace2 = i && prev == 8 || end < rTo && types[end] == 8 ? prevStrong == 1 ? 1 : 8 : 256;
|
|||
|
|
for (let j = i; j < end; j++)
|
|||
|
|
types[j] = replace2;
|
|||
|
|
i = end - 1;
|
|||
|
|
} else if (type == 8 && prevStrong == 1) {
|
|||
|
|
types[i] = 1;
|
|||
|
|
}
|
|||
|
|
prev = type;
|
|||
|
|
if (type & 7)
|
|||
|
|
prevStrong = type;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function processBracketPairs(line, rFrom, rTo, isolates, outerType) {
|
|||
|
|
let oppositeType = outerType == 1 ? 2 : 1;
|
|||
|
|
for (let iI = 0, sI = 0, context = 0; iI <= isolates.length; iI++) {
|
|||
|
|
let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
|
|||
|
|
for (let i = from, ch, br, type; i < to; i++) {
|
|||
|
|
if (br = Brackets[ch = line.charCodeAt(i)]) {
|
|||
|
|
if (br < 0) {
|
|||
|
|
for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
|
|||
|
|
if (BracketStack[sJ + 1] == -br) {
|
|||
|
|
let flags = BracketStack[sJ + 2];
|
|||
|
|
let type2 = flags & 2 ? outerType : !(flags & 4) ? 0 : flags & 1 ? oppositeType : outerType;
|
|||
|
|
if (type2)
|
|||
|
|
types[i] = types[BracketStack[sJ]] = type2;
|
|||
|
|
sI = sJ;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (BracketStack.length == 189) {
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
BracketStack[sI++] = i;
|
|||
|
|
BracketStack[sI++] = ch;
|
|||
|
|
BracketStack[sI++] = context;
|
|||
|
|
}
|
|||
|
|
} else if ((type = types[i]) == 2 || type == 1) {
|
|||
|
|
let embed = type == outerType;
|
|||
|
|
context = embed ? 0 : 1;
|
|||
|
|
for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
|
|||
|
|
let cur = BracketStack[sJ + 2];
|
|||
|
|
if (cur & 2)
|
|||
|
|
break;
|
|||
|
|
if (embed) {
|
|||
|
|
BracketStack[sJ + 2] |= 2;
|
|||
|
|
} else {
|
|||
|
|
if (cur & 4)
|
|||
|
|
break;
|
|||
|
|
BracketStack[sJ + 2] |= 4;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function processNeutrals(rFrom, rTo, isolates, outerType) {
|
|||
|
|
for (let iI = 0, prev = outerType; iI <= isolates.length; iI++) {
|
|||
|
|
let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
|
|||
|
|
for (let i = from; i < to; ) {
|
|||
|
|
let type = types[i];
|
|||
|
|
if (type == 256) {
|
|||
|
|
let end = i + 1;
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (end == to) {
|
|||
|
|
if (iI == isolates.length)
|
|||
|
|
break;
|
|||
|
|
end = isolates[iI++].to;
|
|||
|
|
to = iI < isolates.length ? isolates[iI].from : rTo;
|
|||
|
|
} else if (types[end] == 256) {
|
|||
|
|
end++;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let beforeL = prev == 1;
|
|||
|
|
let afterL = (end < rTo ? types[end] : outerType) == 1;
|
|||
|
|
let replace2 = beforeL == afterL ? beforeL ? 1 : 2 : outerType;
|
|||
|
|
for (let j = end, jI = iI, fromJ = jI ? isolates[jI - 1].to : rFrom; j > i; ) {
|
|||
|
|
if (j == fromJ) {
|
|||
|
|
j = isolates[--jI].from;
|
|||
|
|
fromJ = jI ? isolates[jI - 1].to : rFrom;
|
|||
|
|
}
|
|||
|
|
types[--j] = replace2;
|
|||
|
|
}
|
|||
|
|
i = end;
|
|||
|
|
} else {
|
|||
|
|
prev = type;
|
|||
|
|
i++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function emitSpans(line, from, to, level, baseLevel, isolates, order) {
|
|||
|
|
let ourType = level % 2 ? 2 : 1;
|
|||
|
|
if (level % 2 == baseLevel % 2) {
|
|||
|
|
for (let iCh = from, iI = 0; iCh < to; ) {
|
|||
|
|
let sameDir = true, isNum = false;
|
|||
|
|
if (iI == isolates.length || iCh < isolates[iI].from) {
|
|||
|
|
let next = types[iCh];
|
|||
|
|
if (next != ourType) {
|
|||
|
|
sameDir = false;
|
|||
|
|
isNum = next == 16;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let recurse = !sameDir && ourType == 1 ? [] : null;
|
|||
|
|
let localLevel = sameDir ? level : level + 1;
|
|||
|
|
let iScan = iCh;
|
|||
|
|
run: for (; ; ) {
|
|||
|
|
if (iI < isolates.length && iScan == isolates[iI].from) {
|
|||
|
|
if (isNum)
|
|||
|
|
break run;
|
|||
|
|
let iso = isolates[iI];
|
|||
|
|
if (!sameDir)
|
|||
|
|
for (let upto = iso.to, jI = iI + 1; ; ) {
|
|||
|
|
if (upto == to)
|
|||
|
|
break run;
|
|||
|
|
if (jI < isolates.length && isolates[jI].from == upto)
|
|||
|
|
upto = isolates[jI++].to;
|
|||
|
|
else if (types[upto] == ourType)
|
|||
|
|
break run;
|
|||
|
|
else
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
iI++;
|
|||
|
|
if (recurse) {
|
|||
|
|
recurse.push(iso);
|
|||
|
|
} else {
|
|||
|
|
if (iso.from > iCh)
|
|||
|
|
order.push(new BidiSpan(iCh, iso.from, localLevel));
|
|||
|
|
let dirSwap = iso.direction == LTR != !(localLevel % 2);
|
|||
|
|
computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
|
|||
|
|
iCh = iso.to;
|
|||
|
|
}
|
|||
|
|
iScan = iso.to;
|
|||
|
|
} else if (iScan == to || (sameDir ? types[iScan] != ourType : types[iScan] == ourType)) {
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
iScan++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (recurse)
|
|||
|
|
emitSpans(line, iCh, iScan, level + 1, baseLevel, recurse, order);
|
|||
|
|
else if (iCh < iScan)
|
|||
|
|
order.push(new BidiSpan(iCh, iScan, localLevel));
|
|||
|
|
iCh = iScan;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
for (let iCh = to, iI = isolates.length; iCh > from; ) {
|
|||
|
|
let sameDir = true, isNum = false;
|
|||
|
|
if (!iI || iCh > isolates[iI - 1].to) {
|
|||
|
|
let next = types[iCh - 1];
|
|||
|
|
if (next != ourType) {
|
|||
|
|
sameDir = false;
|
|||
|
|
isNum = next == 16;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let recurse = !sameDir && ourType == 1 ? [] : null;
|
|||
|
|
let localLevel = sameDir ? level : level + 1;
|
|||
|
|
let iScan = iCh;
|
|||
|
|
run: for (; ; ) {
|
|||
|
|
if (iI && iScan == isolates[iI - 1].to) {
|
|||
|
|
if (isNum)
|
|||
|
|
break run;
|
|||
|
|
let iso = isolates[--iI];
|
|||
|
|
if (!sameDir)
|
|||
|
|
for (let upto = iso.from, jI = iI; ; ) {
|
|||
|
|
if (upto == from)
|
|||
|
|
break run;
|
|||
|
|
if (jI && isolates[jI - 1].to == upto)
|
|||
|
|
upto = isolates[--jI].from;
|
|||
|
|
else if (types[upto - 1] == ourType)
|
|||
|
|
break run;
|
|||
|
|
else
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
if (recurse) {
|
|||
|
|
recurse.push(iso);
|
|||
|
|
} else {
|
|||
|
|
if (iso.to < iCh)
|
|||
|
|
order.push(new BidiSpan(iso.to, iCh, localLevel));
|
|||
|
|
let dirSwap = iso.direction == LTR != !(localLevel % 2);
|
|||
|
|
computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
|
|||
|
|
iCh = iso.from;
|
|||
|
|
}
|
|||
|
|
iScan = iso.from;
|
|||
|
|
} else if (iScan == from || (sameDir ? types[iScan - 1] != ourType : types[iScan - 1] == ourType)) {
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
iScan--;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (recurse)
|
|||
|
|
emitSpans(line, iScan, iCh, level + 1, baseLevel, recurse, order);
|
|||
|
|
else if (iScan < iCh)
|
|||
|
|
order.push(new BidiSpan(iScan, iCh, localLevel));
|
|||
|
|
iCh = iScan;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function computeSectionOrder(line, level, baseLevel, isolates, from, to, order) {
|
|||
|
|
let outerType = level % 2 ? 2 : 1;
|
|||
|
|
computeCharTypes(line, from, to, isolates, outerType);
|
|||
|
|
processBracketPairs(line, from, to, isolates, outerType);
|
|||
|
|
processNeutrals(from, to, isolates, outerType);
|
|||
|
|
emitSpans(line, from, to, level, baseLevel, isolates, order);
|
|||
|
|
}
|
|||
|
|
function computeOrder(line, direction, isolates) {
|
|||
|
|
if (!line)
|
|||
|
|
return [new BidiSpan(0, 0, direction == RTL ? 1 : 0)];
|
|||
|
|
if (direction == LTR && !isolates.length && !BidiRE.test(line))
|
|||
|
|
return trivialOrder(line.length);
|
|||
|
|
if (isolates.length)
|
|||
|
|
while (line.length > types.length)
|
|||
|
|
types[types.length] = 256;
|
|||
|
|
let order = [], level = direction == LTR ? 0 : 1;
|
|||
|
|
computeSectionOrder(line, level, level, isolates, 0, line.length, order);
|
|||
|
|
return order;
|
|||
|
|
}
|
|||
|
|
function trivialOrder(length) {
|
|||
|
|
return [new BidiSpan(0, length, 0)];
|
|||
|
|
}
|
|||
|
|
var movedOver = "";
|
|||
|
|
function moveVisually(line, order, dir, start, forward) {
|
|||
|
|
var _a;
|
|||
|
|
let startIndex = start.head - line.from;
|
|||
|
|
let spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
|
|||
|
|
let span = order[spanI], spanEnd = span.side(forward, dir);
|
|||
|
|
if (startIndex == spanEnd) {
|
|||
|
|
let nextI = spanI += forward ? 1 : -1;
|
|||
|
|
if (nextI < 0 || nextI >= order.length)
|
|||
|
|
return null;
|
|||
|
|
span = order[spanI = nextI];
|
|||
|
|
startIndex = span.side(!forward, dir);
|
|||
|
|
spanEnd = span.side(forward, dir);
|
|||
|
|
}
|
|||
|
|
let nextIndex = findClusterBreak(line.text, startIndex, span.forward(forward, dir));
|
|||
|
|
if (nextIndex < span.from || nextIndex > span.to)
|
|||
|
|
nextIndex = spanEnd;
|
|||
|
|
movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
|
|||
|
|
let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
|
|||
|
|
if (nextSpan && nextIndex == spanEnd && nextSpan.level + (forward ? 0 : 1) < span.level)
|
|||
|
|
return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, nextSpan.forward(forward, dir) ? 1 : -1, nextSpan.level);
|
|||
|
|
return EditorSelection.cursor(nextIndex + line.from, span.forward(forward, dir) ? -1 : 1, span.level);
|
|||
|
|
}
|
|||
|
|
function autoDirection(text, from, to) {
|
|||
|
|
for (let i = from; i < to; i++) {
|
|||
|
|
let type = charType(text.charCodeAt(i));
|
|||
|
|
if (type == 1)
|
|||
|
|
return LTR;
|
|||
|
|
if (type == 2 || type == 4)
|
|||
|
|
return RTL;
|
|||
|
|
}
|
|||
|
|
return LTR;
|
|||
|
|
}
|
|||
|
|
var clickAddsSelectionRange = Facet.define();
|
|||
|
|
var dragMovesSelection$1 = Facet.define();
|
|||
|
|
var mouseSelectionStyle = Facet.define();
|
|||
|
|
var exceptionSink = Facet.define();
|
|||
|
|
var updateListener = Facet.define();
|
|||
|
|
var inputHandler = Facet.define();
|
|||
|
|
var focusChangeEffect = Facet.define();
|
|||
|
|
var clipboardInputFilter = Facet.define();
|
|||
|
|
var clipboardOutputFilter = Facet.define();
|
|||
|
|
var perLineTextDirection = Facet.define({
|
|||
|
|
combine: (values) => values.some((x) => x)
|
|||
|
|
});
|
|||
|
|
var nativeSelectionHidden = Facet.define({
|
|||
|
|
combine: (values) => values.some((x) => x)
|
|||
|
|
});
|
|||
|
|
var scrollHandler = Facet.define();
|
|||
|
|
var ScrollTarget = class _ScrollTarget {
|
|||
|
|
constructor(range, y, x, yMargin, xMargin, isSnapshot = false) {
|
|||
|
|
this.range = range;
|
|||
|
|
this.y = y;
|
|||
|
|
this.x = x;
|
|||
|
|
this.yMargin = yMargin;
|
|||
|
|
this.xMargin = xMargin;
|
|||
|
|
this.isSnapshot = isSnapshot;
|
|||
|
|
}
|
|||
|
|
map(changes) {
|
|||
|
|
return changes.empty ? this : new _ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
|
|||
|
|
}
|
|||
|
|
clip(state) {
|
|||
|
|
return this.range.to <= state.doc.length ? this : new _ScrollTarget(EditorSelection.cursor(state.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var scrollIntoView = StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|||
|
|
var setEditContextFormatting = StateEffect.define();
|
|||
|
|
function logException(state, exception, context) {
|
|||
|
|
let handler = state.facet(exceptionSink);
|
|||
|
|
if (handler.length)
|
|||
|
|
handler[0](exception);
|
|||
|
|
else if (window.onerror && window.onerror(String(exception), context, void 0, void 0, exception)) ;
|
|||
|
|
else if (context)
|
|||
|
|
console.error(context + ":", exception);
|
|||
|
|
else
|
|||
|
|
console.error(exception);
|
|||
|
|
}
|
|||
|
|
var editable = Facet.define({ combine: (values) => values.length ? values[0] : true });
|
|||
|
|
var nextPluginID = 0;
|
|||
|
|
var viewPlugin = Facet.define({
|
|||
|
|
combine(plugins) {
|
|||
|
|
return plugins.filter((p, i) => {
|
|||
|
|
for (let j = 0; j < i; j++)
|
|||
|
|
if (plugins[j].plugin == p.plugin)
|
|||
|
|
return false;
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var ViewPlugin = class _ViewPlugin {
|
|||
|
|
constructor(id, create, domEventHandlers, domEventObservers, buildExtensions) {
|
|||
|
|
this.id = id;
|
|||
|
|
this.create = create;
|
|||
|
|
this.domEventHandlers = domEventHandlers;
|
|||
|
|
this.domEventObservers = domEventObservers;
|
|||
|
|
this.baseExtensions = buildExtensions(this);
|
|||
|
|
this.extension = this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg: void 0 }));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create an extension for this plugin with the given argument.
|
|||
|
|
*/
|
|||
|
|
of(arg) {
|
|||
|
|
return this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg }));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Define a plugin from a constructor function that creates the
|
|||
|
|
plugin's value, given an editor view.
|
|||
|
|
*/
|
|||
|
|
static define(create, spec) {
|
|||
|
|
const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
|
|||
|
|
return new _ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, (plugin2) => {
|
|||
|
|
let ext = [];
|
|||
|
|
if (deco)
|
|||
|
|
ext.push(decorations.of((view) => {
|
|||
|
|
let pluginInst = view.plugin(plugin2);
|
|||
|
|
return pluginInst ? deco(pluginInst) : Decoration.none;
|
|||
|
|
}));
|
|||
|
|
if (provide)
|
|||
|
|
ext.push(provide(plugin2));
|
|||
|
|
return ext;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a plugin for a class whose constructor takes a single
|
|||
|
|
editor view as argument.
|
|||
|
|
*/
|
|||
|
|
static fromClass(cls, spec) {
|
|||
|
|
return _ViewPlugin.define((view, arg) => new cls(view, arg), spec);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var PluginInstance = class {
|
|||
|
|
constructor(spec) {
|
|||
|
|
this.spec = spec;
|
|||
|
|
this.mustUpdate = null;
|
|||
|
|
this.value = null;
|
|||
|
|
}
|
|||
|
|
get plugin() {
|
|||
|
|
return this.spec && this.spec.plugin;
|
|||
|
|
}
|
|||
|
|
update(view) {
|
|||
|
|
if (!this.value) {
|
|||
|
|
if (this.spec) {
|
|||
|
|
try {
|
|||
|
|
this.value = this.spec.plugin.create(view, this.spec.arg);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(view.state, e, "CodeMirror plugin crashed");
|
|||
|
|
this.deactivate();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (this.mustUpdate) {
|
|||
|
|
let update = this.mustUpdate;
|
|||
|
|
this.mustUpdate = null;
|
|||
|
|
if (this.value.update) {
|
|||
|
|
try {
|
|||
|
|
this.value.update(update);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(update.state, e, "CodeMirror plugin crashed");
|
|||
|
|
if (this.value.destroy)
|
|||
|
|
try {
|
|||
|
|
this.value.destroy();
|
|||
|
|
} catch (_) {
|
|||
|
|
}
|
|||
|
|
this.deactivate();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
destroy(view) {
|
|||
|
|
var _a;
|
|||
|
|
if ((_a = this.value) === null || _a === void 0 ? void 0 : _a.destroy) {
|
|||
|
|
try {
|
|||
|
|
this.value.destroy();
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(view.state, e, "CodeMirror plugin crashed");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
deactivate() {
|
|||
|
|
this.spec = this.value = null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var editorAttributes = Facet.define();
|
|||
|
|
var contentAttributes = Facet.define();
|
|||
|
|
var decorations = Facet.define();
|
|||
|
|
var blockWrappers = Facet.define();
|
|||
|
|
var outerDecorations = Facet.define();
|
|||
|
|
var atomicRanges = Facet.define();
|
|||
|
|
var bidiIsolatedRanges = Facet.define();
|
|||
|
|
function getIsolatedRanges(view, line) {
|
|||
|
|
let isolates = view.state.facet(bidiIsolatedRanges);
|
|||
|
|
if (!isolates.length)
|
|||
|
|
return isolates;
|
|||
|
|
let sets = isolates.map((i) => i instanceof Function ? i(view) : i);
|
|||
|
|
let result = [];
|
|||
|
|
RangeSet.spans(sets, line.from, line.to, {
|
|||
|
|
point() {
|
|||
|
|
},
|
|||
|
|
span(fromDoc, toDoc, active, open) {
|
|||
|
|
let from = fromDoc - line.from, to = toDoc - line.from;
|
|||
|
|
let level = result;
|
|||
|
|
for (let i = active.length - 1; i >= 0; i--, open--) {
|
|||
|
|
let direction = active[i].spec.bidiIsolate, update;
|
|||
|
|
if (direction == null)
|
|||
|
|
direction = autoDirection(line.text, from, to);
|
|||
|
|
if (open > 0 && level.length && (update = level[level.length - 1]).to == from && update.direction == direction) {
|
|||
|
|
update.to = to;
|
|||
|
|
level = update.inner;
|
|||
|
|
} else {
|
|||
|
|
let add2 = { from, to, direction, inner: [] };
|
|||
|
|
level.push(add2);
|
|||
|
|
level = add2.inner;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
var scrollMargins = Facet.define();
|
|||
|
|
function getScrollMargins(view) {
|
|||
|
|
let left = 0, right = 0, top2 = 0, bottom = 0;
|
|||
|
|
for (let source of view.state.facet(scrollMargins)) {
|
|||
|
|
let m = source(view);
|
|||
|
|
if (m) {
|
|||
|
|
if (m.left != null)
|
|||
|
|
left = Math.max(left, m.left);
|
|||
|
|
if (m.right != null)
|
|||
|
|
right = Math.max(right, m.right);
|
|||
|
|
if (m.top != null)
|
|||
|
|
top2 = Math.max(top2, m.top);
|
|||
|
|
if (m.bottom != null)
|
|||
|
|
bottom = Math.max(bottom, m.bottom);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { left, right, top: top2, bottom };
|
|||
|
|
}
|
|||
|
|
var styleModule = Facet.define();
|
|||
|
|
var ChangedRange = class _ChangedRange {
|
|||
|
|
constructor(fromA, toA, fromB, toB) {
|
|||
|
|
this.fromA = fromA;
|
|||
|
|
this.toA = toA;
|
|||
|
|
this.fromB = fromB;
|
|||
|
|
this.toB = toB;
|
|||
|
|
}
|
|||
|
|
join(other) {
|
|||
|
|
return new _ChangedRange(Math.min(this.fromA, other.fromA), Math.max(this.toA, other.toA), Math.min(this.fromB, other.fromB), Math.max(this.toB, other.toB));
|
|||
|
|
}
|
|||
|
|
addToSet(set) {
|
|||
|
|
let i = set.length, me = this;
|
|||
|
|
for (; i > 0; i--) {
|
|||
|
|
let range = set[i - 1];
|
|||
|
|
if (range.fromA > me.toA)
|
|||
|
|
continue;
|
|||
|
|
if (range.toA < me.fromA)
|
|||
|
|
break;
|
|||
|
|
me = me.join(range);
|
|||
|
|
set.splice(i - 1, 1);
|
|||
|
|
}
|
|||
|
|
set.splice(i, 0, me);
|
|||
|
|
return set;
|
|||
|
|
}
|
|||
|
|
// Extend a set to cover all the content in `ranges`, which is a
|
|||
|
|
// flat array with each pair of numbers representing fromB/toB
|
|||
|
|
// positions. These pairs are generated in unchanged ranges, so the
|
|||
|
|
// offset between doc A and doc B is the same for their start and
|
|||
|
|
// end points.
|
|||
|
|
static extendWithRanges(diff, ranges) {
|
|||
|
|
if (ranges.length == 0)
|
|||
|
|
return diff;
|
|||
|
|
let result = [];
|
|||
|
|
for (let dI = 0, rI = 0, off = 0; ; ) {
|
|||
|
|
let nextD = dI < diff.length ? diff[dI].fromB : 1e9;
|
|||
|
|
let nextR = rI < ranges.length ? ranges[rI] : 1e9;
|
|||
|
|
let fromB = Math.min(nextD, nextR);
|
|||
|
|
if (fromB == 1e9)
|
|||
|
|
break;
|
|||
|
|
let fromA = fromB + off, toB = fromB, toA = fromA;
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (rI < ranges.length && ranges[rI] <= toB) {
|
|||
|
|
let end = ranges[rI + 1];
|
|||
|
|
rI += 2;
|
|||
|
|
toB = Math.max(toB, end);
|
|||
|
|
for (let i = dI; i < diff.length && diff[i].fromB <= toB; i++)
|
|||
|
|
off = diff[i].toA - diff[i].toB;
|
|||
|
|
toA = Math.max(toA, end + off);
|
|||
|
|
} else if (dI < diff.length && diff[dI].fromB <= toB) {
|
|||
|
|
let next = diff[dI++];
|
|||
|
|
toB = Math.max(toB, next.toB);
|
|||
|
|
toA = Math.max(toA, next.toA);
|
|||
|
|
off = next.toA - next.toB;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
result.push(new _ChangedRange(fromA, toA, fromB, toB));
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var ViewUpdate = class _ViewUpdate {
|
|||
|
|
constructor(view, state, transactions) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.state = state;
|
|||
|
|
this.transactions = transactions;
|
|||
|
|
this.flags = 0;
|
|||
|
|
this.startState = view.state;
|
|||
|
|
this.changes = ChangeSet.empty(this.startState.doc.length);
|
|||
|
|
for (let tr of transactions)
|
|||
|
|
this.changes = this.changes.compose(tr.changes);
|
|||
|
|
let changedRanges = [];
|
|||
|
|
this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)));
|
|||
|
|
this.changedRanges = changedRanges;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
static create(view, state, transactions) {
|
|||
|
|
return new _ViewUpdate(view, state, transactions);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
|
|||
|
|
[visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
|
|||
|
|
update.
|
|||
|
|
*/
|
|||
|
|
get viewportChanged() {
|
|||
|
|
return (this.flags & 4) > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns true when
|
|||
|
|
[`viewportChanged`](https://codemirror.net/6/docs/ref/#view.ViewUpdate.viewportChanged) is true
|
|||
|
|
and the viewport change is not just the result of mapping it in
|
|||
|
|
response to document changes.
|
|||
|
|
*/
|
|||
|
|
get viewportMoved() {
|
|||
|
|
return (this.flags & 8) > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Indicates whether the height of a block element in the editor
|
|||
|
|
changed in this update.
|
|||
|
|
*/
|
|||
|
|
get heightChanged() {
|
|||
|
|
return (this.flags & 2) > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns true when the document was modified or the size of the
|
|||
|
|
editor, or elements within the editor, changed.
|
|||
|
|
*/
|
|||
|
|
get geometryChanged() {
|
|||
|
|
return this.docChanged || (this.flags & (16 | 2)) > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
True when this update indicates a focus change.
|
|||
|
|
*/
|
|||
|
|
get focusChanged() {
|
|||
|
|
return (this.flags & 1) > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Whether the document changed in this update.
|
|||
|
|
*/
|
|||
|
|
get docChanged() {
|
|||
|
|
return !this.changes.empty;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Whether the selection was explicitly set in this update.
|
|||
|
|
*/
|
|||
|
|
get selectionSet() {
|
|||
|
|
return this.transactions.some((tr) => tr.selection);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
get empty() {
|
|||
|
|
return this.flags == 0 && this.transactions.length == 0;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var noChildren = [];
|
|||
|
|
var Tile = class {
|
|||
|
|
constructor(dom, length, flags = 0) {
|
|||
|
|
this.dom = dom;
|
|||
|
|
this.length = length;
|
|||
|
|
this.flags = flags;
|
|||
|
|
this.parent = null;
|
|||
|
|
dom.cmTile = this;
|
|||
|
|
}
|
|||
|
|
get breakAfter() {
|
|||
|
|
return this.flags & 1;
|
|||
|
|
}
|
|||
|
|
get children() {
|
|||
|
|
return noChildren;
|
|||
|
|
}
|
|||
|
|
isWidget() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
get isHidden() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
isComposite() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
isLine() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
isText() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
isBlock() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
get domAttrs() {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
sync(track) {
|
|||
|
|
this.flags |= 2;
|
|||
|
|
if (this.flags & 4) {
|
|||
|
|
this.flags &= ~4;
|
|||
|
|
let attrs = this.domAttrs;
|
|||
|
|
if (attrs)
|
|||
|
|
setAttrs(this.dom, attrs);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return this.constructor.name + (this.children.length ? `(${this.children})` : "") + (this.breakAfter ? "#" : "");
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
this.parent = null;
|
|||
|
|
}
|
|||
|
|
setDOM(dom) {
|
|||
|
|
this.dom = dom;
|
|||
|
|
dom.cmTile = this;
|
|||
|
|
}
|
|||
|
|
get posAtStart() {
|
|||
|
|
return this.parent ? this.parent.posBefore(this) : 0;
|
|||
|
|
}
|
|||
|
|
get posAtEnd() {
|
|||
|
|
return this.posAtStart + this.length;
|
|||
|
|
}
|
|||
|
|
posBefore(tile, start = this.posAtStart) {
|
|||
|
|
let pos = start;
|
|||
|
|
for (let child of this.children) {
|
|||
|
|
if (child == tile)
|
|||
|
|
return pos;
|
|||
|
|
pos += child.length + child.breakAfter;
|
|||
|
|
}
|
|||
|
|
throw new RangeError("Invalid child in posBefore");
|
|||
|
|
}
|
|||
|
|
posAfter(tile) {
|
|||
|
|
return this.posBefore(tile) + tile.length;
|
|||
|
|
}
|
|||
|
|
covers(side) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
coordsIn(pos, side) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
domPosFor(off, side) {
|
|||
|
|
let index = domIndex(this.dom);
|
|||
|
|
let after = this.length ? off > 0 : side > 0;
|
|||
|
|
return new DOMPos(this.parent.dom, index + (after ? 1 : 0), off == 0 || off == this.length);
|
|||
|
|
}
|
|||
|
|
markDirty(attrs) {
|
|||
|
|
this.flags &= ~2;
|
|||
|
|
if (attrs)
|
|||
|
|
this.flags |= 4;
|
|||
|
|
if (this.parent && this.parent.flags & 2)
|
|||
|
|
this.parent.markDirty(false);
|
|||
|
|
}
|
|||
|
|
get overrideDOMText() {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
get root() {
|
|||
|
|
for (let t = this; t; t = t.parent)
|
|||
|
|
if (t instanceof DocTile)
|
|||
|
|
return t;
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
static get(dom) {
|
|||
|
|
return dom.cmTile;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var CompositeTile = class extends Tile {
|
|||
|
|
constructor(dom) {
|
|||
|
|
super(dom, 0);
|
|||
|
|
this._children = [];
|
|||
|
|
}
|
|||
|
|
isComposite() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get children() {
|
|||
|
|
return this._children;
|
|||
|
|
}
|
|||
|
|
get lastChild() {
|
|||
|
|
return this.children.length ? this.children[this.children.length - 1] : null;
|
|||
|
|
}
|
|||
|
|
append(child) {
|
|||
|
|
this.children.push(child);
|
|||
|
|
child.parent = this;
|
|||
|
|
}
|
|||
|
|
sync(track) {
|
|||
|
|
if (this.flags & 2)
|
|||
|
|
return;
|
|||
|
|
super.sync(track);
|
|||
|
|
let parent = this.dom, prev = null, next;
|
|||
|
|
let tracking = (track === null || track === void 0 ? void 0 : track.node) == parent ? track : null;
|
|||
|
|
let length = 0;
|
|||
|
|
for (let child of this.children) {
|
|||
|
|
child.sync(track);
|
|||
|
|
length += child.length + child.breakAfter;
|
|||
|
|
next = prev ? prev.nextSibling : parent.firstChild;
|
|||
|
|
if (tracking && next != child.dom)
|
|||
|
|
tracking.written = true;
|
|||
|
|
if (child.dom.parentNode == parent) {
|
|||
|
|
while (next && next != child.dom)
|
|||
|
|
next = rm$1(next);
|
|||
|
|
} else {
|
|||
|
|
parent.insertBefore(child.dom, next);
|
|||
|
|
}
|
|||
|
|
prev = child.dom;
|
|||
|
|
}
|
|||
|
|
next = prev ? prev.nextSibling : parent.firstChild;
|
|||
|
|
if (tracking && next)
|
|||
|
|
tracking.written = true;
|
|||
|
|
while (next)
|
|||
|
|
next = rm$1(next);
|
|||
|
|
this.length = length;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function rm$1(dom) {
|
|||
|
|
let next = dom.nextSibling;
|
|||
|
|
dom.parentNode.removeChild(dom);
|
|||
|
|
return next;
|
|||
|
|
}
|
|||
|
|
var DocTile = class extends CompositeTile {
|
|||
|
|
constructor(view, dom) {
|
|||
|
|
super(dom);
|
|||
|
|
this.view = view;
|
|||
|
|
}
|
|||
|
|
owns(tile) {
|
|||
|
|
for (; tile; tile = tile.parent)
|
|||
|
|
if (tile == this)
|
|||
|
|
return true;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
isBlock() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
nearest(dom) {
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (!dom)
|
|||
|
|
return null;
|
|||
|
|
let tile = Tile.get(dom);
|
|||
|
|
if (tile && this.owns(tile))
|
|||
|
|
return tile;
|
|||
|
|
dom = dom.parentNode;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
blockTiles(f) {
|
|||
|
|
for (let stack = [], cur = this, i = 0, pos = 0; ; ) {
|
|||
|
|
if (i == cur.children.length) {
|
|||
|
|
if (!stack.length)
|
|||
|
|
return;
|
|||
|
|
cur = cur.parent;
|
|||
|
|
if (cur.breakAfter)
|
|||
|
|
pos++;
|
|||
|
|
i = stack.pop();
|
|||
|
|
} else {
|
|||
|
|
let next = cur.children[i++];
|
|||
|
|
if (next instanceof BlockWrapperTile) {
|
|||
|
|
stack.push(i);
|
|||
|
|
cur = next;
|
|||
|
|
i = 0;
|
|||
|
|
} else {
|
|||
|
|
let end = pos + next.length;
|
|||
|
|
let result = f(next, pos);
|
|||
|
|
if (result !== void 0)
|
|||
|
|
return result;
|
|||
|
|
pos = end + next.breakAfter;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Find the block at the given position. If side < -1, make sure to
|
|||
|
|
// stay before block widgets at that position, if side > 1, after
|
|||
|
|
// such widgets (used for selection drawing, which needs to be able
|
|||
|
|
// to get coordinates for positions that aren't valid cursor positions).
|
|||
|
|
resolveBlock(pos, side) {
|
|||
|
|
let before, beforeOff = -1, after, afterOff = -1;
|
|||
|
|
this.blockTiles((tile, off) => {
|
|||
|
|
let end = off + tile.length;
|
|||
|
|
if (pos >= off && pos <= end) {
|
|||
|
|
if (tile.isWidget() && side >= -1 && side <= 1) {
|
|||
|
|
if (tile.flags & 32)
|
|||
|
|
return true;
|
|||
|
|
if (tile.flags & 16)
|
|||
|
|
before = void 0;
|
|||
|
|
}
|
|||
|
|
if ((off < pos || pos == end && (side < -1 ? tile.length : tile.covers(1))) && (!before || !tile.isWidget() && before.isWidget())) {
|
|||
|
|
before = tile;
|
|||
|
|
beforeOff = pos - off;
|
|||
|
|
}
|
|||
|
|
if ((end > pos || pos == off && (side > 1 ? tile.length : tile.covers(-1))) && (!after || !tile.isWidget() && after.isWidget())) {
|
|||
|
|
after = tile;
|
|||
|
|
afterOff = pos - off;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (!before && !after)
|
|||
|
|
throw new Error("No tile at position " + pos);
|
|||
|
|
return before && side < 0 || !after ? { tile: before, offset: beforeOff } : { tile: after, offset: afterOff };
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var BlockWrapperTile = class _BlockWrapperTile extends CompositeTile {
|
|||
|
|
constructor(dom, wrapper) {
|
|||
|
|
super(dom);
|
|||
|
|
this.wrapper = wrapper;
|
|||
|
|
}
|
|||
|
|
isBlock() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
covers(side) {
|
|||
|
|
if (!this.children.length)
|
|||
|
|
return false;
|
|||
|
|
return side < 0 ? this.children[0].covers(-1) : this.lastChild.covers(1);
|
|||
|
|
}
|
|||
|
|
get domAttrs() {
|
|||
|
|
return this.wrapper.attributes;
|
|||
|
|
}
|
|||
|
|
static of(wrapper, dom) {
|
|||
|
|
let tile = new _BlockWrapperTile(dom || document.createElement(wrapper.tagName), wrapper);
|
|||
|
|
if (!dom)
|
|||
|
|
tile.flags |= 4;
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var LineTile = class _LineTile extends CompositeTile {
|
|||
|
|
constructor(dom, attrs) {
|
|||
|
|
super(dom);
|
|||
|
|
this.attrs = attrs;
|
|||
|
|
}
|
|||
|
|
isLine() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
static start(attrs, dom, keepAttrs) {
|
|||
|
|
let line = new _LineTile(dom || document.createElement("div"), attrs);
|
|||
|
|
if (!dom || !keepAttrs)
|
|||
|
|
line.flags |= 4;
|
|||
|
|
return line;
|
|||
|
|
}
|
|||
|
|
get domAttrs() {
|
|||
|
|
return this.attrs;
|
|||
|
|
}
|
|||
|
|
// Find the tile associated with a given position in this line.
|
|||
|
|
resolveInline(pos, side, forCoords) {
|
|||
|
|
let before = null, beforeOff = -1, after = null, afterOff = -1;
|
|||
|
|
function scan(tile, pos2) {
|
|||
|
|
for (let i = 0, off = 0; i < tile.children.length && off <= pos2; i++) {
|
|||
|
|
let child = tile.children[i], end = off + child.length;
|
|||
|
|
if (end >= pos2) {
|
|||
|
|
if (child.isComposite()) {
|
|||
|
|
scan(child, pos2 - off);
|
|||
|
|
} else if ((!after || after.isHidden && (side > 0 || forCoords && onSameLine(after, child))) && (end > pos2 || child.flags & 32)) {
|
|||
|
|
after = child;
|
|||
|
|
afterOff = pos2 - off;
|
|||
|
|
} else if (off < pos2 || child.flags & 16 && !child.isHidden) {
|
|||
|
|
before = child;
|
|||
|
|
beforeOff = pos2 - off;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
off = end;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
scan(this, pos);
|
|||
|
|
let target = (side < 0 ? before : after) || before || after;
|
|||
|
|
return target ? { tile: target, offset: target == before ? beforeOff : afterOff } : null;
|
|||
|
|
}
|
|||
|
|
coordsIn(pos, side) {
|
|||
|
|
let found = this.resolveInline(pos, side, true);
|
|||
|
|
if (!found)
|
|||
|
|
return fallbackRect(this);
|
|||
|
|
return found.tile.coordsIn(Math.max(0, found.offset), side);
|
|||
|
|
}
|
|||
|
|
domIn(pos, side) {
|
|||
|
|
let found = this.resolveInline(pos, side);
|
|||
|
|
if (found) {
|
|||
|
|
let { tile, offset } = found;
|
|||
|
|
if (this.dom.contains(tile.dom)) {
|
|||
|
|
if (tile.isText())
|
|||
|
|
return new DOMPos(tile.dom, Math.min(tile.dom.nodeValue.length, offset));
|
|||
|
|
return tile.domPosFor(offset, tile.flags & 16 ? 1 : tile.flags & 32 ? -1 : side);
|
|||
|
|
}
|
|||
|
|
let parent = found.tile.parent, saw = false;
|
|||
|
|
for (let ch of parent.children) {
|
|||
|
|
if (saw)
|
|||
|
|
return new DOMPos(ch.dom, 0);
|
|||
|
|
if (ch == found.tile) {
|
|||
|
|
saw = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return new DOMPos(this.dom, 0);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function fallbackRect(tile) {
|
|||
|
|
let last = tile.dom.lastChild;
|
|||
|
|
if (!last)
|
|||
|
|
return tile.dom.getBoundingClientRect();
|
|||
|
|
let rects = clientRectsFor(last);
|
|||
|
|
return rects[rects.length - 1] || null;
|
|||
|
|
}
|
|||
|
|
function onSameLine(a, b) {
|
|||
|
|
let posA = a.coordsIn(0, 1), posB = b.coordsIn(0, 1);
|
|||
|
|
return posA && posB && posB.top < posA.bottom;
|
|||
|
|
}
|
|||
|
|
var MarkTile = class _MarkTile extends CompositeTile {
|
|||
|
|
constructor(dom, mark) {
|
|||
|
|
super(dom);
|
|||
|
|
this.mark = mark;
|
|||
|
|
}
|
|||
|
|
get domAttrs() {
|
|||
|
|
return this.mark.attrs;
|
|||
|
|
}
|
|||
|
|
static of(mark, dom) {
|
|||
|
|
let tile = new _MarkTile(dom || document.createElement(mark.tagName), mark);
|
|||
|
|
if (!dom)
|
|||
|
|
tile.flags |= 4;
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TextTile = class _TextTile extends Tile {
|
|||
|
|
constructor(dom, text) {
|
|||
|
|
super(dom, text.length);
|
|||
|
|
this.text = text;
|
|||
|
|
}
|
|||
|
|
sync(track) {
|
|||
|
|
if (this.flags & 2)
|
|||
|
|
return;
|
|||
|
|
super.sync(track);
|
|||
|
|
if (this.dom.nodeValue != this.text) {
|
|||
|
|
if (track && track.node == this.dom)
|
|||
|
|
track.written = true;
|
|||
|
|
this.dom.nodeValue = this.text;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
isText() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return JSON.stringify(this.text);
|
|||
|
|
}
|
|||
|
|
coordsIn(pos, side) {
|
|||
|
|
let length = this.dom.nodeValue.length;
|
|||
|
|
if (pos > length)
|
|||
|
|
pos = length;
|
|||
|
|
let from = pos, to = pos, flatten = 0;
|
|||
|
|
if (pos == 0 && side < 0 || pos == length && side >= 0) {
|
|||
|
|
if (!(browser.chrome || browser.gecko)) {
|
|||
|
|
if (pos) {
|
|||
|
|
from--;
|
|||
|
|
flatten = 1;
|
|||
|
|
} else if (to < length) {
|
|||
|
|
to++;
|
|||
|
|
flatten = -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (side < 0)
|
|||
|
|
from--;
|
|||
|
|
else if (to < length)
|
|||
|
|
to++;
|
|||
|
|
}
|
|||
|
|
let rects = textRange(this.dom, from, to).getClientRects();
|
|||
|
|
if (!rects.length)
|
|||
|
|
return null;
|
|||
|
|
let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
|
|||
|
|
if (browser.safari && !flatten && rect.width == 0)
|
|||
|
|
rect = Array.prototype.find.call(rects, (r) => r.width) || rect;
|
|||
|
|
return flatten ? flattenRect(rect, flatten < 0) : rect || null;
|
|||
|
|
}
|
|||
|
|
static of(text, dom) {
|
|||
|
|
let tile = new _TextTile(dom || document.createTextNode(text), text);
|
|||
|
|
if (!dom)
|
|||
|
|
tile.flags |= 2;
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var WidgetTile = class _WidgetTile extends Tile {
|
|||
|
|
constructor(dom, length, widget, flags) {
|
|||
|
|
super(dom, length, flags);
|
|||
|
|
this.widget = widget;
|
|||
|
|
}
|
|||
|
|
isWidget() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get isHidden() {
|
|||
|
|
return this.widget.isHidden;
|
|||
|
|
}
|
|||
|
|
covers(side) {
|
|||
|
|
if (this.flags & 48)
|
|||
|
|
return false;
|
|||
|
|
return (this.flags & (side < 0 ? 64 : 128)) > 0;
|
|||
|
|
}
|
|||
|
|
coordsIn(pos, side) {
|
|||
|
|
return this.coordsInWidget(pos, side, false);
|
|||
|
|
}
|
|||
|
|
coordsInWidget(pos, side, block) {
|
|||
|
|
let custom = this.widget.coordsAt(this.dom, pos, side);
|
|||
|
|
if (custom)
|
|||
|
|
return custom;
|
|||
|
|
if (block) {
|
|||
|
|
return flattenRect(this.dom.getBoundingClientRect(), this.length ? pos == 0 : side <= 0);
|
|||
|
|
} else {
|
|||
|
|
let rects = this.dom.getClientRects(), rect = null;
|
|||
|
|
if (!rects.length)
|
|||
|
|
return null;
|
|||
|
|
let fromBack = this.flags & 16 ? true : this.flags & 32 ? false : pos > 0;
|
|||
|
|
for (let i = fromBack ? rects.length - 1 : 0; ; i += fromBack ? -1 : 1) {
|
|||
|
|
rect = rects[i];
|
|||
|
|
if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
return flattenRect(rect, !fromBack);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
get overrideDOMText() {
|
|||
|
|
if (!this.length)
|
|||
|
|
return Text.empty;
|
|||
|
|
let { root } = this;
|
|||
|
|
if (!root)
|
|||
|
|
return Text.empty;
|
|||
|
|
let start = this.posAtStart;
|
|||
|
|
return root.view.state.doc.slice(start, start + this.length);
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
super.destroy();
|
|||
|
|
this.widget.destroy(this.dom);
|
|||
|
|
}
|
|||
|
|
static of(widget, view, length, flags, dom) {
|
|||
|
|
if (!dom) {
|
|||
|
|
dom = widget.toDOM(view);
|
|||
|
|
if (!widget.editable)
|
|||
|
|
dom.contentEditable = "false";
|
|||
|
|
}
|
|||
|
|
return new _WidgetTile(dom, length, widget, flags);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var WidgetBufferTile = class extends Tile {
|
|||
|
|
constructor(flags) {
|
|||
|
|
let img = document.createElement("img");
|
|||
|
|
img.className = "cm-widgetBuffer";
|
|||
|
|
img.setAttribute("aria-hidden", "true");
|
|||
|
|
super(img, 0, flags);
|
|||
|
|
}
|
|||
|
|
get isHidden() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get overrideDOMText() {
|
|||
|
|
return Text.empty;
|
|||
|
|
}
|
|||
|
|
coordsIn(pos) {
|
|||
|
|
return this.dom.getBoundingClientRect();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TilePointer = class {
|
|||
|
|
constructor(top2) {
|
|||
|
|
this.index = 0;
|
|||
|
|
this.beforeBreak = false;
|
|||
|
|
this.parents = [];
|
|||
|
|
this.tile = top2;
|
|||
|
|
}
|
|||
|
|
// Advance by the given distance. If side is -1, stop leaving or
|
|||
|
|
// entering tiles, or skipping zero-length tiles, once the distance
|
|||
|
|
// has been traversed. When side is 1, leave, enter, or skip
|
|||
|
|
// everything at the end position.
|
|||
|
|
advance(dist2, side, walker) {
|
|||
|
|
let { tile, index, beforeBreak, parents } = this;
|
|||
|
|
while (dist2 || side > 0) {
|
|||
|
|
if (!tile.isComposite()) {
|
|||
|
|
if (index == tile.length) {
|
|||
|
|
beforeBreak = !!tile.breakAfter;
|
|||
|
|
({ tile, index } = parents.pop());
|
|||
|
|
index++;
|
|||
|
|
} else if (!dist2) {
|
|||
|
|
break;
|
|||
|
|
} else {
|
|||
|
|
let take = Math.min(dist2, tile.length - index);
|
|||
|
|
if (walker)
|
|||
|
|
walker.skip(tile, index, index + take);
|
|||
|
|
dist2 -= take;
|
|||
|
|
index += take;
|
|||
|
|
}
|
|||
|
|
} else if (beforeBreak) {
|
|||
|
|
if (!dist2)
|
|||
|
|
break;
|
|||
|
|
if (walker)
|
|||
|
|
walker.break();
|
|||
|
|
dist2--;
|
|||
|
|
beforeBreak = false;
|
|||
|
|
} else if (index == tile.children.length) {
|
|||
|
|
if (!dist2 && !parents.length)
|
|||
|
|
break;
|
|||
|
|
if (walker)
|
|||
|
|
walker.leave(tile);
|
|||
|
|
beforeBreak = !!tile.breakAfter;
|
|||
|
|
({ tile, index } = parents.pop());
|
|||
|
|
index++;
|
|||
|
|
} else {
|
|||
|
|
let next = tile.children[index], brk = next.breakAfter;
|
|||
|
|
if ((side > 0 ? next.length <= dist2 : next.length < dist2) && (!walker || walker.skip(next, 0, next.length) !== false || !next.isComposite)) {
|
|||
|
|
beforeBreak = !!brk;
|
|||
|
|
index++;
|
|||
|
|
dist2 -= next.length;
|
|||
|
|
} else {
|
|||
|
|
parents.push({ tile, index });
|
|||
|
|
tile = next;
|
|||
|
|
index = 0;
|
|||
|
|
if (walker && next.isComposite())
|
|||
|
|
walker.enter(next);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.tile = tile;
|
|||
|
|
this.index = index;
|
|||
|
|
this.beforeBreak = beforeBreak;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
get root() {
|
|||
|
|
return this.parents.length ? this.parents[0].tile : this.tile;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var OpenWrapper = class {
|
|||
|
|
constructor(from, to, wrapper, rank) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
this.wrapper = wrapper;
|
|||
|
|
this.rank = rank;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TileBuilder = class {
|
|||
|
|
constructor(cache, root, blockWrappers2) {
|
|||
|
|
this.cache = cache;
|
|||
|
|
this.root = root;
|
|||
|
|
this.blockWrappers = blockWrappers2;
|
|||
|
|
this.curLine = null;
|
|||
|
|
this.lastBlock = null;
|
|||
|
|
this.afterWidget = null;
|
|||
|
|
this.pos = 0;
|
|||
|
|
this.wrappers = [];
|
|||
|
|
this.wrapperPos = 0;
|
|||
|
|
}
|
|||
|
|
addText(text, marks, openStart, tile) {
|
|||
|
|
var _a;
|
|||
|
|
this.flushBuffer();
|
|||
|
|
let parent = this.ensureMarks(marks, openStart);
|
|||
|
|
let prev = parent.lastChild;
|
|||
|
|
if (prev && prev.isText() && !(prev.flags & 8) && prev.length + text.length < 512) {
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
prev,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
let tile2 = parent.children[parent.children.length - 1] = new TextTile(prev.dom, prev.text + text);
|
|||
|
|
tile2.parent = parent;
|
|||
|
|
} else {
|
|||
|
|
parent.append(tile || TextTile.of(text, (_a = this.cache.find(TextTile)) === null || _a === void 0 ? void 0 : _a.dom));
|
|||
|
|
}
|
|||
|
|
this.pos += text.length;
|
|||
|
|
this.afterWidget = null;
|
|||
|
|
}
|
|||
|
|
addComposition(composition, context) {
|
|||
|
|
let line = this.curLine;
|
|||
|
|
if (line.dom != context.line.dom) {
|
|||
|
|
line.setDOM(this.cache.reused.has(context.line) ? freeNode(context.line.dom) : context.line.dom);
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
context.line,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
let head = line;
|
|||
|
|
for (let i = context.marks.length - 1; i >= 0; i--) {
|
|||
|
|
let mark = context.marks[i];
|
|||
|
|
let last = head.lastChild;
|
|||
|
|
if (last instanceof MarkTile && last.mark.eq(mark.mark)) {
|
|||
|
|
if (last.dom != mark.dom)
|
|||
|
|
last.setDOM(freeNode(mark.dom));
|
|||
|
|
head = last;
|
|||
|
|
} else {
|
|||
|
|
if (this.cache.reused.get(mark)) {
|
|||
|
|
let tile = Tile.get(mark.dom);
|
|||
|
|
if (tile)
|
|||
|
|
tile.setDOM(freeNode(mark.dom));
|
|||
|
|
}
|
|||
|
|
let nw = MarkTile.of(mark.mark, mark.dom);
|
|||
|
|
head.append(nw);
|
|||
|
|
head = nw;
|
|||
|
|
}
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
mark,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
let oldTile = Tile.get(composition.text);
|
|||
|
|
if (oldTile)
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
oldTile,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
let text = new TextTile(composition.text, composition.text.nodeValue);
|
|||
|
|
text.flags |= 8;
|
|||
|
|
this.pos = composition.range.toB;
|
|||
|
|
head.append(text);
|
|||
|
|
}
|
|||
|
|
addInlineWidget(widget, marks, openStart) {
|
|||
|
|
let noSpace = this.afterWidget && widget.flags & 48 && (this.afterWidget.flags & 48) == (widget.flags & 48);
|
|||
|
|
if (!noSpace)
|
|||
|
|
this.flushBuffer();
|
|||
|
|
let parent = this.ensureMarks(marks, openStart);
|
|||
|
|
if (!noSpace && !(widget.flags & 16))
|
|||
|
|
parent.append(this.getBuffer(1));
|
|||
|
|
parent.append(widget);
|
|||
|
|
this.pos += widget.length;
|
|||
|
|
this.afterWidget = widget;
|
|||
|
|
}
|
|||
|
|
addMark(tile, marks, openStart) {
|
|||
|
|
this.flushBuffer();
|
|||
|
|
let parent = this.ensureMarks(marks, openStart);
|
|||
|
|
parent.append(tile);
|
|||
|
|
this.pos += tile.length;
|
|||
|
|
this.afterWidget = null;
|
|||
|
|
}
|
|||
|
|
addBlockWidget(widget) {
|
|||
|
|
this.getBlockPos().append(widget);
|
|||
|
|
this.pos += widget.length;
|
|||
|
|
this.lastBlock = widget;
|
|||
|
|
this.endLine();
|
|||
|
|
}
|
|||
|
|
continueWidget(length) {
|
|||
|
|
let widget = this.afterWidget || this.lastBlock;
|
|||
|
|
widget.length += length;
|
|||
|
|
this.pos += length;
|
|||
|
|
}
|
|||
|
|
addLineStart(attrs, dom) {
|
|||
|
|
var _a;
|
|||
|
|
if (!attrs)
|
|||
|
|
attrs = lineBaseAttrs;
|
|||
|
|
let tile = LineTile.start(attrs, dom || ((_a = this.cache.find(LineTile)) === null || _a === void 0 ? void 0 : _a.dom), !!dom);
|
|||
|
|
this.getBlockPos().append(this.lastBlock = this.curLine = tile);
|
|||
|
|
}
|
|||
|
|
addLine(tile) {
|
|||
|
|
this.getBlockPos().append(tile);
|
|||
|
|
this.pos += tile.length;
|
|||
|
|
this.lastBlock = tile;
|
|||
|
|
this.endLine();
|
|||
|
|
}
|
|||
|
|
addBreak() {
|
|||
|
|
this.lastBlock.flags |= 1;
|
|||
|
|
this.endLine();
|
|||
|
|
this.pos++;
|
|||
|
|
}
|
|||
|
|
addLineStartIfNotCovered(attrs) {
|
|||
|
|
if (!this.blockPosCovered())
|
|||
|
|
this.addLineStart(attrs);
|
|||
|
|
}
|
|||
|
|
ensureLine(attrs) {
|
|||
|
|
if (!this.curLine)
|
|||
|
|
this.addLineStart(attrs);
|
|||
|
|
}
|
|||
|
|
ensureMarks(marks, openStart) {
|
|||
|
|
var _a;
|
|||
|
|
let parent = this.curLine;
|
|||
|
|
for (let i = marks.length - 1; i >= 0; i--) {
|
|||
|
|
let mark = marks[i], last;
|
|||
|
|
if (openStart > 0 && (last = parent.lastChild) && last instanceof MarkTile && last.mark.eq(mark)) {
|
|||
|
|
parent = last;
|
|||
|
|
openStart--;
|
|||
|
|
} else {
|
|||
|
|
let tile = MarkTile.of(mark, (_a = this.cache.find(MarkTile, (m) => m.mark.eq(mark))) === null || _a === void 0 ? void 0 : _a.dom);
|
|||
|
|
parent.append(tile);
|
|||
|
|
parent = tile;
|
|||
|
|
openStart = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return parent;
|
|||
|
|
}
|
|||
|
|
endLine() {
|
|||
|
|
if (this.curLine) {
|
|||
|
|
this.flushBuffer();
|
|||
|
|
let last = this.curLine.lastChild;
|
|||
|
|
if (!last || !hasContent(this.curLine, false) || last.dom.nodeName != "BR" && last.isWidget() && !(browser.ios && hasContent(this.curLine, true)))
|
|||
|
|
this.curLine.append(this.cache.findWidget(
|
|||
|
|
BreakWidget,
|
|||
|
|
0,
|
|||
|
|
32
|
|||
|
|
/* TileFlag.After */
|
|||
|
|
) || new WidgetTile(
|
|||
|
|
BreakWidget.toDOM(),
|
|||
|
|
0,
|
|||
|
|
BreakWidget,
|
|||
|
|
32
|
|||
|
|
/* TileFlag.After */
|
|||
|
|
));
|
|||
|
|
this.curLine = this.afterWidget = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
updateBlockWrappers() {
|
|||
|
|
if (this.wrapperPos > this.pos + 1e4) {
|
|||
|
|
this.blockWrappers.goto(this.pos);
|
|||
|
|
this.wrappers.length = 0;
|
|||
|
|
}
|
|||
|
|
for (let i = this.wrappers.length - 1; i >= 0; i--)
|
|||
|
|
if (this.wrappers[i].to < this.pos)
|
|||
|
|
this.wrappers.splice(i, 1);
|
|||
|
|
for (let cur = this.blockWrappers; cur.value && cur.from <= this.pos; cur.next())
|
|||
|
|
if (cur.to >= this.pos) {
|
|||
|
|
let rank = cur.rank * 102 + cur.value.rank;
|
|||
|
|
let wrap = new OpenWrapper(cur.from, cur.to, cur.value, rank), i = this.wrappers.length;
|
|||
|
|
while (i > 0 && (this.wrappers[i - 1].rank - wrap.rank || this.wrappers[i - 1].to - wrap.to) < 0)
|
|||
|
|
i--;
|
|||
|
|
this.wrappers.splice(i, 0, wrap);
|
|||
|
|
}
|
|||
|
|
this.wrapperPos = this.pos;
|
|||
|
|
}
|
|||
|
|
getBlockPos() {
|
|||
|
|
var _a;
|
|||
|
|
this.updateBlockWrappers();
|
|||
|
|
let parent = this.root;
|
|||
|
|
for (let wrap of this.wrappers) {
|
|||
|
|
let last = parent.lastChild;
|
|||
|
|
if (wrap.from < this.pos && last instanceof BlockWrapperTile && last.wrapper.eq(wrap.wrapper)) {
|
|||
|
|
parent = last;
|
|||
|
|
} else {
|
|||
|
|
let tile = BlockWrapperTile.of(wrap.wrapper, (_a = this.cache.find(BlockWrapperTile, (t) => t.wrapper.eq(wrap.wrapper))) === null || _a === void 0 ? void 0 : _a.dom);
|
|||
|
|
parent.append(tile);
|
|||
|
|
parent = tile;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return parent;
|
|||
|
|
}
|
|||
|
|
blockPosCovered() {
|
|||
|
|
let last = this.lastBlock;
|
|||
|
|
return last != null && !last.breakAfter && (!last.isWidget() || (last.flags & (32 | 128)) > 0);
|
|||
|
|
}
|
|||
|
|
getBuffer(side) {
|
|||
|
|
let flags = 2 | (side < 0 ? 16 : 32);
|
|||
|
|
let found = this.cache.find(
|
|||
|
|
WidgetBufferTile,
|
|||
|
|
void 0,
|
|||
|
|
1
|
|||
|
|
/* Reused.Full */
|
|||
|
|
);
|
|||
|
|
if (found)
|
|||
|
|
found.flags = flags;
|
|||
|
|
return found || new WidgetBufferTile(flags);
|
|||
|
|
}
|
|||
|
|
flushBuffer() {
|
|||
|
|
if (this.afterWidget && !(this.afterWidget.flags & 32)) {
|
|||
|
|
this.afterWidget.parent.append(this.getBuffer(-1));
|
|||
|
|
this.afterWidget = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TextStream = class {
|
|||
|
|
constructor(doc2) {
|
|||
|
|
this.skipCount = 0;
|
|||
|
|
this.text = "";
|
|||
|
|
this.textOff = 0;
|
|||
|
|
this.cursor = doc2.iter();
|
|||
|
|
}
|
|||
|
|
skip(len) {
|
|||
|
|
if (this.textOff + len <= this.text.length) {
|
|||
|
|
this.textOff += len;
|
|||
|
|
} else {
|
|||
|
|
this.skipCount += len - (this.text.length - this.textOff);
|
|||
|
|
this.text = "";
|
|||
|
|
this.textOff = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
next(maxLen) {
|
|||
|
|
if (this.textOff == this.text.length) {
|
|||
|
|
let { value, lineBreak, done } = this.cursor.next(this.skipCount);
|
|||
|
|
this.skipCount = 0;
|
|||
|
|
if (done)
|
|||
|
|
throw new Error("Ran out of text content when drawing inline views");
|
|||
|
|
this.text = value;
|
|||
|
|
let len = this.textOff = Math.min(maxLen, value.length);
|
|||
|
|
return lineBreak ? null : value.slice(0, len);
|
|||
|
|
}
|
|||
|
|
let end = Math.min(this.text.length, this.textOff + maxLen);
|
|||
|
|
let chars = this.text.slice(this.textOff, end);
|
|||
|
|
this.textOff = end;
|
|||
|
|
return chars;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var buckets = [WidgetTile, LineTile, TextTile, MarkTile, WidgetBufferTile, BlockWrapperTile, DocTile];
|
|||
|
|
for (let i = 0; i < buckets.length; i++)
|
|||
|
|
buckets[i].bucket = i;
|
|||
|
|
var TileCache = class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.buckets = buckets.map(() => []);
|
|||
|
|
this.index = buckets.map(() => 0);
|
|||
|
|
this.reused = /* @__PURE__ */ new Map();
|
|||
|
|
}
|
|||
|
|
// Put a tile in the cache.
|
|||
|
|
add(tile) {
|
|||
|
|
let i = tile.constructor.bucket, bucket = this.buckets[i];
|
|||
|
|
if (bucket.length < 6)
|
|||
|
|
bucket.push(tile);
|
|||
|
|
else
|
|||
|
|
bucket[
|
|||
|
|
this.index[i] = (this.index[i] + 1) % 6
|
|||
|
|
/* C.Bucket */
|
|||
|
|
] = tile;
|
|||
|
|
}
|
|||
|
|
find(cls, test, type = 2) {
|
|||
|
|
let i = cls.bucket;
|
|||
|
|
let bucket = this.buckets[i], off = this.index[i];
|
|||
|
|
for (let j = bucket.length - 1; j >= 0; j--) {
|
|||
|
|
let index = (j + off) % bucket.length, tile = bucket[index];
|
|||
|
|
if ((!test || test(tile)) && !this.reused.has(tile)) {
|
|||
|
|
bucket.splice(index, 1);
|
|||
|
|
if (index < off)
|
|||
|
|
this.index[i]--;
|
|||
|
|
this.reused.set(tile, type);
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
findWidget(widget, length, flags) {
|
|||
|
|
let widgets = this.buckets[0];
|
|||
|
|
if (widgets.length)
|
|||
|
|
for (let i = 0, pass = 0; ; i++) {
|
|||
|
|
if (i == widgets.length) {
|
|||
|
|
if (pass)
|
|||
|
|
return null;
|
|||
|
|
pass = 1;
|
|||
|
|
i = 0;
|
|||
|
|
}
|
|||
|
|
let tile = widgets[i];
|
|||
|
|
if (!this.reused.has(tile) && (pass == 0 ? tile.widget.compare(widget) : tile.widget.constructor == widget.constructor && widget.updateDOM(tile.dom, this.view, tile.widget))) {
|
|||
|
|
widgets.splice(i, 1);
|
|||
|
|
if (i < this.index[0])
|
|||
|
|
this.index[0]--;
|
|||
|
|
if (tile.widget == widget && tile.length == length && (tile.flags & (496 | 1)) == flags) {
|
|||
|
|
this.reused.set(
|
|||
|
|
tile,
|
|||
|
|
1
|
|||
|
|
/* Reused.Full */
|
|||
|
|
);
|
|||
|
|
return tile;
|
|||
|
|
} else {
|
|||
|
|
this.reused.set(
|
|||
|
|
tile,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
return new WidgetTile(tile.dom, length, widget, tile.flags & ~(496 | 1) | flags);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
reuse(tile) {
|
|||
|
|
this.reused.set(
|
|||
|
|
tile,
|
|||
|
|
1
|
|||
|
|
/* Reused.Full */
|
|||
|
|
);
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
maybeReuse(tile, type = 2) {
|
|||
|
|
if (this.reused.has(tile))
|
|||
|
|
return void 0;
|
|||
|
|
this.reused.set(tile, type);
|
|||
|
|
return tile.dom;
|
|||
|
|
}
|
|||
|
|
clear() {
|
|||
|
|
for (let i = 0; i < this.buckets.length; i++)
|
|||
|
|
this.buckets[i].length = this.index[i] = 0;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TileUpdate = class {
|
|||
|
|
constructor(view, old, blockWrappers2, decorations2, disallowBlockEffectsFor) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.decorations = decorations2;
|
|||
|
|
this.disallowBlockEffectsFor = disallowBlockEffectsFor;
|
|||
|
|
this.openWidget = false;
|
|||
|
|
this.openMarks = 0;
|
|||
|
|
this.cache = new TileCache(view);
|
|||
|
|
this.text = new TextStream(view.state.doc);
|
|||
|
|
this.builder = new TileBuilder(this.cache, new DocTile(view, view.contentDOM), RangeSet.iter(blockWrappers2));
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
old,
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
this.old = new TilePointer(old);
|
|||
|
|
this.reuseWalker = {
|
|||
|
|
skip: (tile, from, to) => {
|
|||
|
|
this.cache.add(tile);
|
|||
|
|
if (tile.isComposite())
|
|||
|
|
return false;
|
|||
|
|
},
|
|||
|
|
enter: (tile) => this.cache.add(tile),
|
|||
|
|
leave: () => {
|
|||
|
|
},
|
|||
|
|
break: () => {
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
run(changes, composition) {
|
|||
|
|
let compositionContext = composition && this.getCompositionContext(composition.text);
|
|||
|
|
for (let posA = 0, posB = 0, i = 0; ; ) {
|
|||
|
|
let next = i < changes.length ? changes[i++] : null;
|
|||
|
|
let skipA = next ? next.fromA : this.old.root.length;
|
|||
|
|
if (skipA > posA) {
|
|||
|
|
let len = skipA - posA;
|
|||
|
|
this.preserve(len, !i, !next);
|
|||
|
|
posA = skipA;
|
|||
|
|
posB += len;
|
|||
|
|
}
|
|||
|
|
if (!next)
|
|||
|
|
break;
|
|||
|
|
if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
|
|||
|
|
this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
|
|||
|
|
this.emit(posB, composition.range.fromB);
|
|||
|
|
this.cache.clear();
|
|||
|
|
this.builder.addComposition(composition, compositionContext);
|
|||
|
|
this.text.skip(composition.range.toB - composition.range.fromB);
|
|||
|
|
this.forward(composition.range.fromA, next.toA);
|
|||
|
|
this.emit(composition.range.toB, next.toB);
|
|||
|
|
} else {
|
|||
|
|
this.forward(next.fromA, next.toA);
|
|||
|
|
this.emit(posB, next.toB);
|
|||
|
|
}
|
|||
|
|
posB = next.toB;
|
|||
|
|
posA = next.toA;
|
|||
|
|
}
|
|||
|
|
if (this.builder.curLine)
|
|||
|
|
this.builder.endLine();
|
|||
|
|
return this.builder.root;
|
|||
|
|
}
|
|||
|
|
preserve(length, incStart, incEnd) {
|
|||
|
|
let activeMarks = getMarks(this.old), openMarks = this.openMarks;
|
|||
|
|
this.old.advance(length, incEnd ? 1 : -1, {
|
|||
|
|
skip: (tile, from, to) => {
|
|||
|
|
if (tile.isWidget()) {
|
|||
|
|
if (this.openWidget) {
|
|||
|
|
this.builder.continueWidget(to - from);
|
|||
|
|
} else {
|
|||
|
|
let widget = to > 0 || from < tile.length ? WidgetTile.of(tile.widget, this.view, to - from, tile.flags & 496, this.cache.maybeReuse(tile)) : this.cache.reuse(tile);
|
|||
|
|
if (widget.flags & 256) {
|
|||
|
|
widget.flags &= ~1;
|
|||
|
|
this.builder.addBlockWidget(widget);
|
|||
|
|
} else {
|
|||
|
|
this.builder.ensureLine(null);
|
|||
|
|
this.builder.addInlineWidget(widget, activeMarks, openMarks);
|
|||
|
|
openMarks = activeMarks.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (tile.isText()) {
|
|||
|
|
this.builder.ensureLine(null);
|
|||
|
|
if (!from && to == tile.length && !this.cache.reused.has(tile)) {
|
|||
|
|
this.builder.addText(tile.text, activeMarks, openMarks, this.cache.reuse(tile));
|
|||
|
|
} else {
|
|||
|
|
this.cache.add(tile);
|
|||
|
|
this.builder.addText(tile.text.slice(from, to), activeMarks, openMarks);
|
|||
|
|
}
|
|||
|
|
openMarks = activeMarks.length;
|
|||
|
|
} else if (tile.isLine()) {
|
|||
|
|
tile.flags &= ~1;
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
tile,
|
|||
|
|
1
|
|||
|
|
/* Reused.Full */
|
|||
|
|
);
|
|||
|
|
this.builder.addLine(tile);
|
|||
|
|
} else if (tile instanceof WidgetBufferTile) {
|
|||
|
|
this.cache.add(tile);
|
|||
|
|
} else if (tile instanceof MarkTile) {
|
|||
|
|
this.builder.ensureLine(null);
|
|||
|
|
this.builder.addMark(tile, activeMarks, openMarks);
|
|||
|
|
this.cache.reused.set(
|
|||
|
|
tile,
|
|||
|
|
1
|
|||
|
|
/* Reused.Full */
|
|||
|
|
);
|
|||
|
|
openMarks = activeMarks.length;
|
|||
|
|
} else {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
this.openWidget = false;
|
|||
|
|
},
|
|||
|
|
enter: (tile) => {
|
|||
|
|
if (tile.isLine()) {
|
|||
|
|
this.builder.addLineStart(tile.attrs, this.cache.maybeReuse(tile));
|
|||
|
|
} else {
|
|||
|
|
this.cache.add(tile);
|
|||
|
|
if (tile instanceof MarkTile)
|
|||
|
|
activeMarks.unshift(tile.mark);
|
|||
|
|
}
|
|||
|
|
this.openWidget = false;
|
|||
|
|
},
|
|||
|
|
leave: (tile) => {
|
|||
|
|
if (tile.isLine()) {
|
|||
|
|
if (activeMarks.length)
|
|||
|
|
activeMarks.length = openMarks = 0;
|
|||
|
|
} else if (tile instanceof MarkTile) {
|
|||
|
|
activeMarks.shift();
|
|||
|
|
openMarks = Math.min(openMarks, activeMarks.length);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
break: () => {
|
|||
|
|
this.builder.addBreak();
|
|||
|
|
this.openWidget = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.text.skip(length);
|
|||
|
|
}
|
|||
|
|
emit(from, to) {
|
|||
|
|
let pendingLineAttrs = null;
|
|||
|
|
let b = this.builder, markCount = 0;
|
|||
|
|
let openEnd = RangeSet.spans(this.decorations, from, to, {
|
|||
|
|
point: (from2, to2, deco, active, openStart, index) => {
|
|||
|
|
if (deco instanceof PointDecoration) {
|
|||
|
|
if (this.disallowBlockEffectsFor[index]) {
|
|||
|
|
if (deco.block)
|
|||
|
|
throw new RangeError("Block decorations may not be specified via plugins");
|
|||
|
|
if (to2 > this.view.state.doc.lineAt(from2).to)
|
|||
|
|
throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
|
|||
|
|
}
|
|||
|
|
markCount = active.length;
|
|||
|
|
if (openStart > active.length) {
|
|||
|
|
b.continueWidget(to2 - from2);
|
|||
|
|
} else {
|
|||
|
|
let widget = deco.widget || (deco.block ? NullWidget.block : NullWidget.inline);
|
|||
|
|
let flags = widgetFlags(deco);
|
|||
|
|
let tile = this.cache.findWidget(widget, to2 - from2, flags) || WidgetTile.of(widget, this.view, to2 - from2, flags);
|
|||
|
|
if (deco.block) {
|
|||
|
|
if (deco.startSide > 0)
|
|||
|
|
b.addLineStartIfNotCovered(pendingLineAttrs);
|
|||
|
|
b.addBlockWidget(tile);
|
|||
|
|
} else {
|
|||
|
|
b.ensureLine(pendingLineAttrs);
|
|||
|
|
b.addInlineWidget(tile, active, openStart);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
pendingLineAttrs = null;
|
|||
|
|
} else {
|
|||
|
|
pendingLineAttrs = addLineDeco(pendingLineAttrs, deco);
|
|||
|
|
}
|
|||
|
|
if (to2 > from2)
|
|||
|
|
this.text.skip(to2 - from2);
|
|||
|
|
},
|
|||
|
|
span: (from2, to2, active, openStart) => {
|
|||
|
|
for (let pos = from2; pos < to2; ) {
|
|||
|
|
let chars = this.text.next(Math.min(512, to2 - pos));
|
|||
|
|
if (chars == null) {
|
|||
|
|
b.addLineStartIfNotCovered(pendingLineAttrs);
|
|||
|
|
b.addBreak();
|
|||
|
|
pos++;
|
|||
|
|
} else {
|
|||
|
|
b.ensureLine(pendingLineAttrs);
|
|||
|
|
b.addText(chars, active, pos == from2 ? openStart : active.length);
|
|||
|
|
pos += chars.length;
|
|||
|
|
}
|
|||
|
|
pendingLineAttrs = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
b.addLineStartIfNotCovered(pendingLineAttrs);
|
|||
|
|
this.openWidget = openEnd > markCount;
|
|||
|
|
this.openMarks = openEnd;
|
|||
|
|
}
|
|||
|
|
forward(from, to, side = 1) {
|
|||
|
|
if (to - from <= 10) {
|
|||
|
|
this.old.advance(to - from, side, this.reuseWalker);
|
|||
|
|
} else {
|
|||
|
|
this.old.advance(5, -1, this.reuseWalker);
|
|||
|
|
this.old.advance(to - from - 10, -1);
|
|||
|
|
this.old.advance(5, side, this.reuseWalker);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
getCompositionContext(text) {
|
|||
|
|
let marks = [], line = null;
|
|||
|
|
for (let parent = text.parentNode; ; parent = parent.parentNode) {
|
|||
|
|
let tile = Tile.get(parent);
|
|||
|
|
if (parent == this.view.contentDOM)
|
|||
|
|
break;
|
|||
|
|
if (tile instanceof MarkTile)
|
|||
|
|
marks.push(tile);
|
|||
|
|
else if (tile === null || tile === void 0 ? void 0 : tile.isLine())
|
|||
|
|
line = tile;
|
|||
|
|
else if (tile instanceof BlockWrapperTile) ;
|
|||
|
|
else if (parent.nodeName == "DIV" && !line && parent != this.view.contentDOM)
|
|||
|
|
line = new LineTile(parent, lineBaseAttrs);
|
|||
|
|
else if (!line)
|
|||
|
|
marks.push(MarkTile.of(new MarkDecoration({ tagName: parent.nodeName.toLowerCase(), attributes: getAttrs(parent) }), parent));
|
|||
|
|
}
|
|||
|
|
return { line, marks };
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function hasContent(tile, requireText) {
|
|||
|
|
let scan = (tile2) => {
|
|||
|
|
for (let ch of tile2.children)
|
|||
|
|
if ((requireText ? ch.isText() : ch.length) || scan(ch))
|
|||
|
|
return true;
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
return scan(tile);
|
|||
|
|
}
|
|||
|
|
function widgetFlags(deco) {
|
|||
|
|
let flags = deco.isReplace ? (deco.startSide < 0 ? 64 : 0) | (deco.endSide > 0 ? 128 : 0) : deco.startSide > 0 ? 32 : 16;
|
|||
|
|
if (deco.block)
|
|||
|
|
flags |= 256;
|
|||
|
|
return flags;
|
|||
|
|
}
|
|||
|
|
var lineBaseAttrs = { class: "cm-line" };
|
|||
|
|
function addLineDeco(value, deco) {
|
|||
|
|
let attrs = deco.spec.attributes, cls = deco.spec.class;
|
|||
|
|
if (!attrs && !cls)
|
|||
|
|
return value;
|
|||
|
|
if (!value)
|
|||
|
|
value = { class: "cm-line" };
|
|||
|
|
if (attrs)
|
|||
|
|
combineAttrs(attrs, value);
|
|||
|
|
if (cls)
|
|||
|
|
value.class += " " + cls;
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
function getMarks(ptr) {
|
|||
|
|
let found = [];
|
|||
|
|
for (let i = ptr.parents.length; i > 1; i--) {
|
|||
|
|
let tile = i == ptr.parents.length ? ptr.tile : ptr.parents[i].tile;
|
|||
|
|
if (tile instanceof MarkTile)
|
|||
|
|
found.push(tile.mark);
|
|||
|
|
}
|
|||
|
|
return found;
|
|||
|
|
}
|
|||
|
|
function freeNode(node) {
|
|||
|
|
let tile = Tile.get(node);
|
|||
|
|
if (tile)
|
|||
|
|
tile.setDOM(node.cloneNode());
|
|||
|
|
return node;
|
|||
|
|
}
|
|||
|
|
var NullWidget = class extends WidgetType {
|
|||
|
|
constructor(tag) {
|
|||
|
|
super();
|
|||
|
|
this.tag = tag;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.tag == this.tag;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
return document.createElement(this.tag);
|
|||
|
|
}
|
|||
|
|
updateDOM(elt) {
|
|||
|
|
return elt.nodeName.toLowerCase() == this.tag;
|
|||
|
|
}
|
|||
|
|
get isHidden() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
NullWidget.inline = new NullWidget("span");
|
|||
|
|
NullWidget.block = new NullWidget("div");
|
|||
|
|
var BreakWidget = new class extends WidgetType {
|
|||
|
|
toDOM() {
|
|||
|
|
return document.createElement("br");
|
|||
|
|
}
|
|||
|
|
get isHidden() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get editable() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}();
|
|||
|
|
var DocView = class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.decorations = [];
|
|||
|
|
this.blockWrappers = [];
|
|||
|
|
this.dynamicDecorationMap = [false];
|
|||
|
|
this.domChanged = null;
|
|||
|
|
this.hasComposition = null;
|
|||
|
|
this.editContextFormatting = Decoration.none;
|
|||
|
|
this.lastCompositionAfterCursor = false;
|
|||
|
|
this.minWidth = 0;
|
|||
|
|
this.minWidthFrom = 0;
|
|||
|
|
this.minWidthTo = 0;
|
|||
|
|
this.impreciseAnchor = null;
|
|||
|
|
this.impreciseHead = null;
|
|||
|
|
this.forceSelection = false;
|
|||
|
|
this.lastUpdate = Date.now();
|
|||
|
|
this.updateDeco();
|
|||
|
|
this.tile = new DocTile(view, view.contentDOM);
|
|||
|
|
this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], null);
|
|||
|
|
}
|
|||
|
|
// Update the document view to a given state.
|
|||
|
|
update(update) {
|
|||
|
|
var _a;
|
|||
|
|
let changedRanges = update.changedRanges;
|
|||
|
|
if (this.minWidth > 0 && changedRanges.length) {
|
|||
|
|
if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
|
|||
|
|
this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
|
|||
|
|
} else {
|
|||
|
|
this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
|
|||
|
|
this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.updateEditContextFormatting(update);
|
|||
|
|
let readCompositionAt = -1;
|
|||
|
|
if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
|
|||
|
|
if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
|
|||
|
|
readCompositionAt = this.domChanged.newSel.head;
|
|||
|
|
else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
|
|||
|
|
readCompositionAt = update.state.selection.main.head;
|
|||
|
|
}
|
|||
|
|
let composition = readCompositionAt > -1 ? findCompositionRange(this.view, update.changes, readCompositionAt) : null;
|
|||
|
|
this.domChanged = null;
|
|||
|
|
if (this.hasComposition) {
|
|||
|
|
let { from, to } = this.hasComposition;
|
|||
|
|
changedRanges = new ChangedRange(from, to, update.changes.mapPos(from, -1), update.changes.mapPos(to, 1)).addToSet(changedRanges.slice());
|
|||
|
|
}
|
|||
|
|
this.hasComposition = composition ? { from: composition.range.fromB, to: composition.range.toB } : null;
|
|||
|
|
if ((browser.ie || browser.chrome) && !composition && update && update.state.doc.lines != update.startState.doc.lines)
|
|||
|
|
this.forceSelection = true;
|
|||
|
|
let prevDeco = this.decorations, prevWrappers = this.blockWrappers;
|
|||
|
|
this.updateDeco();
|
|||
|
|
let decoDiff = findChangedDeco(prevDeco, this.decorations, update.changes);
|
|||
|
|
if (decoDiff.length)
|
|||
|
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
|
|||
|
|
let blockDiff = findChangedWrappers(prevWrappers, this.blockWrappers, update.changes);
|
|||
|
|
if (blockDiff.length)
|
|||
|
|
changedRanges = ChangedRange.extendWithRanges(changedRanges, blockDiff);
|
|||
|
|
if (composition && !changedRanges.some((r) => r.fromA <= composition.range.fromA && r.toA >= composition.range.toA))
|
|||
|
|
changedRanges = composition.range.addToSet(changedRanges.slice());
|
|||
|
|
if (this.tile.flags & 2 && changedRanges.length == 0) {
|
|||
|
|
return false;
|
|||
|
|
} else {
|
|||
|
|
this.updateInner(changedRanges, composition);
|
|||
|
|
if (update.transactions.length)
|
|||
|
|
this.lastUpdate = Date.now();
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Used by update and the constructor do perform the actual DOM
|
|||
|
|
// update
|
|||
|
|
updateInner(changes, composition) {
|
|||
|
|
this.view.viewState.mustMeasureContent = true;
|
|||
|
|
let { observer } = this.view;
|
|||
|
|
observer.ignore(() => {
|
|||
|
|
if (composition || changes.length) {
|
|||
|
|
let oldTile = this.tile;
|
|||
|
|
let builder = new TileUpdate(this.view, oldTile, this.blockWrappers, this.decorations, this.dynamicDecorationMap);
|
|||
|
|
if (composition && Tile.get(composition.text))
|
|||
|
|
builder.cache.reused.set(
|
|||
|
|
Tile.get(composition.text),
|
|||
|
|
2
|
|||
|
|
/* Reused.DOM */
|
|||
|
|
);
|
|||
|
|
this.tile = builder.run(changes, composition);
|
|||
|
|
destroyDropped(oldTile, builder.cache.reused);
|
|||
|
|
}
|
|||
|
|
this.tile.dom.style.height = this.view.viewState.contentHeight / this.view.scaleY + "px";
|
|||
|
|
this.tile.dom.style.flexBasis = this.minWidth ? this.minWidth + "px" : "";
|
|||
|
|
let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : void 0;
|
|||
|
|
this.tile.sync(track);
|
|||
|
|
if (track && (track.written || observer.selectionRange.focusNode != track.node || !this.tile.dom.contains(track.node)))
|
|||
|
|
this.forceSelection = true;
|
|||
|
|
this.tile.dom.style.height = "";
|
|||
|
|
});
|
|||
|
|
let gaps = [];
|
|||
|
|
if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length) {
|
|||
|
|
for (let child of this.tile.children)
|
|||
|
|
if (child.isWidget() && child.widget instanceof BlockGapWidget)
|
|||
|
|
gaps.push(child.dom);
|
|||
|
|
}
|
|||
|
|
observer.updateGaps(gaps);
|
|||
|
|
}
|
|||
|
|
updateEditContextFormatting(update) {
|
|||
|
|
this.editContextFormatting = this.editContextFormatting.map(update.changes);
|
|||
|
|
for (let tr of update.transactions)
|
|||
|
|
for (let effect of tr.effects)
|
|||
|
|
if (effect.is(setEditContextFormatting)) {
|
|||
|
|
this.editContextFormatting = effect.value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Sync the DOM selection to this.state.selection
|
|||
|
|
updateSelection(mustRead = false, fromPointer = false) {
|
|||
|
|
if (mustRead || !this.view.observer.selectionRange.focusNode)
|
|||
|
|
this.view.observer.readSelectionRange();
|
|||
|
|
let { dom } = this.tile;
|
|||
|
|
let activeElt = this.view.root.activeElement, focused = activeElt == dom;
|
|||
|
|
let selectionNotFocus = !focused && !(this.view.state.facet(editable) || dom.tabIndex > -1) && hasSelection(dom, this.view.observer.selectionRange) && !(activeElt && dom.contains(activeElt));
|
|||
|
|
if (!(focused || fromPointer || selectionNotFocus))
|
|||
|
|
return;
|
|||
|
|
let force = this.forceSelection;
|
|||
|
|
this.forceSelection = false;
|
|||
|
|
let main = this.view.state.selection.main, anchor, head;
|
|||
|
|
if (main.empty) {
|
|||
|
|
head = anchor = this.inlineDOMNearPos(main.anchor, main.assoc || 1);
|
|||
|
|
} else {
|
|||
|
|
head = this.inlineDOMNearPos(main.head, main.head == main.from ? 1 : -1);
|
|||
|
|
anchor = this.inlineDOMNearPos(main.anchor, main.anchor == main.from ? 1 : -1);
|
|||
|
|
}
|
|||
|
|
if (browser.gecko && main.empty && !this.hasComposition && betweenUneditable(anchor)) {
|
|||
|
|
let dummy = document.createTextNode("");
|
|||
|
|
this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
|
|||
|
|
anchor = head = new DOMPos(dummy, 0);
|
|||
|
|
force = true;
|
|||
|
|
}
|
|||
|
|
let domSel = this.view.observer.selectionRange;
|
|||
|
|
if (force || !domSel.focusNode || (!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) || !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) && !this.suppressWidgetCursorChange(domSel, main)) {
|
|||
|
|
this.view.observer.ignore(() => {
|
|||
|
|
if (browser.android && browser.chrome && dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, dom)) {
|
|||
|
|
dom.blur();
|
|||
|
|
dom.focus({ preventScroll: true });
|
|||
|
|
}
|
|||
|
|
let rawSel = getSelection(this.view.root);
|
|||
|
|
if (!rawSel) ;
|
|||
|
|
else if (main.empty) {
|
|||
|
|
if (browser.gecko) {
|
|||
|
|
let nextTo = nextToUneditable(anchor.node, anchor.offset);
|
|||
|
|
if (nextTo && nextTo != (1 | 2)) {
|
|||
|
|
let text = (nextTo == 1 ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
|
|||
|
|
if (text)
|
|||
|
|
anchor = new DOMPos(text.node, text.offset);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
rawSel.collapse(anchor.node, anchor.offset);
|
|||
|
|
if (main.bidiLevel != null && rawSel.caretBidiLevel !== void 0)
|
|||
|
|
rawSel.caretBidiLevel = main.bidiLevel;
|
|||
|
|
} else if (rawSel.extend) {
|
|||
|
|
rawSel.collapse(anchor.node, anchor.offset);
|
|||
|
|
try {
|
|||
|
|
rawSel.extend(head.node, head.offset);
|
|||
|
|
} catch (_) {
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let range = document.createRange();
|
|||
|
|
if (main.anchor > main.head)
|
|||
|
|
[anchor, head] = [head, anchor];
|
|||
|
|
range.setEnd(head.node, head.offset);
|
|||
|
|
range.setStart(anchor.node, anchor.offset);
|
|||
|
|
rawSel.removeAllRanges();
|
|||
|
|
rawSel.addRange(range);
|
|||
|
|
}
|
|||
|
|
if (selectionNotFocus && this.view.root.activeElement == dom) {
|
|||
|
|
dom.blur();
|
|||
|
|
if (activeElt)
|
|||
|
|
activeElt.focus();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.view.observer.setSelectionRange(anchor, head);
|
|||
|
|
}
|
|||
|
|
this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
|
|||
|
|
this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
|
|||
|
|
}
|
|||
|
|
// If a zero-length widget is inserted next to the cursor during
|
|||
|
|
// composition, avoid moving it across it and disrupting the
|
|||
|
|
// composition.
|
|||
|
|
suppressWidgetCursorChange(sel, cursor) {
|
|||
|
|
return this.hasComposition && cursor.empty && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset) && this.posFromDOM(sel.focusNode, sel.focusOffset) == cursor.head;
|
|||
|
|
}
|
|||
|
|
enforceCursorAssoc() {
|
|||
|
|
if (this.hasComposition)
|
|||
|
|
return;
|
|||
|
|
let { view } = this, cursor = view.state.selection.main;
|
|||
|
|
let sel = getSelection(view.root);
|
|||
|
|
let { anchorNode, anchorOffset } = view.observer.selectionRange;
|
|||
|
|
if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
|
|||
|
|
return;
|
|||
|
|
let line = this.lineAt(cursor.head, cursor.assoc);
|
|||
|
|
if (!line)
|
|||
|
|
return;
|
|||
|
|
let lineStart = line.posAtStart;
|
|||
|
|
if (cursor.head == lineStart || cursor.head == lineStart + line.length)
|
|||
|
|
return;
|
|||
|
|
let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
|
|||
|
|
if (!before || !after || before.bottom > after.top)
|
|||
|
|
return;
|
|||
|
|
let dom = this.domAtPos(cursor.head + cursor.assoc, cursor.assoc);
|
|||
|
|
sel.collapse(dom.node, dom.offset);
|
|||
|
|
sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
|
|||
|
|
view.observer.readSelectionRange();
|
|||
|
|
let newRange = view.observer.selectionRange;
|
|||
|
|
if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
|
|||
|
|
sel.collapse(anchorNode, anchorOffset);
|
|||
|
|
}
|
|||
|
|
posFromDOM(node, offset) {
|
|||
|
|
let tile = this.tile.nearest(node);
|
|||
|
|
if (!tile)
|
|||
|
|
return this.tile.dom.compareDocumentPosition(node) & 2 ? 0 : this.view.state.doc.length;
|
|||
|
|
let start = tile.posAtStart;
|
|||
|
|
if (tile.isComposite()) {
|
|||
|
|
let after;
|
|||
|
|
if (node == tile.dom) {
|
|||
|
|
after = tile.dom.childNodes[offset];
|
|||
|
|
} else {
|
|||
|
|
let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
|
|||
|
|
for (; ; ) {
|
|||
|
|
let parent = node.parentNode;
|
|||
|
|
if (parent == tile.dom)
|
|||
|
|
break;
|
|||
|
|
if (bias == 0 && parent.firstChild != parent.lastChild) {
|
|||
|
|
if (node == parent.firstChild)
|
|||
|
|
bias = -1;
|
|||
|
|
else
|
|||
|
|
bias = 1;
|
|||
|
|
}
|
|||
|
|
node = parent;
|
|||
|
|
}
|
|||
|
|
if (bias < 0)
|
|||
|
|
after = node;
|
|||
|
|
else
|
|||
|
|
after = node.nextSibling;
|
|||
|
|
}
|
|||
|
|
if (after == tile.dom.firstChild)
|
|||
|
|
return start;
|
|||
|
|
while (after && !Tile.get(after))
|
|||
|
|
after = after.nextSibling;
|
|||
|
|
if (!after)
|
|||
|
|
return start + tile.length;
|
|||
|
|
for (let i = 0, pos = start; ; i++) {
|
|||
|
|
let child = tile.children[i];
|
|||
|
|
if (child.dom == after)
|
|||
|
|
return pos;
|
|||
|
|
pos += child.length + child.breakAfter;
|
|||
|
|
}
|
|||
|
|
} else if (tile.isText()) {
|
|||
|
|
return node == tile.dom ? start + offset : start + (offset ? tile.length : 0);
|
|||
|
|
} else {
|
|||
|
|
return start;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
domAtPos(pos, side) {
|
|||
|
|
let { tile, offset } = this.tile.resolveBlock(pos, side);
|
|||
|
|
if (tile.isWidget())
|
|||
|
|
return tile.domPosFor(pos, side);
|
|||
|
|
return tile.domIn(offset, side);
|
|||
|
|
}
|
|||
|
|
inlineDOMNearPos(pos, side) {
|
|||
|
|
let before, beforeOff = -1, beforeBad = false;
|
|||
|
|
let after, afterOff = -1, afterBad = false;
|
|||
|
|
this.tile.blockTiles((tile, off) => {
|
|||
|
|
if (tile.isWidget()) {
|
|||
|
|
if (tile.flags & 32 && off >= pos)
|
|||
|
|
return true;
|
|||
|
|
if (tile.flags & 16)
|
|||
|
|
beforeBad = true;
|
|||
|
|
} else {
|
|||
|
|
let end = off + tile.length;
|
|||
|
|
if (off <= pos) {
|
|||
|
|
before = tile;
|
|||
|
|
beforeOff = pos - off;
|
|||
|
|
beforeBad = end < pos;
|
|||
|
|
}
|
|||
|
|
if (end >= pos && !after) {
|
|||
|
|
after = tile;
|
|||
|
|
afterOff = pos - off;
|
|||
|
|
afterBad = off > pos;
|
|||
|
|
}
|
|||
|
|
if (off > pos && after)
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (!before && !after)
|
|||
|
|
return this.domAtPos(pos, side);
|
|||
|
|
if (beforeBad && after)
|
|||
|
|
before = null;
|
|||
|
|
else if (afterBad && before)
|
|||
|
|
after = null;
|
|||
|
|
return before && side < 0 || !after ? before.domIn(beforeOff, side) : after.domIn(afterOff, side);
|
|||
|
|
}
|
|||
|
|
coordsAt(pos, side) {
|
|||
|
|
let { tile, offset } = this.tile.resolveBlock(pos, side);
|
|||
|
|
if (tile.isWidget()) {
|
|||
|
|
if (tile.widget instanceof BlockGapWidget)
|
|||
|
|
return null;
|
|||
|
|
return tile.coordsInWidget(offset, side, true);
|
|||
|
|
}
|
|||
|
|
return tile.coordsIn(offset, side);
|
|||
|
|
}
|
|||
|
|
lineAt(pos, side) {
|
|||
|
|
let { tile } = this.tile.resolveBlock(pos, side);
|
|||
|
|
return tile.isLine() ? tile : null;
|
|||
|
|
}
|
|||
|
|
coordsForChar(pos) {
|
|||
|
|
let { tile, offset } = this.tile.resolveBlock(pos, 1);
|
|||
|
|
if (!tile.isLine())
|
|||
|
|
return null;
|
|||
|
|
function scan(tile2, offset2) {
|
|||
|
|
if (tile2.isComposite()) {
|
|||
|
|
for (let ch of tile2.children) {
|
|||
|
|
if (ch.length >= offset2) {
|
|||
|
|
let found = scan(ch, offset2);
|
|||
|
|
if (found)
|
|||
|
|
return found;
|
|||
|
|
}
|
|||
|
|
offset2 -= ch.length;
|
|||
|
|
if (offset2 < 0)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} else if (tile2.isText() && offset2 < tile2.length) {
|
|||
|
|
let end = findClusterBreak(tile2.text, offset2);
|
|||
|
|
if (end == offset2)
|
|||
|
|
return null;
|
|||
|
|
let rects = textRange(tile2.dom, offset2, end).getClientRects();
|
|||
|
|
for (let i = 0; i < rects.length; i++) {
|
|||
|
|
let rect = rects[i];
|
|||
|
|
if (i == rects.length - 1 || rect.top < rect.bottom && rect.left < rect.right)
|
|||
|
|
return rect;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return scan(tile, offset);
|
|||
|
|
}
|
|||
|
|
measureVisibleLineHeights(viewport) {
|
|||
|
|
let result = [], { from, to } = viewport;
|
|||
|
|
let contentWidth = this.view.contentDOM.clientWidth;
|
|||
|
|
let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
|
|||
|
|
let widest = -1, ltr = this.view.textDirection == Direction.LTR;
|
|||
|
|
let spaceAbove = 0;
|
|||
|
|
let scan = (tile, pos, measureBounds) => {
|
|||
|
|
for (let i = 0; i < tile.children.length; i++) {
|
|||
|
|
if (pos > to)
|
|||
|
|
break;
|
|||
|
|
let child = tile.children[i], end = pos + child.length;
|
|||
|
|
let childRect = child.dom.getBoundingClientRect(), { height } = childRect;
|
|||
|
|
if (measureBounds && !i)
|
|||
|
|
spaceAbove += childRect.top - measureBounds.top;
|
|||
|
|
if (child instanceof BlockWrapperTile) {
|
|||
|
|
if (end > from)
|
|||
|
|
scan(child, pos, childRect);
|
|||
|
|
} else if (pos >= from) {
|
|||
|
|
if (spaceAbove > 0)
|
|||
|
|
result.push(-spaceAbove);
|
|||
|
|
result.push(height + spaceAbove);
|
|||
|
|
spaceAbove = 0;
|
|||
|
|
if (isWider) {
|
|||
|
|
let last = child.dom.lastChild;
|
|||
|
|
let rects = last ? clientRectsFor(last) : [];
|
|||
|
|
if (rects.length) {
|
|||
|
|
let rect = rects[rects.length - 1];
|
|||
|
|
let width = ltr ? rect.right - childRect.left : childRect.right - rect.left;
|
|||
|
|
if (width > widest) {
|
|||
|
|
widest = width;
|
|||
|
|
this.minWidth = contentWidth;
|
|||
|
|
this.minWidthFrom = pos;
|
|||
|
|
this.minWidthTo = end;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (measureBounds && i == tile.children.length - 1)
|
|||
|
|
spaceAbove += measureBounds.bottom - childRect.bottom;
|
|||
|
|
pos = end + child.breakAfter;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
scan(this.tile, 0, null);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
textDirectionAt(pos) {
|
|||
|
|
let { tile } = this.tile.resolveBlock(pos, 1);
|
|||
|
|
return getComputedStyle(tile.dom).direction == "rtl" ? Direction.RTL : Direction.LTR;
|
|||
|
|
}
|
|||
|
|
measureTextSize() {
|
|||
|
|
let lineMeasure = this.tile.blockTiles((tile) => {
|
|||
|
|
if (tile.isLine() && tile.children.length && tile.length <= 20) {
|
|||
|
|
let totalWidth = 0, textHeight2;
|
|||
|
|
for (let child of tile.children) {
|
|||
|
|
if (!child.isText() || /[^ -~]/.test(child.text))
|
|||
|
|
return void 0;
|
|||
|
|
let rects = clientRectsFor(child.dom);
|
|||
|
|
if (rects.length != 1)
|
|||
|
|
return void 0;
|
|||
|
|
totalWidth += rects[0].width;
|
|||
|
|
textHeight2 = rects[0].height;
|
|||
|
|
}
|
|||
|
|
if (totalWidth)
|
|||
|
|
return {
|
|||
|
|
lineHeight: tile.dom.getBoundingClientRect().height,
|
|||
|
|
charWidth: totalWidth / tile.length,
|
|||
|
|
textHeight: textHeight2
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (lineMeasure)
|
|||
|
|
return lineMeasure;
|
|||
|
|
let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
|
|||
|
|
dummy.className = "cm-line";
|
|||
|
|
dummy.style.width = "99999px";
|
|||
|
|
dummy.style.position = "absolute";
|
|||
|
|
dummy.textContent = "abc def ghi jkl mno pqr stu";
|
|||
|
|
this.view.observer.ignore(() => {
|
|||
|
|
this.tile.dom.appendChild(dummy);
|
|||
|
|
let rect = clientRectsFor(dummy.firstChild)[0];
|
|||
|
|
lineHeight = dummy.getBoundingClientRect().height;
|
|||
|
|
charWidth = rect && rect.width ? rect.width / 27 : 7;
|
|||
|
|
textHeight = rect && rect.height ? rect.height : lineHeight;
|
|||
|
|
dummy.remove();
|
|||
|
|
});
|
|||
|
|
return { lineHeight, charWidth, textHeight };
|
|||
|
|
}
|
|||
|
|
computeBlockGapDeco() {
|
|||
|
|
let deco = [], vs = this.view.viewState;
|
|||
|
|
for (let pos = 0, i = 0; ; i++) {
|
|||
|
|
let next = i == vs.viewports.length ? null : vs.viewports[i];
|
|||
|
|
let end = next ? next.from - 1 : this.view.state.doc.length;
|
|||
|
|
if (end > pos) {
|
|||
|
|
let height = (vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top) / this.view.scaleY;
|
|||
|
|
deco.push(Decoration.replace({
|
|||
|
|
widget: new BlockGapWidget(height),
|
|||
|
|
block: true,
|
|||
|
|
inclusive: true,
|
|||
|
|
isBlockGap: true
|
|||
|
|
}).range(pos, end));
|
|||
|
|
}
|
|||
|
|
if (!next)
|
|||
|
|
break;
|
|||
|
|
pos = next.to + 1;
|
|||
|
|
}
|
|||
|
|
return Decoration.set(deco);
|
|||
|
|
}
|
|||
|
|
updateDeco() {
|
|||
|
|
let i = 1;
|
|||
|
|
let allDeco = this.view.state.facet(decorations).map((d) => {
|
|||
|
|
let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
|
|||
|
|
return dynamic ? d(this.view) : d;
|
|||
|
|
});
|
|||
|
|
let dynamicOuter = false, outerDeco = this.view.state.facet(outerDecorations).map((d, i2) => {
|
|||
|
|
let dynamic = typeof d == "function";
|
|||
|
|
if (dynamic)
|
|||
|
|
dynamicOuter = true;
|
|||
|
|
return dynamic ? d(this.view) : d;
|
|||
|
|
});
|
|||
|
|
if (outerDeco.length) {
|
|||
|
|
this.dynamicDecorationMap[i++] = dynamicOuter;
|
|||
|
|
allDeco.push(RangeSet.join(outerDeco));
|
|||
|
|
}
|
|||
|
|
this.decorations = [
|
|||
|
|
this.editContextFormatting,
|
|||
|
|
...allDeco,
|
|||
|
|
this.computeBlockGapDeco(),
|
|||
|
|
this.view.viewState.lineGapDeco
|
|||
|
|
];
|
|||
|
|
while (i < this.decorations.length)
|
|||
|
|
this.dynamicDecorationMap[i++] = false;
|
|||
|
|
this.blockWrappers = this.view.state.facet(blockWrappers).map((v) => typeof v == "function" ? v(this.view) : v);
|
|||
|
|
}
|
|||
|
|
scrollIntoView(target) {
|
|||
|
|
var _a;
|
|||
|
|
if (target.isSnapshot) {
|
|||
|
|
let ref = this.view.viewState.lineBlockAt(target.range.head);
|
|||
|
|
this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
|
|||
|
|
this.view.scrollDOM.scrollLeft = target.xMargin;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
for (let handler of this.view.state.facet(scrollHandler)) {
|
|||
|
|
try {
|
|||
|
|
if (handler(this.view, target.range, target))
|
|||
|
|
return true;
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(this.view.state, e, "scroll handler");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let { range } = target;
|
|||
|
|
let rect = this.coordsAt(range.head, (_a = range.assoc) !== null && _a !== void 0 ? _a : range.empty ? 0 : range.head > range.anchor ? -1 : 1), other;
|
|||
|
|
if (!rect)
|
|||
|
|
return;
|
|||
|
|
if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
|
|||
|
|
rect = {
|
|||
|
|
left: Math.min(rect.left, other.left),
|
|||
|
|
top: Math.min(rect.top, other.top),
|
|||
|
|
right: Math.max(rect.right, other.right),
|
|||
|
|
bottom: Math.max(rect.bottom, other.bottom)
|
|||
|
|
};
|
|||
|
|
let margins = getScrollMargins(this.view);
|
|||
|
|
let targetRect = {
|
|||
|
|
left: rect.left - margins.left,
|
|||
|
|
top: rect.top - margins.top,
|
|||
|
|
right: rect.right + margins.right,
|
|||
|
|
bottom: rect.bottom + margins.bottom
|
|||
|
|
};
|
|||
|
|
let { offsetWidth, offsetHeight } = this.view.scrollDOM;
|
|||
|
|
scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == Direction.LTR);
|
|||
|
|
if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 && (rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height || rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
|
|||
|
|
let line = this.view.docView.lineAt(range.head, 1);
|
|||
|
|
if (line)
|
|||
|
|
line.dom.scrollIntoView({ block: "nearest" });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
lineHasWidget(pos) {
|
|||
|
|
let scan = (child) => child.isWidget() || child.children.some(scan);
|
|||
|
|
return scan(this.tile.resolveBlock(pos, 1).tile);
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
destroyDropped(this.tile);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function destroyDropped(tile, reused) {
|
|||
|
|
let r = reused === null || reused === void 0 ? void 0 : reused.get(tile);
|
|||
|
|
if (r != 1) {
|
|||
|
|
if (r == null)
|
|||
|
|
tile.destroy();
|
|||
|
|
for (let ch of tile.children)
|
|||
|
|
destroyDropped(ch, reused);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function betweenUneditable(pos) {
|
|||
|
|
return pos.node.nodeType == 1 && pos.node.firstChild && (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") && (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
|
|||
|
|
}
|
|||
|
|
function findCompositionNode(view, headPos) {
|
|||
|
|
let sel = view.observer.selectionRange;
|
|||
|
|
if (!sel.focusNode)
|
|||
|
|
return null;
|
|||
|
|
let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
|
|||
|
|
let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
|
|||
|
|
let textNode = textBefore || textAfter;
|
|||
|
|
if (textAfter && textBefore && textAfter.node != textBefore.node) {
|
|||
|
|
let tileAfter = Tile.get(textAfter.node);
|
|||
|
|
if (!tileAfter || tileAfter.isText() && tileAfter.text != textAfter.node.nodeValue) {
|
|||
|
|
textNode = textAfter;
|
|||
|
|
} else if (view.docView.lastCompositionAfterCursor) {
|
|||
|
|
let tileBefore = Tile.get(textBefore.node);
|
|||
|
|
if (!(!tileBefore || tileBefore.isText() && tileBefore.text != textBefore.node.nodeValue))
|
|||
|
|
textNode = textAfter;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
view.docView.lastCompositionAfterCursor = textNode != textBefore;
|
|||
|
|
if (!textNode)
|
|||
|
|
return null;
|
|||
|
|
let from = headPos - textNode.offset;
|
|||
|
|
return { from, to: from + textNode.node.nodeValue.length, node: textNode.node };
|
|||
|
|
}
|
|||
|
|
function findCompositionRange(view, changes, headPos) {
|
|||
|
|
let found = findCompositionNode(view, headPos);
|
|||
|
|
if (!found)
|
|||
|
|
return null;
|
|||
|
|
let { node: textNode, from, to } = found, text = textNode.nodeValue;
|
|||
|
|
if (/[\n\r]/.test(text))
|
|||
|
|
return null;
|
|||
|
|
if (view.state.doc.sliceString(found.from, found.to) != text)
|
|||
|
|
return null;
|
|||
|
|
let inv = changes.invertedDesc;
|
|||
|
|
return { range: new ChangedRange(inv.mapPos(from), inv.mapPos(to), from, to), text: textNode };
|
|||
|
|
}
|
|||
|
|
function nextToUneditable(node, offset) {
|
|||
|
|
if (node.nodeType != 1)
|
|||
|
|
return 0;
|
|||
|
|
return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 : 0) | (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 : 0);
|
|||
|
|
}
|
|||
|
|
var DecorationComparator$1 = class DecorationComparator {
|
|||
|
|
constructor() {
|
|||
|
|
this.changes = [];
|
|||
|
|
}
|
|||
|
|
compareRange(from, to) {
|
|||
|
|
addRange(from, to, this.changes);
|
|||
|
|
}
|
|||
|
|
comparePoint(from, to) {
|
|||
|
|
addRange(from, to, this.changes);
|
|||
|
|
}
|
|||
|
|
boundChange(pos) {
|
|||
|
|
addRange(pos, pos, this.changes);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function findChangedDeco(a, b, diff) {
|
|||
|
|
let comp = new DecorationComparator$1();
|
|||
|
|
RangeSet.compare(a, b, diff, comp);
|
|||
|
|
return comp.changes;
|
|||
|
|
}
|
|||
|
|
var WrapperComparator = class {
|
|||
|
|
constructor() {
|
|||
|
|
this.changes = [];
|
|||
|
|
}
|
|||
|
|
compareRange(from, to) {
|
|||
|
|
addRange(from, to, this.changes);
|
|||
|
|
}
|
|||
|
|
comparePoint() {
|
|||
|
|
}
|
|||
|
|
boundChange(pos) {
|
|||
|
|
addRange(pos, pos, this.changes);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function findChangedWrappers(a, b, diff) {
|
|||
|
|
let comp = new WrapperComparator();
|
|||
|
|
RangeSet.compare(a, b, diff, comp);
|
|||
|
|
return comp.changes;
|
|||
|
|
}
|
|||
|
|
function inUneditable(node, inside) {
|
|||
|
|
for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
|
|||
|
|
if (cur.nodeType == 1 && cur.contentEditable == "false") {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
function touchesComposition(changes, composition) {
|
|||
|
|
let touched = false;
|
|||
|
|
if (composition)
|
|||
|
|
changes.iterChangedRanges((from, to) => {
|
|||
|
|
if (from < composition.to && to > composition.from)
|
|||
|
|
touched = true;
|
|||
|
|
});
|
|||
|
|
return touched;
|
|||
|
|
}
|
|||
|
|
var BlockGapWidget = class extends WidgetType {
|
|||
|
|
constructor(height) {
|
|||
|
|
super();
|
|||
|
|
this.height = height;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
let elt = document.createElement("div");
|
|||
|
|
elt.className = "cm-gap";
|
|||
|
|
this.updateDOM(elt);
|
|||
|
|
return elt;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.height == this.height;
|
|||
|
|
}
|
|||
|
|
updateDOM(elt) {
|
|||
|
|
elt.style.height = this.height + "px";
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get editable() {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
get estimatedHeight() {
|
|||
|
|
return this.height;
|
|||
|
|
}
|
|||
|
|
ignoreEvent() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function groupAt(state, pos, bias = 1) {
|
|||
|
|
let categorize = state.charCategorizer(pos);
|
|||
|
|
let line = state.doc.lineAt(pos), linePos = pos - line.from;
|
|||
|
|
if (line.length == 0)
|
|||
|
|
return EditorSelection.cursor(pos);
|
|||
|
|
if (linePos == 0)
|
|||
|
|
bias = 1;
|
|||
|
|
else if (linePos == line.length)
|
|||
|
|
bias = -1;
|
|||
|
|
let from = linePos, to = linePos;
|
|||
|
|
if (bias < 0)
|
|||
|
|
from = findClusterBreak(line.text, linePos, false);
|
|||
|
|
else
|
|||
|
|
to = findClusterBreak(line.text, linePos);
|
|||
|
|
let cat = categorize(line.text.slice(from, to));
|
|||
|
|
while (from > 0) {
|
|||
|
|
let prev = findClusterBreak(line.text, from, false);
|
|||
|
|
if (categorize(line.text.slice(prev, from)) != cat)
|
|||
|
|
break;
|
|||
|
|
from = prev;
|
|||
|
|
}
|
|||
|
|
while (to < line.length) {
|
|||
|
|
let next = findClusterBreak(line.text, to);
|
|||
|
|
if (categorize(line.text.slice(to, next)) != cat)
|
|||
|
|
break;
|
|||
|
|
to = next;
|
|||
|
|
}
|
|||
|
|
return EditorSelection.range(from + line.from, to + line.from);
|
|||
|
|
}
|
|||
|
|
function posAtCoordsImprecise(view, contentRect, block, x, y) {
|
|||
|
|
let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
|
|||
|
|
if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
|
|||
|
|
let textHeight = view.viewState.heightOracle.textHeight;
|
|||
|
|
let line = Math.floor((y - block.top - (view.defaultLineHeight - textHeight) * 0.5) / textHeight);
|
|||
|
|
into += line * view.viewState.heightOracle.lineLength;
|
|||
|
|
}
|
|||
|
|
let content = view.state.sliceDoc(block.from, block.to);
|
|||
|
|
return block.from + findColumn(content, into, view.state.tabSize);
|
|||
|
|
}
|
|||
|
|
function blockAt(view, pos, side) {
|
|||
|
|
let line = view.lineBlockAt(pos);
|
|||
|
|
if (Array.isArray(line.type)) {
|
|||
|
|
let best;
|
|||
|
|
for (let l of line.type) {
|
|||
|
|
if (l.from > pos)
|
|||
|
|
break;
|
|||
|
|
if (l.to < pos)
|
|||
|
|
continue;
|
|||
|
|
if (l.from < pos && l.to > pos)
|
|||
|
|
return l;
|
|||
|
|
if (!best || l.type == BlockType.Text && (best.type != l.type || (side < 0 ? l.from < pos : l.to > pos)))
|
|||
|
|
best = l;
|
|||
|
|
}
|
|||
|
|
return best || line;
|
|||
|
|
}
|
|||
|
|
return line;
|
|||
|
|
}
|
|||
|
|
function moveToLineBoundary(view, start, forward, includeWrap) {
|
|||
|
|
let line = blockAt(view, start.head, start.assoc || -1);
|
|||
|
|
let coords = !includeWrap || line.type != BlockType.Text || !(view.lineWrapping || line.widgetLineBreaks) ? null : view.coordsAtPos(start.assoc < 0 && start.head > line.from ? start.head - 1 : start.head);
|
|||
|
|
if (coords) {
|
|||
|
|
let editorRect = view.dom.getBoundingClientRect();
|
|||
|
|
let direction = view.textDirectionAt(line.from);
|
|||
|
|
let pos = view.posAtCoords({
|
|||
|
|
x: forward == (direction == Direction.LTR) ? editorRect.right - 1 : editorRect.left + 1,
|
|||
|
|
y: (coords.top + coords.bottom) / 2
|
|||
|
|
});
|
|||
|
|
if (pos != null)
|
|||
|
|
return EditorSelection.cursor(pos, forward ? -1 : 1);
|
|||
|
|
}
|
|||
|
|
return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1);
|
|||
|
|
}
|
|||
|
|
function moveByChar(view, start, forward, by) {
|
|||
|
|
let line = view.state.doc.lineAt(start.head), spans = view.bidiSpans(line);
|
|||
|
|
let direction = view.textDirectionAt(line.from);
|
|||
|
|
for (let cur = start, check = null; ; ) {
|
|||
|
|
let next = moveVisually(line, spans, direction, cur, forward), char = movedOver;
|
|||
|
|
if (!next) {
|
|||
|
|
if (line.number == (forward ? view.state.doc.lines : 1))
|
|||
|
|
return cur;
|
|||
|
|
char = "\n";
|
|||
|
|
line = view.state.doc.line(line.number + (forward ? 1 : -1));
|
|||
|
|
spans = view.bidiSpans(line);
|
|||
|
|
next = view.visualLineSide(line, !forward);
|
|||
|
|
}
|
|||
|
|
if (!check) {
|
|||
|
|
if (!by)
|
|||
|
|
return next;
|
|||
|
|
check = by(char);
|
|||
|
|
} else if (!check(char)) {
|
|||
|
|
return cur;
|
|||
|
|
}
|
|||
|
|
cur = next;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function byGroup(view, pos, start) {
|
|||
|
|
let categorize = view.state.charCategorizer(pos);
|
|||
|
|
let cat = categorize(start);
|
|||
|
|
return (next) => {
|
|||
|
|
let nextCat = categorize(next);
|
|||
|
|
if (cat == CharCategory.Space)
|
|||
|
|
cat = nextCat;
|
|||
|
|
return cat == nextCat;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function moveVertically(view, start, forward, distance) {
|
|||
|
|
let startPos = start.head, dir = forward ? 1 : -1;
|
|||
|
|
if (startPos == (forward ? view.state.doc.length : 0))
|
|||
|
|
return EditorSelection.cursor(startPos, start.assoc);
|
|||
|
|
let goal = start.goalColumn, startY;
|
|||
|
|
let rect = view.contentDOM.getBoundingClientRect();
|
|||
|
|
let startCoords = view.coordsAtPos(startPos, start.assoc || ((start.empty ? forward : start.head == start.from) ? 1 : -1));
|
|||
|
|
let docTop = view.documentTop;
|
|||
|
|
if (startCoords) {
|
|||
|
|
if (goal == null)
|
|||
|
|
goal = startCoords.left - rect.left;
|
|||
|
|
startY = dir < 0 ? startCoords.top : startCoords.bottom;
|
|||
|
|
} else {
|
|||
|
|
let line = view.viewState.lineBlockAt(startPos);
|
|||
|
|
if (goal == null)
|
|||
|
|
goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
|
|||
|
|
startY = (dir < 0 ? line.top : line.bottom) + docTop;
|
|||
|
|
}
|
|||
|
|
let resolvedGoal = rect.left + goal;
|
|||
|
|
let halfText = view.viewState.heightOracle.textHeight >> 1, dist2 = distance !== null && distance !== void 0 ? distance : halfText;
|
|||
|
|
for (let scan = 0; ; scan += halfText) {
|
|||
|
|
let y = startY + (dist2 + scan) * dir;
|
|||
|
|
let pos = posAtCoords(view, { x: resolvedGoal, y }, false, dir);
|
|||
|
|
if (forward ? y > rect.bottom : y < rect.top)
|
|||
|
|
return EditorSelection.cursor(pos.pos, pos.assoc);
|
|||
|
|
let posCoords = view.coordsAtPos(pos.pos, pos.assoc), mid = posCoords ? (posCoords.top + posCoords.bottom) / 2 : 0;
|
|||
|
|
if (!posCoords || (forward ? mid > startY : mid < startY))
|
|||
|
|
return EditorSelection.cursor(pos.pos, pos.assoc, void 0, goal);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function skipAtomicRanges(atoms, pos, bias) {
|
|||
|
|
for (; ; ) {
|
|||
|
|
let moved = 0;
|
|||
|
|
for (let set of atoms) {
|
|||
|
|
set.between(pos - 1, pos + 1, (from, to, value) => {
|
|||
|
|
if (pos > from && pos < to) {
|
|||
|
|
let side = moved || bias || (pos - from < to - pos ? -1 : 1);
|
|||
|
|
pos = side < 0 ? from : to;
|
|||
|
|
moved = side;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (!moved)
|
|||
|
|
return pos;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function skipAtomsForSelection(atoms, sel) {
|
|||
|
|
let ranges = null;
|
|||
|
|
for (let i = 0; i < sel.ranges.length; i++) {
|
|||
|
|
let range = sel.ranges[i], updated = null;
|
|||
|
|
if (range.empty) {
|
|||
|
|
let pos = skipAtomicRanges(atoms, range.from, 0);
|
|||
|
|
if (pos != range.from)
|
|||
|
|
updated = EditorSelection.cursor(pos, -1);
|
|||
|
|
} else {
|
|||
|
|
let from = skipAtomicRanges(atoms, range.from, -1);
|
|||
|
|
let to = skipAtomicRanges(atoms, range.to, 1);
|
|||
|
|
if (from != range.from || to != range.to)
|
|||
|
|
updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
|
|||
|
|
}
|
|||
|
|
if (updated) {
|
|||
|
|
if (!ranges)
|
|||
|
|
ranges = sel.ranges.slice();
|
|||
|
|
ranges[i] = updated;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ranges ? EditorSelection.create(ranges, sel.mainIndex) : sel;
|
|||
|
|
}
|
|||
|
|
function skipAtoms(view, oldPos, pos) {
|
|||
|
|
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map((f) => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
|
|||
|
|
return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
|
|||
|
|
}
|
|||
|
|
var PosAssoc = class {
|
|||
|
|
constructor(pos, assoc) {
|
|||
|
|
this.pos = pos;
|
|||
|
|
this.assoc = assoc;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function posAtCoords(view, coords, precise, scanY) {
|
|||
|
|
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|||
|
|
let { x, y } = coords, yOffset = y - docTop, block;
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (yOffset < 0)
|
|||
|
|
return new PosAssoc(0, 1);
|
|||
|
|
if (yOffset > view.viewState.docHeight)
|
|||
|
|
return new PosAssoc(view.state.doc.length, -1);
|
|||
|
|
block = view.elementAtHeight(yOffset);
|
|||
|
|
if (scanY == null)
|
|||
|
|
break;
|
|||
|
|
if (block.type == BlockType.Text) {
|
|||
|
|
if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
|
|||
|
|
break;
|
|||
|
|
let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY > 0 ? -1 : 1);
|
|||
|
|
if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
let halfLine = view.viewState.heightOracle.textHeight / 2;
|
|||
|
|
yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|||
|
|
}
|
|||
|
|
if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
|
|||
|
|
if (precise)
|
|||
|
|
return null;
|
|||
|
|
if (block.type == BlockType.Text) {
|
|||
|
|
let pos = posAtCoordsImprecise(view, content, block, x, y);
|
|||
|
|
return new PosAssoc(pos, pos == block.from ? 1 : -1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (block.type != BlockType.Text)
|
|||
|
|
return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
|
|||
|
|
let line = view.docView.lineAt(block.from, 2);
|
|||
|
|
if (!line || line.length != block.length)
|
|||
|
|
line = view.docView.lineAt(block.from, -2);
|
|||
|
|
return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
|
|||
|
|
}
|
|||
|
|
var InlineCoordsScan = class {
|
|||
|
|
constructor(view, x, y, baseDir) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.x = x;
|
|||
|
|
this.y = y;
|
|||
|
|
this.baseDir = baseDir;
|
|||
|
|
this.line = null;
|
|||
|
|
this.spans = null;
|
|||
|
|
}
|
|||
|
|
bidiSpansAt(pos) {
|
|||
|
|
if (!this.line || this.line.from > pos || this.line.to < pos) {
|
|||
|
|
this.line = this.view.state.doc.lineAt(pos);
|
|||
|
|
this.spans = this.view.bidiSpans(this.line);
|
|||
|
|
}
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
baseDirAt(pos, side) {
|
|||
|
|
let { line, spans } = this.bidiSpansAt(pos);
|
|||
|
|
let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
|
|||
|
|
return level == this.baseDir;
|
|||
|
|
}
|
|||
|
|
dirAt(pos, side) {
|
|||
|
|
let { line, spans } = this.bidiSpansAt(pos);
|
|||
|
|
return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
|
|||
|
|
}
|
|||
|
|
// Used to short-circuit bidi tests for content with a uniform direction
|
|||
|
|
bidiIn(from, to) {
|
|||
|
|
let { spans, line } = this.bidiSpansAt(from);
|
|||
|
|
return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
|
|||
|
|
}
|
|||
|
|
// Scan through the rectangles for the content of a tile with inline
|
|||
|
|
// content, looking for one that overlaps the queried position
|
|||
|
|
// vertically andis
|
|||
|
|
// closest horizontally. The caller is responsible for dividing its
|
|||
|
|
// content into N pieces, and pass an array with N+1 positions
|
|||
|
|
// (including the position after the last piece). For a text tile,
|
|||
|
|
// these will be character clusters, for a composite tile, these
|
|||
|
|
// will be child tiles.
|
|||
|
|
scan(positions, getRects, recursed = false) {
|
|||
|
|
let lo = 0, hi = positions.length - 1, seen = /* @__PURE__ */ new Set();
|
|||
|
|
let bidi = this.bidiIn(positions[0], positions[hi]);
|
|||
|
|
let above, below;
|
|||
|
|
let closestI = -1, closestDx = 1e9, closestRect;
|
|||
|
|
search: while (lo < hi) {
|
|||
|
|
let dist2 = hi - lo, mid = lo + hi >> 1;
|
|||
|
|
adjust: if (seen.has(mid)) {
|
|||
|
|
let scan = lo + Math.floor(Math.random() * dist2);
|
|||
|
|
for (let i = 0; i < dist2; i++) {
|
|||
|
|
if (!seen.has(scan)) {
|
|||
|
|
mid = scan;
|
|||
|
|
break adjust;
|
|||
|
|
}
|
|||
|
|
scan++;
|
|||
|
|
if (scan == hi)
|
|||
|
|
scan = lo;
|
|||
|
|
}
|
|||
|
|
break search;
|
|||
|
|
}
|
|||
|
|
seen.add(mid);
|
|||
|
|
let rects = getRects(mid);
|
|||
|
|
if (rects)
|
|||
|
|
for (let i = 0; i < rects.length; i++) {
|
|||
|
|
let rect = rects[i], side = 0;
|
|||
|
|
if (rect.width == 0 && rects.length > 1)
|
|||
|
|
continue;
|
|||
|
|
if (rect.bottom < this.y) {
|
|||
|
|
if (!above || above.bottom < rect.bottom)
|
|||
|
|
above = rect;
|
|||
|
|
side = 1;
|
|||
|
|
} else if (rect.top > this.y) {
|
|||
|
|
if (!below || below.top > rect.top)
|
|||
|
|
below = rect;
|
|||
|
|
side = -1;
|
|||
|
|
} else {
|
|||
|
|
let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
|
|||
|
|
let dx = Math.abs(off);
|
|||
|
|
if (dx < closestDx) {
|
|||
|
|
closestI = mid;
|
|||
|
|
closestDx = dx;
|
|||
|
|
closestRect = rect;
|
|||
|
|
}
|
|||
|
|
if (off)
|
|||
|
|
side = off < 0 == (this.baseDir == Direction.LTR) ? -1 : 1;
|
|||
|
|
}
|
|||
|
|
if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
|
|||
|
|
hi = mid;
|
|||
|
|
else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
|
|||
|
|
lo = mid + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!closestRect) {
|
|||
|
|
let side = above && (!below || this.y - above.bottom < below.top - this.y) ? above : below;
|
|||
|
|
this.y = (side.top + side.bottom) / 2;
|
|||
|
|
return this.scan(positions, getRects, true);
|
|||
|
|
}
|
|||
|
|
if (closestDx && !recursed) {
|
|||
|
|
let { top: top2, bottom } = closestRect;
|
|||
|
|
if (above && above.bottom > (top2 + top2 + bottom) / 3) {
|
|||
|
|
this.y = above.bottom - 1;
|
|||
|
|
return this.scan(positions, getRects, true);
|
|||
|
|
}
|
|||
|
|
if (below && below.top < (top2 + bottom + bottom) / 3) {
|
|||
|
|
this.y = below.top + 1;
|
|||
|
|
return this.scan(positions, getRects, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
|
|||
|
|
return {
|
|||
|
|
i: closestI,
|
|||
|
|
// Test whether x is closes to the start or end of this element
|
|||
|
|
after: this.x > (closestRect.left + closestRect.right) / 2 == ltr
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
scanText(tile, offset) {
|
|||
|
|
let positions = [];
|
|||
|
|
for (let i = 0; i < tile.length; i = findClusterBreak(tile.text, i))
|
|||
|
|
positions.push(offset + i);
|
|||
|
|
positions.push(offset + tile.length);
|
|||
|
|
let scan = this.scan(positions, (i) => {
|
|||
|
|
let off = positions[i] - offset, end = positions[i + 1] - offset;
|
|||
|
|
return textRange(tile.dom, off, end).getClientRects();
|
|||
|
|
});
|
|||
|
|
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
|
|||
|
|
}
|
|||
|
|
scanTile(tile, offset) {
|
|||
|
|
if (!tile.length)
|
|||
|
|
return new PosAssoc(offset, 1);
|
|||
|
|
if (tile.children.length == 1) {
|
|||
|
|
let child2 = tile.children[0];
|
|||
|
|
if (child2.isText())
|
|||
|
|
return this.scanText(child2, offset);
|
|||
|
|
else if (child2.isComposite())
|
|||
|
|
return this.scanTile(child2, offset);
|
|||
|
|
}
|
|||
|
|
let positions = [offset];
|
|||
|
|
for (let i = 0, pos2 = offset; i < tile.children.length; i++)
|
|||
|
|
positions.push(pos2 += tile.children[i].length);
|
|||
|
|
let scan = this.scan(positions, (i) => {
|
|||
|
|
let child2 = tile.children[i];
|
|||
|
|
if (child2.flags & 48)
|
|||
|
|
return null;
|
|||
|
|
return (child2.dom.nodeType == 1 ? child2.dom : textRange(child2.dom, 0, child2.length)).getClientRects();
|
|||
|
|
});
|
|||
|
|
let child = tile.children[scan.i], pos = positions[scan.i];
|
|||
|
|
if (child.isText())
|
|||
|
|
return this.scanText(child, pos);
|
|||
|
|
if (child.isComposite())
|
|||
|
|
return this.scanTile(child, pos);
|
|||
|
|
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var LineBreakPlaceholder = "";
|
|||
|
|
var DOMReader = class {
|
|||
|
|
constructor(points, view) {
|
|||
|
|
this.points = points;
|
|||
|
|
this.view = view;
|
|||
|
|
this.text = "";
|
|||
|
|
this.lineSeparator = view.state.facet(EditorState.lineSeparator);
|
|||
|
|
}
|
|||
|
|
append(text) {
|
|||
|
|
this.text += text;
|
|||
|
|
}
|
|||
|
|
lineBreak() {
|
|||
|
|
this.text += LineBreakPlaceholder;
|
|||
|
|
}
|
|||
|
|
readRange(start, end) {
|
|||
|
|
if (!start)
|
|||
|
|
return this;
|
|||
|
|
let parent = start.parentNode;
|
|||
|
|
for (let cur = start; ; ) {
|
|||
|
|
this.findPointBefore(parent, cur);
|
|||
|
|
let oldLen = this.text.length;
|
|||
|
|
this.readNode(cur);
|
|||
|
|
let tile = Tile.get(cur), next = cur.nextSibling;
|
|||
|
|
if (next == end) {
|
|||
|
|
if ((tile === null || tile === void 0 ? void 0 : tile.breakAfter) && !next && parent != this.view.contentDOM)
|
|||
|
|
this.lineBreak();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
let nextTile = Tile.get(next);
|
|||
|
|
if ((tile && nextTile ? tile.breakAfter : (tile ? tile.breakAfter : isBlockElement(cur)) || isBlockElement(next) && (cur.nodeName != "BR" || (tile === null || tile === void 0 ? void 0 : tile.isWidget())) && this.text.length > oldLen) && !isEmptyToEnd(next, end))
|
|||
|
|
this.lineBreak();
|
|||
|
|
cur = next;
|
|||
|
|
}
|
|||
|
|
this.findPointBefore(parent, end);
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
readTextNode(node) {
|
|||
|
|
let text = node.nodeValue;
|
|||
|
|
for (let point of this.points)
|
|||
|
|
if (point.node == node)
|
|||
|
|
point.pos = this.text.length + Math.min(point.offset, text.length);
|
|||
|
|
for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g; ; ) {
|
|||
|
|
let nextBreak = -1, breakSize = 1, m;
|
|||
|
|
if (this.lineSeparator) {
|
|||
|
|
nextBreak = text.indexOf(this.lineSeparator, off);
|
|||
|
|
breakSize = this.lineSeparator.length;
|
|||
|
|
} else if (m = re.exec(text)) {
|
|||
|
|
nextBreak = m.index;
|
|||
|
|
breakSize = m[0].length;
|
|||
|
|
}
|
|||
|
|
this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
|
|||
|
|
if (nextBreak < 0)
|
|||
|
|
break;
|
|||
|
|
this.lineBreak();
|
|||
|
|
if (breakSize > 1) {
|
|||
|
|
for (let point of this.points)
|
|||
|
|
if (point.node == node && point.pos > this.text.length)
|
|||
|
|
point.pos -= breakSize - 1;
|
|||
|
|
}
|
|||
|
|
off = nextBreak + breakSize;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
readNode(node) {
|
|||
|
|
let tile = Tile.get(node);
|
|||
|
|
let fromView = tile && tile.overrideDOMText;
|
|||
|
|
if (fromView != null) {
|
|||
|
|
this.findPointInside(node, fromView.length);
|
|||
|
|
for (let i = fromView.iter(); !i.next().done; ) {
|
|||
|
|
if (i.lineBreak)
|
|||
|
|
this.lineBreak();
|
|||
|
|
else
|
|||
|
|
this.append(i.value);
|
|||
|
|
}
|
|||
|
|
} else if (node.nodeType == 3) {
|
|||
|
|
this.readTextNode(node);
|
|||
|
|
} else if (node.nodeName == "BR") {
|
|||
|
|
if (node.nextSibling)
|
|||
|
|
this.lineBreak();
|
|||
|
|
} else if (node.nodeType == 1) {
|
|||
|
|
this.readRange(node.firstChild, null);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
findPointBefore(node, next) {
|
|||
|
|
for (let point of this.points)
|
|||
|
|
if (point.node == node && node.childNodes[point.offset] == next)
|
|||
|
|
point.pos = this.text.length;
|
|||
|
|
}
|
|||
|
|
findPointInside(node, length) {
|
|||
|
|
for (let point of this.points)
|
|||
|
|
if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
|
|||
|
|
point.pos = this.text.length + (isAtEnd(node, point.node, point.offset) ? length : 0);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function isAtEnd(parent, node, offset) {
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (!node || offset < maxOffset(node))
|
|||
|
|
return false;
|
|||
|
|
if (node == parent)
|
|||
|
|
return true;
|
|||
|
|
offset = domIndex(node) + 1;
|
|||
|
|
node = node.parentNode;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function isEmptyToEnd(node, end) {
|
|||
|
|
let widgets;
|
|||
|
|
for (; ; node = node.nextSibling) {
|
|||
|
|
if (node == end || !node)
|
|||
|
|
break;
|
|||
|
|
let view = Tile.get(node);
|
|||
|
|
if (!(view === null || view === void 0 ? void 0 : view.isWidget()))
|
|||
|
|
return false;
|
|||
|
|
if (view)
|
|||
|
|
(widgets || (widgets = [])).push(view);
|
|||
|
|
}
|
|||
|
|
if (widgets)
|
|||
|
|
for (let w of widgets) {
|
|||
|
|
let override = w.overrideDOMText;
|
|||
|
|
if (override === null || override === void 0 ? void 0 : override.length)
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
var DOMPoint = class {
|
|||
|
|
constructor(node, offset) {
|
|||
|
|
this.node = node;
|
|||
|
|
this.offset = offset;
|
|||
|
|
this.pos = -1;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var DOMChange = class {
|
|||
|
|
constructor(view, start, end, typeOver) {
|
|||
|
|
this.typeOver = typeOver;
|
|||
|
|
this.bounds = null;
|
|||
|
|
this.text = "";
|
|||
|
|
this.domChanged = start > -1;
|
|||
|
|
let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView, curSel = view.state.selection;
|
|||
|
|
if (view.state.readOnly && start > -1) {
|
|||
|
|
this.newSel = null;
|
|||
|
|
} else if (start > -1 && (this.bounds = domBoundsAround(view.docView.tile, start, end, 0))) {
|
|||
|
|
let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
|
|||
|
|
let reader = new DOMReader(selPoints, view);
|
|||
|
|
reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
|
|||
|
|
this.text = reader.text;
|
|||
|
|
this.newSel = selectionFromPoints(selPoints, this.bounds.from);
|
|||
|
|
} else {
|
|||
|
|
let domSel = view.observer.selectionRange;
|
|||
|
|
let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset || !contains(view.contentDOM, domSel.focusNode) ? curSel.main.head : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
|
|||
|
|
let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset || !contains(view.contentDOM, domSel.anchorNode) ? curSel.main.anchor : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
|
|||
|
|
let vp = view.viewport;
|
|||
|
|
if ((browser.ios || browser.chrome) && curSel.main.empty && head != anchor && (vp.from > 0 || vp.to < view.state.doc.length)) {
|
|||
|
|
let from = Math.min(head, anchor), to = Math.max(head, anchor);
|
|||
|
|
let offFrom = vp.from - from, offTo = vp.to - to;
|
|||
|
|
if ((offFrom == 0 || offFrom == 1 || from == 0) && (offTo == 0 || offTo == -1 || to == view.state.doc.length)) {
|
|||
|
|
head = 0;
|
|||
|
|
anchor = view.state.doc.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (view.inputState.composing > -1 && curSel.ranges.length > 1) {
|
|||
|
|
this.newSel = curSel.replaceRange(EditorSelection.range(anchor, head));
|
|||
|
|
} else if (view.lineWrapping && anchor == head && !(curSel.main.empty && curSel.main.head == head) && view.inputState.lastTouchTime > Date.now() - 100) {
|
|||
|
|
let before = view.coordsAtPos(head, -1), assoc = 0;
|
|||
|
|
if (before)
|
|||
|
|
assoc = view.inputState.lastTouchY <= before.bottom ? -1 : 1;
|
|||
|
|
this.newSel = EditorSelection.create([EditorSelection.cursor(head, assoc)]);
|
|||
|
|
} else {
|
|||
|
|
this.newSel = EditorSelection.single(anchor, head);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function domBoundsAround(tile, from, to, offset) {
|
|||
|
|
if (tile.isComposite()) {
|
|||
|
|
let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
|
|||
|
|
for (let i = 0, pos = offset, prevEnd = offset; i < tile.children.length; i++) {
|
|||
|
|
let child = tile.children[i], end = pos + child.length;
|
|||
|
|
if (pos < from && end > to)
|
|||
|
|
return domBoundsAround(child, from, to, pos);
|
|||
|
|
if (end >= from && fromI == -1) {
|
|||
|
|
fromI = i;
|
|||
|
|
fromStart = pos;
|
|||
|
|
}
|
|||
|
|
if (pos > to && child.dom.parentNode == tile.dom) {
|
|||
|
|
toI = i;
|
|||
|
|
toEnd = prevEnd;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
prevEnd = end;
|
|||
|
|
pos = end + child.breakAfter;
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
from: fromStart,
|
|||
|
|
to: toEnd < 0 ? offset + tile.length : toEnd,
|
|||
|
|
startDOM: (fromI ? tile.children[fromI - 1].dom.nextSibling : null) || tile.dom.firstChild,
|
|||
|
|
endDOM: toI < tile.children.length && toI >= 0 ? tile.children[toI].dom : null
|
|||
|
|
};
|
|||
|
|
} else if (tile.isText()) {
|
|||
|
|
return { from: offset, to: offset + tile.length, startDOM: tile.dom, endDOM: tile.dom.nextSibling };
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function applyDOMChange(view, domChange) {
|
|||
|
|
let change;
|
|||
|
|
let { newSel } = domChange, { state } = view, sel = state.selection.main;
|
|||
|
|
let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
|
|||
|
|
if (domChange.bounds) {
|
|||
|
|
let { from, to } = domChange.bounds;
|
|||
|
|
let preferredPos = sel.from, preferredSide = null;
|
|||
|
|
if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
|
|||
|
|
preferredPos = sel.to;
|
|||
|
|
preferredSide = "end";
|
|||
|
|
}
|
|||
|
|
let cmp = state.doc.sliceString(from, to, LineBreakPlaceholder), selEnd, diff;
|
|||
|
|
if (!sel.empty && sel.from >= from && sel.to <= to && (domChange.typeOver || cmp != domChange.text) && cmp.slice(0, sel.from - from) == domChange.text.slice(0, sel.from - from) && cmp.slice(sel.to - from) == domChange.text.slice(selEnd = domChange.text.length - (cmp.length - (sel.to - from)))) {
|
|||
|
|
change = {
|
|||
|
|
from: sel.from,
|
|||
|
|
to: sel.to,
|
|||
|
|
insert: Text.of(domChange.text.slice(sel.from - from, selEnd).split(LineBreakPlaceholder))
|
|||
|
|
};
|
|||
|
|
} else if (diff = findDiff(cmp, domChange.text, preferredPos - from, preferredSide)) {
|
|||
|
|
if (browser.chrome && lastKey == 13 && diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|||
|
|
diff.toB--;
|
|||
|
|
change = {
|
|||
|
|
from: from + diff.from,
|
|||
|
|
to: from + diff.toA,
|
|||
|
|
insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder))
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
} else if (newSel && (!view.hasFocus && state.facet(editable) || sameSelPos(newSel, sel))) {
|
|||
|
|
newSel = null;
|
|||
|
|
}
|
|||
|
|
if (!change && !newSel)
|
|||
|
|
return false;
|
|||
|
|
if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 && /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
|
|||
|
|
if (newSel && change.insert.length == 2)
|
|||
|
|
newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|||
|
|
change = { from: change.from, to: change.to, insert: Text.of([change.insert.toString().replace(".", " ")]) };
|
|||
|
|
} else if (state.doc.lineAt(sel.from).to < sel.to && view.docView.lineHasWidget(sel.to) && view.inputState.insertingTextAt > Date.now() - 50) {
|
|||
|
|
change = {
|
|||
|
|
from: sel.from,
|
|||
|
|
to: sel.to,
|
|||
|
|
insert: state.toText(view.inputState.insertingText)
|
|||
|
|
};
|
|||
|
|
} else if (browser.chrome && change && change.from == change.to && change.from == sel.head && change.insert.toString() == "\n " && view.lineWrapping) {
|
|||
|
|
if (newSel)
|
|||
|
|
newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|||
|
|
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|||
|
|
}
|
|||
|
|
if (change) {
|
|||
|
|
return applyDOMChangeInner(view, change, newSel, lastKey);
|
|||
|
|
} else if (newSel && !sameSelPos(newSel, sel)) {
|
|||
|
|
let scrollIntoView2 = false, userEvent = "select";
|
|||
|
|
if (view.inputState.lastSelectionTime > Date.now() - 50) {
|
|||
|
|
if (view.inputState.lastSelectionOrigin == "select")
|
|||
|
|
scrollIntoView2 = true;
|
|||
|
|
userEvent = view.inputState.lastSelectionOrigin;
|
|||
|
|
if (userEvent == "select.pointer")
|
|||
|
|
newSel = skipAtomsForSelection(state.facet(atomicRanges).map((f) => f(view)), newSel);
|
|||
|
|
}
|
|||
|
|
view.dispatch({ selection: newSel, scrollIntoView: scrollIntoView2, userEvent });
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|||
|
|
if (browser.ios && view.inputState.flushIOSKey(change))
|
|||
|
|
return true;
|
|||
|
|
let sel = view.state.selection.main;
|
|||
|
|
if (browser.android && (change.to == sel.to && // GBoard will sometimes remove a space it just inserted
|
|||
|
|
// after a completion when you press enter
|
|||
|
|
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") && change.insert.length == 1 && change.insert.lines == 2 && dispatchKey(view.contentDOM, "Enter", 13) || (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 || lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) && dispatchKey(view.contentDOM, "Backspace", 8) || change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 && dispatchKey(view.contentDOM, "Delete", 46)))
|
|||
|
|
return true;
|
|||
|
|
let text = change.insert.toString();
|
|||
|
|
if (view.inputState.composing >= 0)
|
|||
|
|
view.inputState.composing++;
|
|||
|
|
let defaultTr;
|
|||
|
|
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|||
|
|
if (!view.state.facet(inputHandler).some((h) => h(view, change.from, change.to, text, defaultInsert)))
|
|||
|
|
view.dispatch(defaultInsert());
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
function applyDefaultInsert(view, change, newSel) {
|
|||
|
|
let tr, startState = view.state, sel = startState.selection.main, inAtomic = -1;
|
|||
|
|
if (change.from == change.to && change.from < sel.from || change.from > sel.to) {
|
|||
|
|
let side = change.from < sel.from ? -1 : 1, pos = side < 0 ? sel.from : sel.to;
|
|||
|
|
let moved = skipAtomicRanges(startState.facet(atomicRanges).map((f) => f(view)), pos, side);
|
|||
|
|
if (change.from == moved)
|
|||
|
|
inAtomic = moved;
|
|||
|
|
}
|
|||
|
|
if (inAtomic > -1) {
|
|||
|
|
tr = {
|
|||
|
|
changes: change,
|
|||
|
|
selection: EditorSelection.cursor(change.from + change.insert.length, -1)
|
|||
|
|
};
|
|||
|
|
} else if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 && (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) && view.inputState.composing < 0) {
|
|||
|
|
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|||
|
|
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|||
|
|
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, void 0, view.state.lineBreak) + after));
|
|||
|
|
} else {
|
|||
|
|
let changes = startState.changes(change);
|
|||
|
|
let mainSel = newSel && newSel.main.to <= changes.newLength ? newSel.main : void 0;
|
|||
|
|
if (startState.selection.ranges.length > 1 && (view.inputState.composing >= 0 || view.inputState.compositionPendingChange) && change.to <= sel.to + 10 && change.to >= sel.to - 10) {
|
|||
|
|
let replaced = view.state.sliceDoc(change.from, change.to);
|
|||
|
|
let compositionRange, composition = newSel && findCompositionNode(view, newSel.main.head);
|
|||
|
|
if (composition) {
|
|||
|
|
let dLen = change.insert.length - (change.to - change.from);
|
|||
|
|
compositionRange = { from: composition.from, to: composition.to - dLen };
|
|||
|
|
} else {
|
|||
|
|
compositionRange = view.state.doc.lineAt(sel.head);
|
|||
|
|
}
|
|||
|
|
let offset = sel.to - change.to;
|
|||
|
|
tr = startState.changeByRange((range) => {
|
|||
|
|
if (range.from == sel.from && range.to == sel.to)
|
|||
|
|
return { changes, range: mainSel || range.map(changes) };
|
|||
|
|
let to = range.to - offset, from = to - replaced.length;
|
|||
|
|
if (view.state.sliceDoc(from, to) != replaced || // Unfortunately, there's no way to make multiple
|
|||
|
|
// changes in the same node work without aborting
|
|||
|
|
// composition, so cursors in the composition range are
|
|||
|
|
// ignored.
|
|||
|
|
to >= compositionRange.from && from <= compositionRange.to)
|
|||
|
|
return { range };
|
|||
|
|
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|||
|
|
return {
|
|||
|
|
changes: rangeChanges,
|
|||
|
|
range: !mainSel ? range.map(rangeChanges) : EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
tr = {
|
|||
|
|
changes,
|
|||
|
|
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let userEvent = "input.type";
|
|||
|
|
if (view.composing || view.inputState.compositionPendingChange && view.inputState.compositionEndedAt > Date.now() - 50) {
|
|||
|
|
view.inputState.compositionPendingChange = false;
|
|||
|
|
userEvent += ".compose";
|
|||
|
|
if (view.inputState.compositionFirstChange) {
|
|||
|
|
userEvent += ".start";
|
|||
|
|
view.inputState.compositionFirstChange = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return startState.update(tr, { userEvent, scrollIntoView: true });
|
|||
|
|
}
|
|||
|
|
function findDiff(a, b, preferredPos, preferredSide) {
|
|||
|
|
let minLen = Math.min(a.length, b.length);
|
|||
|
|
let from = 0;
|
|||
|
|
while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
|
|||
|
|
from++;
|
|||
|
|
if (from == minLen && a.length == b.length)
|
|||
|
|
return null;
|
|||
|
|
let toA = a.length, toB = b.length;
|
|||
|
|
while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
|
|||
|
|
toA--;
|
|||
|
|
toB--;
|
|||
|
|
}
|
|||
|
|
if (preferredSide == "end") {
|
|||
|
|
let adjust = Math.max(0, from - Math.min(toA, toB));
|
|||
|
|
preferredPos -= toA + adjust - from;
|
|||
|
|
}
|
|||
|
|
if (toA < from && a.length < b.length) {
|
|||
|
|
let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
|
|||
|
|
from -= move;
|
|||
|
|
toB = from + (toB - toA);
|
|||
|
|
toA = from;
|
|||
|
|
} else if (toB < from) {
|
|||
|
|
let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
|
|||
|
|
from -= move;
|
|||
|
|
toA = from + (toA - toB);
|
|||
|
|
toB = from;
|
|||
|
|
}
|
|||
|
|
return { from, toA, toB };
|
|||
|
|
}
|
|||
|
|
function selectionPoints(view) {
|
|||
|
|
let result = [];
|
|||
|
|
if (view.root.activeElement != view.contentDOM)
|
|||
|
|
return result;
|
|||
|
|
let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
|
|||
|
|
if (anchorNode) {
|
|||
|
|
result.push(new DOMPoint(anchorNode, anchorOffset));
|
|||
|
|
if (focusNode != anchorNode || focusOffset != anchorOffset)
|
|||
|
|
result.push(new DOMPoint(focusNode, focusOffset));
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
function selectionFromPoints(points, base2) {
|
|||
|
|
if (points.length == 0)
|
|||
|
|
return null;
|
|||
|
|
let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
|
|||
|
|
return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base2, head + base2) : null;
|
|||
|
|
}
|
|||
|
|
function sameSelPos(selection, range) {
|
|||
|
|
return range.head == selection.main.head && range.anchor == selection.main.anchor;
|
|||
|
|
}
|
|||
|
|
var InputState = class {
|
|||
|
|
setSelectionOrigin(origin) {
|
|||
|
|
this.lastSelectionOrigin = origin;
|
|||
|
|
this.lastSelectionTime = Date.now();
|
|||
|
|
}
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.lastKeyCode = 0;
|
|||
|
|
this.lastKeyTime = 0;
|
|||
|
|
this.lastTouchTime = 0;
|
|||
|
|
this.lastTouchX = 0;
|
|||
|
|
this.lastTouchY = 0;
|
|||
|
|
this.lastFocusTime = 0;
|
|||
|
|
this.lastScrollTop = 0;
|
|||
|
|
this.lastScrollLeft = 0;
|
|||
|
|
this.lastWheelEvent = 0;
|
|||
|
|
this.pendingIOSKey = void 0;
|
|||
|
|
this.tabFocusMode = -1;
|
|||
|
|
this.lastSelectionOrigin = null;
|
|||
|
|
this.lastSelectionTime = 0;
|
|||
|
|
this.lastContextMenu = 0;
|
|||
|
|
this.scrollHandlers = [];
|
|||
|
|
this.handlers = /* @__PURE__ */ Object.create(null);
|
|||
|
|
this.composing = -1;
|
|||
|
|
this.compositionFirstChange = null;
|
|||
|
|
this.compositionEndedAt = 0;
|
|||
|
|
this.compositionPendingKey = false;
|
|||
|
|
this.compositionPendingChange = false;
|
|||
|
|
this.insertingText = "";
|
|||
|
|
this.insertingTextAt = 0;
|
|||
|
|
this.mouseSelection = null;
|
|||
|
|
this.draggedContent = null;
|
|||
|
|
this.handleEvent = this.handleEvent.bind(this);
|
|||
|
|
this.notifiedFocused = view.hasFocus;
|
|||
|
|
if (browser.safari)
|
|||
|
|
view.contentDOM.addEventListener("input", () => null);
|
|||
|
|
if (browser.gecko)
|
|||
|
|
firefoxCopyCutHack(view.contentDOM.ownerDocument);
|
|||
|
|
}
|
|||
|
|
handleEvent(event) {
|
|||
|
|
if (!eventBelongsToEditor(this.view, event) || this.ignoreDuringComposition(event))
|
|||
|
|
return;
|
|||
|
|
if (event.type == "keydown" && this.keydown(event))
|
|||
|
|
return;
|
|||
|
|
if (this.view.updateState != 0)
|
|||
|
|
Promise.resolve().then(() => this.runHandlers(event.type, event));
|
|||
|
|
else
|
|||
|
|
this.runHandlers(event.type, event);
|
|||
|
|
}
|
|||
|
|
runHandlers(type, event) {
|
|||
|
|
let handlers2 = this.handlers[type];
|
|||
|
|
if (handlers2) {
|
|||
|
|
for (let observer of handlers2.observers)
|
|||
|
|
observer(this.view, event);
|
|||
|
|
for (let handler of handlers2.handlers) {
|
|||
|
|
if (event.defaultPrevented)
|
|||
|
|
break;
|
|||
|
|
if (handler(this.view, event)) {
|
|||
|
|
event.preventDefault();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
ensureHandlers(plugins) {
|
|||
|
|
let handlers2 = computeHandlers(plugins), prev = this.handlers, dom = this.view.contentDOM;
|
|||
|
|
for (let type in handlers2)
|
|||
|
|
if (type != "scroll") {
|
|||
|
|
let passive = !handlers2[type].handlers.length;
|
|||
|
|
let exists = prev[type];
|
|||
|
|
if (exists && passive != !exists.handlers.length) {
|
|||
|
|
dom.removeEventListener(type, this.handleEvent);
|
|||
|
|
exists = null;
|
|||
|
|
}
|
|||
|
|
if (!exists)
|
|||
|
|
dom.addEventListener(type, this.handleEvent, { passive });
|
|||
|
|
}
|
|||
|
|
for (let type in prev)
|
|||
|
|
if (type != "scroll" && !handlers2[type])
|
|||
|
|
dom.removeEventListener(type, this.handleEvent);
|
|||
|
|
this.handlers = handlers2;
|
|||
|
|
}
|
|||
|
|
keydown(event) {
|
|||
|
|
this.lastKeyCode = event.keyCode;
|
|||
|
|
this.lastKeyTime = Date.now();
|
|||
|
|
if (event.keyCode == 9 && this.tabFocusMode > -1 && (!this.tabFocusMode || Date.now() <= this.tabFocusMode))
|
|||
|
|
return true;
|
|||
|
|
if (this.tabFocusMode > 0 && event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
|
|||
|
|
this.tabFocusMode = -1;
|
|||
|
|
if (browser.android && browser.chrome && !event.synthetic && (event.keyCode == 13 || event.keyCode == 8)) {
|
|||
|
|
this.view.observer.delayAndroidKey(event.key, event.keyCode);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
let pending;
|
|||
|
|
if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey && !event.shiftKey && ((pending = PendingKeys.find((key) => key.keyCode == event.keyCode)) && !event.ctrlKey || EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey)) {
|
|||
|
|
this.pendingIOSKey = pending || event;
|
|||
|
|
setTimeout(() => this.flushIOSKey(), 250);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if (event.keyCode != 229)
|
|||
|
|
this.view.observer.forceFlush();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
flushIOSKey(change) {
|
|||
|
|
let key = this.pendingIOSKey;
|
|||
|
|
if (!key)
|
|||
|
|
return false;
|
|||
|
|
if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
|
|||
|
|
return false;
|
|||
|
|
this.pendingIOSKey = void 0;
|
|||
|
|
return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : void 0);
|
|||
|
|
}
|
|||
|
|
ignoreDuringComposition(event) {
|
|||
|
|
if (!/^key/.test(event.type) || event.synthetic)
|
|||
|
|
return false;
|
|||
|
|
if (this.composing > 0)
|
|||
|
|
return true;
|
|||
|
|
if (browser.safari && !browser.ios && this.compositionPendingKey && Date.now() - this.compositionEndedAt < 100) {
|
|||
|
|
this.compositionPendingKey = false;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
startMouseSelection(mouseSelection) {
|
|||
|
|
if (this.mouseSelection)
|
|||
|
|
this.mouseSelection.destroy();
|
|||
|
|
this.mouseSelection = mouseSelection;
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
this.view.observer.update(update);
|
|||
|
|
if (this.mouseSelection)
|
|||
|
|
this.mouseSelection.update(update);
|
|||
|
|
if (this.draggedContent && update.docChanged)
|
|||
|
|
this.draggedContent = this.draggedContent.map(update.changes);
|
|||
|
|
if (update.transactions.length)
|
|||
|
|
this.lastKeyCode = this.lastSelectionTime = 0;
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
if (this.mouseSelection)
|
|||
|
|
this.mouseSelection.destroy();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function bindHandler(plugin2, handler) {
|
|||
|
|
return (view, event) => {
|
|||
|
|
try {
|
|||
|
|
return handler.call(plugin2, event, view);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(view.state, e);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function computeHandlers(plugins) {
|
|||
|
|
let result = /* @__PURE__ */ Object.create(null);
|
|||
|
|
function record(type) {
|
|||
|
|
return result[type] || (result[type] = { observers: [], handlers: [] });
|
|||
|
|
}
|
|||
|
|
for (let plugin2 of plugins) {
|
|||
|
|
let spec = plugin2.spec, handlers2 = spec && spec.plugin.domEventHandlers, observers2 = spec && spec.plugin.domEventObservers;
|
|||
|
|
if (handlers2)
|
|||
|
|
for (let type in handlers2) {
|
|||
|
|
let f = handlers2[type];
|
|||
|
|
if (f)
|
|||
|
|
record(type).handlers.push(bindHandler(plugin2.value, f));
|
|||
|
|
}
|
|||
|
|
if (observers2)
|
|||
|
|
for (let type in observers2) {
|
|||
|
|
let f = observers2[type];
|
|||
|
|
if (f)
|
|||
|
|
record(type).observers.push(bindHandler(plugin2.value, f));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let type in handlers)
|
|||
|
|
record(type).handlers.push(handlers[type]);
|
|||
|
|
for (let type in observers)
|
|||
|
|
record(type).observers.push(observers[type]);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
var PendingKeys = [
|
|||
|
|
{ key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
|
|||
|
|
{ key: "Enter", keyCode: 13, inputType: "insertParagraph" },
|
|||
|
|
{ key: "Enter", keyCode: 13, inputType: "insertLineBreak" },
|
|||
|
|
{ key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
|
|||
|
|
];
|
|||
|
|
var EmacsyPendingKeys = "dthko";
|
|||
|
|
var modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|||
|
|
var dragScrollMargin = 6;
|
|||
|
|
function dragScrollSpeed(dist2) {
|
|||
|
|
return Math.max(0, dist2) * 0.7 + 8;
|
|||
|
|
}
|
|||
|
|
function dist(a, b) {
|
|||
|
|
return Math.max(Math.abs(a.clientX - b.clientX), Math.abs(a.clientY - b.clientY));
|
|||
|
|
}
|
|||
|
|
var MouseSelection = class {
|
|||
|
|
constructor(view, startEvent, style, mustSelect) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.startEvent = startEvent;
|
|||
|
|
this.style = style;
|
|||
|
|
this.mustSelect = mustSelect;
|
|||
|
|
this.scrollSpeed = { x: 0, y: 0 };
|
|||
|
|
this.scrolling = -1;
|
|||
|
|
this.lastEvent = startEvent;
|
|||
|
|
this.scrollParents = scrollableParents(view.contentDOM);
|
|||
|
|
this.atoms = view.state.facet(atomicRanges).map((f) => f(view));
|
|||
|
|
let doc2 = view.contentDOM.ownerDocument;
|
|||
|
|
doc2.addEventListener("mousemove", this.move = this.move.bind(this));
|
|||
|
|
doc2.addEventListener("mouseup", this.up = this.up.bind(this));
|
|||
|
|
this.extend = startEvent.shiftKey;
|
|||
|
|
this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
|
|||
|
|
this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
|
|||
|
|
}
|
|||
|
|
start(event) {
|
|||
|
|
if (this.dragging === false)
|
|||
|
|
this.select(event);
|
|||
|
|
}
|
|||
|
|
move(event) {
|
|||
|
|
if (event.buttons == 0)
|
|||
|
|
return this.destroy();
|
|||
|
|
if (this.dragging || this.dragging == null && dist(this.startEvent, event) < 10)
|
|||
|
|
return;
|
|||
|
|
this.select(this.lastEvent = event);
|
|||
|
|
let sx = 0, sy = 0;
|
|||
|
|
let left = 0, top2 = 0, right = this.view.win.innerWidth, bottom = this.view.win.innerHeight;
|
|||
|
|
if (this.scrollParents.x)
|
|||
|
|
({ left, right } = this.scrollParents.x.getBoundingClientRect());
|
|||
|
|
if (this.scrollParents.y)
|
|||
|
|
({ top: top2, bottom } = this.scrollParents.y.getBoundingClientRect());
|
|||
|
|
let margins = getScrollMargins(this.view);
|
|||
|
|
if (event.clientX - margins.left <= left + dragScrollMargin)
|
|||
|
|
sx = -dragScrollSpeed(left - event.clientX);
|
|||
|
|
else if (event.clientX + margins.right >= right - dragScrollMargin)
|
|||
|
|
sx = dragScrollSpeed(event.clientX - right);
|
|||
|
|
if (event.clientY - margins.top <= top2 + dragScrollMargin)
|
|||
|
|
sy = -dragScrollSpeed(top2 - event.clientY);
|
|||
|
|
else if (event.clientY + margins.bottom >= bottom - dragScrollMargin)
|
|||
|
|
sy = dragScrollSpeed(event.clientY - bottom);
|
|||
|
|
this.setScrollSpeed(sx, sy);
|
|||
|
|
}
|
|||
|
|
up(event) {
|
|||
|
|
if (this.dragging == null)
|
|||
|
|
this.select(this.lastEvent);
|
|||
|
|
if (!this.dragging)
|
|||
|
|
event.preventDefault();
|
|||
|
|
this.destroy();
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
this.setScrollSpeed(0, 0);
|
|||
|
|
let doc2 = this.view.contentDOM.ownerDocument;
|
|||
|
|
doc2.removeEventListener("mousemove", this.move);
|
|||
|
|
doc2.removeEventListener("mouseup", this.up);
|
|||
|
|
this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
|
|||
|
|
}
|
|||
|
|
setScrollSpeed(sx, sy) {
|
|||
|
|
this.scrollSpeed = { x: sx, y: sy };
|
|||
|
|
if (sx || sy) {
|
|||
|
|
if (this.scrolling < 0)
|
|||
|
|
this.scrolling = setInterval(() => this.scroll(), 50);
|
|||
|
|
} else if (this.scrolling > -1) {
|
|||
|
|
clearInterval(this.scrolling);
|
|||
|
|
this.scrolling = -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
scroll() {
|
|||
|
|
let { x, y } = this.scrollSpeed;
|
|||
|
|
if (x && this.scrollParents.x) {
|
|||
|
|
this.scrollParents.x.scrollLeft += x;
|
|||
|
|
x = 0;
|
|||
|
|
}
|
|||
|
|
if (y && this.scrollParents.y) {
|
|||
|
|
this.scrollParents.y.scrollTop += y;
|
|||
|
|
y = 0;
|
|||
|
|
}
|
|||
|
|
if (x || y)
|
|||
|
|
this.view.win.scrollBy(x, y);
|
|||
|
|
if (this.dragging === false)
|
|||
|
|
this.select(this.lastEvent);
|
|||
|
|
}
|
|||
|
|
select(event) {
|
|||
|
|
let { view } = this, selection = skipAtomsForSelection(this.atoms, this.style.get(event, this.extend, this.multiple));
|
|||
|
|
if (this.mustSelect || !selection.eq(view.state.selection, this.dragging === false))
|
|||
|
|
this.view.dispatch({
|
|||
|
|
selection,
|
|||
|
|
userEvent: "select.pointer"
|
|||
|
|
});
|
|||
|
|
this.mustSelect = false;
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (update.transactions.some((tr) => tr.isUserEvent("input.type")))
|
|||
|
|
this.destroy();
|
|||
|
|
else if (this.style.update(update))
|
|||
|
|
setTimeout(() => this.select(this.lastEvent), 20);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function addsSelectionRange(view, event) {
|
|||
|
|
let facet = view.state.facet(clickAddsSelectionRange);
|
|||
|
|
return facet.length ? facet[0](event) : browser.mac ? event.metaKey : event.ctrlKey;
|
|||
|
|
}
|
|||
|
|
function dragMovesSelection(view, event) {
|
|||
|
|
let facet = view.state.facet(dragMovesSelection$1);
|
|||
|
|
return facet.length ? facet[0](event) : browser.mac ? !event.altKey : !event.ctrlKey;
|
|||
|
|
}
|
|||
|
|
function isInPrimarySelection(view, event) {
|
|||
|
|
let { main } = view.state.selection;
|
|||
|
|
if (main.empty)
|
|||
|
|
return false;
|
|||
|
|
let sel = getSelection(view.root);
|
|||
|
|
if (!sel || sel.rangeCount == 0)
|
|||
|
|
return true;
|
|||
|
|
let rects = sel.getRangeAt(0).getClientRects();
|
|||
|
|
for (let i = 0; i < rects.length; i++) {
|
|||
|
|
let rect = rects[i];
|
|||
|
|
if (rect.left <= event.clientX && rect.right >= event.clientX && rect.top <= event.clientY && rect.bottom >= event.clientY)
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
function eventBelongsToEditor(view, event) {
|
|||
|
|
if (!event.bubbles)
|
|||
|
|
return true;
|
|||
|
|
if (event.defaultPrevented)
|
|||
|
|
return false;
|
|||
|
|
for (let node = event.target, tile; node != view.contentDOM; node = node.parentNode)
|
|||
|
|
if (!node || node.nodeType == 11 || (tile = Tile.get(node)) && tile.isWidget() && !tile.isHidden && tile.widget.ignoreEvent(event))
|
|||
|
|
return false;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
var handlers = /* @__PURE__ */ Object.create(null);
|
|||
|
|
var observers = /* @__PURE__ */ Object.create(null);
|
|||
|
|
var brokenClipboardAPI = browser.ie && browser.ie_version < 15 || browser.ios && browser.webkit_version < 604;
|
|||
|
|
function capturePaste(view) {
|
|||
|
|
let parent = view.dom.parentNode;
|
|||
|
|
if (!parent)
|
|||
|
|
return;
|
|||
|
|
let target = parent.appendChild(document.createElement("textarea"));
|
|||
|
|
target.style.cssText = "position: fixed; left: -10000px; top: 10px";
|
|||
|
|
target.focus();
|
|||
|
|
setTimeout(() => {
|
|||
|
|
view.focus();
|
|||
|
|
target.remove();
|
|||
|
|
doPaste(view, target.value);
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
function textFilter(state, facet, text) {
|
|||
|
|
for (let filter of state.facet(facet))
|
|||
|
|
text = filter(text, state);
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
function doPaste(view, input) {
|
|||
|
|
input = textFilter(view.state, clipboardInputFilter, input);
|
|||
|
|
let { state } = view, changes, i = 1, text = state.toText(input);
|
|||
|
|
let byLine = text.lines == state.selection.ranges.length;
|
|||
|
|
let linewise = lastLinewiseCopy != null && state.selection.ranges.every((r) => r.empty) && lastLinewiseCopy == text.toString();
|
|||
|
|
if (linewise) {
|
|||
|
|
let lastLine = -1;
|
|||
|
|
changes = state.changeByRange((range) => {
|
|||
|
|
let line = state.doc.lineAt(range.from);
|
|||
|
|
if (line.from == lastLine)
|
|||
|
|
return { range };
|
|||
|
|
lastLine = line.from;
|
|||
|
|
let insert = state.toText((byLine ? text.line(i++).text : input) + state.lineBreak);
|
|||
|
|
return {
|
|||
|
|
changes: { from: line.from, insert },
|
|||
|
|
range: EditorSelection.cursor(range.from + insert.length)
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
} else if (byLine) {
|
|||
|
|
changes = state.changeByRange((range) => {
|
|||
|
|
let line = text.line(i++);
|
|||
|
|
return {
|
|||
|
|
changes: { from: range.from, to: range.to, insert: line.text },
|
|||
|
|
range: EditorSelection.cursor(range.from + line.length)
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
changes = state.replaceSelection(text);
|
|||
|
|
}
|
|||
|
|
view.dispatch(changes, {
|
|||
|
|
userEvent: "input.paste",
|
|||
|
|
scrollIntoView: true
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
observers.scroll = (view) => {
|
|||
|
|
view.inputState.lastScrollTop = view.scrollDOM.scrollTop;
|
|||
|
|
view.inputState.lastScrollLeft = view.scrollDOM.scrollLeft;
|
|||
|
|
};
|
|||
|
|
observers.wheel = observers.mousewheel = (view) => {
|
|||
|
|
view.inputState.lastWheelEvent = Date.now();
|
|||
|
|
};
|
|||
|
|
handlers.keydown = (view, event) => {
|
|||
|
|
view.inputState.setSelectionOrigin("select");
|
|||
|
|
if (event.keyCode == 27 && view.inputState.tabFocusMode != 0)
|
|||
|
|
view.inputState.tabFocusMode = Date.now() + 2e3;
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
observers.touchstart = (view, e) => {
|
|||
|
|
let iState = view.inputState, touch = e.targetTouches[0];
|
|||
|
|
iState.lastTouchTime = Date.now();
|
|||
|
|
if (touch) {
|
|||
|
|
iState.lastTouchX = touch.clientX;
|
|||
|
|
iState.lastTouchY = touch.clientY;
|
|||
|
|
}
|
|||
|
|
iState.setSelectionOrigin("select.pointer");
|
|||
|
|
};
|
|||
|
|
observers.touchmove = (view) => {
|
|||
|
|
view.inputState.setSelectionOrigin("select.pointer");
|
|||
|
|
};
|
|||
|
|
handlers.mousedown = (view, event) => {
|
|||
|
|
view.observer.flush();
|
|||
|
|
if (view.inputState.lastTouchTime > Date.now() - 2e3)
|
|||
|
|
return false;
|
|||
|
|
let style = null;
|
|||
|
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|||
|
|
style = makeStyle(view, event);
|
|||
|
|
if (style)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
if (!style && event.button == 0)
|
|||
|
|
style = basicMouseSelection(view, event);
|
|||
|
|
if (style) {
|
|||
|
|
let mustFocus = !view.hasFocus;
|
|||
|
|
view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
|
|||
|
|
if (mustFocus)
|
|||
|
|
view.observer.ignore(() => {
|
|||
|
|
focusPreventScroll(view.contentDOM);
|
|||
|
|
let active = view.root.activeElement;
|
|||
|
|
if (active && !active.contains(view.contentDOM))
|
|||
|
|
active.blur();
|
|||
|
|
});
|
|||
|
|
let mouseSel = view.inputState.mouseSelection;
|
|||
|
|
if (mouseSel) {
|
|||
|
|
mouseSel.start(event);
|
|||
|
|
return mouseSel.dragging === false;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
view.inputState.setSelectionOrigin("select.pointer");
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
function rangeForClick(view, pos, bias, type) {
|
|||
|
|
if (type == 1) {
|
|||
|
|
return EditorSelection.cursor(pos, bias);
|
|||
|
|
} else if (type == 2) {
|
|||
|
|
return groupAt(view.state, pos, bias);
|
|||
|
|
} else {
|
|||
|
|
let visual = view.docView.lineAt(pos, bias), line = view.state.doc.lineAt(visual ? visual.posAtEnd : pos);
|
|||
|
|
let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
|
|||
|
|
if (to < view.state.doc.length && to == line.to)
|
|||
|
|
to++;
|
|||
|
|
return EditorSelection.range(from, to);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var BadMouseDetail = browser.ie && browser.ie_version <= 11;
|
|||
|
|
var lastMouseDown = null;
|
|||
|
|
var lastMouseDownCount = 0;
|
|||
|
|
var lastMouseDownTime = 0;
|
|||
|
|
function getClickType(event) {
|
|||
|
|
if (!BadMouseDetail)
|
|||
|
|
return event.detail;
|
|||
|
|
let last = lastMouseDown, lastTime = lastMouseDownTime;
|
|||
|
|
lastMouseDown = event;
|
|||
|
|
lastMouseDownTime = Date.now();
|
|||
|
|
return lastMouseDownCount = !last || lastTime > Date.now() - 400 && Math.abs(last.clientX - event.clientX) < 2 && Math.abs(last.clientY - event.clientY) < 2 ? (lastMouseDownCount + 1) % 3 : 1;
|
|||
|
|
}
|
|||
|
|
function basicMouseSelection(view, event) {
|
|||
|
|
let start = view.posAndSideAtCoords({ x: event.clientX, y: event.clientY }, false), type = getClickType(event);
|
|||
|
|
let startSel = view.state.selection;
|
|||
|
|
return {
|
|||
|
|
update(update) {
|
|||
|
|
if (update.docChanged) {
|
|||
|
|
start.pos = update.changes.mapPos(start.pos);
|
|||
|
|
startSel = startSel.map(update.changes);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
get(event2, extend, multiple) {
|
|||
|
|
let cur = view.posAndSideAtCoords({ x: event2.clientX, y: event2.clientY }, false), removed;
|
|||
|
|
let range = rangeForClick(view, cur.pos, cur.assoc, type);
|
|||
|
|
if (start.pos != cur.pos && !extend) {
|
|||
|
|
let startRange = rangeForClick(view, start.pos, start.assoc, type);
|
|||
|
|
let from = Math.min(startRange.from, range.from), to = Math.max(startRange.to, range.to);
|
|||
|
|
range = from < range.from ? EditorSelection.range(from, to, range.assoc) : EditorSelection.range(to, from, range.assoc);
|
|||
|
|
}
|
|||
|
|
if (extend)
|
|||
|
|
return startSel.replaceRange(startSel.main.extend(range.from, range.to, range.assoc));
|
|||
|
|
else if (multiple && type == 1 && startSel.ranges.length > 1 && (removed = removeRangeAround(startSel, cur.pos)))
|
|||
|
|
return removed;
|
|||
|
|
else if (multiple)
|
|||
|
|
return startSel.addRange(range);
|
|||
|
|
else
|
|||
|
|
return EditorSelection.create([range]);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function removeRangeAround(sel, pos) {
|
|||
|
|
for (let i = 0; i < sel.ranges.length; i++) {
|
|||
|
|
let { from, to } = sel.ranges[i];
|
|||
|
|
if (from <= pos && to >= pos)
|
|||
|
|
return EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
handlers.dragstart = (view, event) => {
|
|||
|
|
let { selection: { main: range } } = view.state;
|
|||
|
|
if (event.target.draggable) {
|
|||
|
|
let tile = view.docView.tile.nearest(event.target);
|
|||
|
|
if (tile && tile.isWidget()) {
|
|||
|
|
let from = tile.posAtStart, to = from + tile.length;
|
|||
|
|
if (from >= range.to || to <= range.from)
|
|||
|
|
range = EditorSelection.range(from, to);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let { inputState } = view;
|
|||
|
|
if (inputState.mouseSelection)
|
|||
|
|
inputState.mouseSelection.dragging = true;
|
|||
|
|
inputState.draggedContent = range;
|
|||
|
|
if (event.dataTransfer) {
|
|||
|
|
event.dataTransfer.setData("Text", textFilter(view.state, clipboardOutputFilter, view.state.sliceDoc(range.from, range.to)));
|
|||
|
|
event.dataTransfer.effectAllowed = "copyMove";
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
handlers.dragend = (view) => {
|
|||
|
|
view.inputState.draggedContent = null;
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
function dropText(view, event, text, direct) {
|
|||
|
|
text = textFilter(view.state, clipboardInputFilter, text);
|
|||
|
|
if (!text)
|
|||
|
|
return;
|
|||
|
|
let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
|
|||
|
|
let { draggedContent } = view.inputState;
|
|||
|
|
let del = direct && draggedContent && dragMovesSelection(view, event) ? { from: draggedContent.from, to: draggedContent.to } : null;
|
|||
|
|
let ins = { from: dropPos, insert: text };
|
|||
|
|
let changes = view.state.changes(del ? [del, ins] : ins);
|
|||
|
|
view.focus();
|
|||
|
|
view.dispatch({
|
|||
|
|
changes,
|
|||
|
|
selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
|
|||
|
|
userEvent: del ? "move.drop" : "input.drop"
|
|||
|
|
});
|
|||
|
|
view.inputState.draggedContent = null;
|
|||
|
|
}
|
|||
|
|
handlers.drop = (view, event) => {
|
|||
|
|
if (!event.dataTransfer)
|
|||
|
|
return false;
|
|||
|
|
if (view.state.readOnly)
|
|||
|
|
return true;
|
|||
|
|
let files = event.dataTransfer.files;
|
|||
|
|
if (files && files.length) {
|
|||
|
|
let text = Array(files.length), read = 0;
|
|||
|
|
let finishFile = () => {
|
|||
|
|
if (++read == files.length)
|
|||
|
|
dropText(view, event, text.filter((s) => s != null).join(view.state.lineBreak), false);
|
|||
|
|
};
|
|||
|
|
for (let i = 0; i < files.length; i++) {
|
|||
|
|
let reader = new FileReader();
|
|||
|
|
reader.onerror = finishFile;
|
|||
|
|
reader.onload = () => {
|
|||
|
|
if (!/[\x00-\x08\x0e-\x1f]{2}/.test(reader.result))
|
|||
|
|
text[i] = reader.result;
|
|||
|
|
finishFile();
|
|||
|
|
};
|
|||
|
|
reader.readAsText(files[i]);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
let text = event.dataTransfer.getData("Text");
|
|||
|
|
if (text) {
|
|||
|
|
dropText(view, event, text, true);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
handlers.paste = (view, event) => {
|
|||
|
|
if (view.state.readOnly)
|
|||
|
|
return true;
|
|||
|
|
view.observer.flush();
|
|||
|
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|||
|
|
if (data) {
|
|||
|
|
doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
capturePaste(view);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function captureCopy(view, text) {
|
|||
|
|
let parent = view.dom.parentNode;
|
|||
|
|
if (!parent)
|
|||
|
|
return;
|
|||
|
|
let target = parent.appendChild(document.createElement("textarea"));
|
|||
|
|
target.style.cssText = "position: fixed; left: -10000px; top: 10px";
|
|||
|
|
target.value = text;
|
|||
|
|
target.focus();
|
|||
|
|
target.selectionEnd = text.length;
|
|||
|
|
target.selectionStart = 0;
|
|||
|
|
setTimeout(() => {
|
|||
|
|
target.remove();
|
|||
|
|
view.focus();
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
function copiedRange(state) {
|
|||
|
|
let content = [], ranges = [], linewise = false;
|
|||
|
|
for (let range of state.selection.ranges)
|
|||
|
|
if (!range.empty) {
|
|||
|
|
content.push(state.sliceDoc(range.from, range.to));
|
|||
|
|
ranges.push(range);
|
|||
|
|
}
|
|||
|
|
if (!content.length) {
|
|||
|
|
let upto = -1;
|
|||
|
|
for (let { from } of state.selection.ranges) {
|
|||
|
|
let line = state.doc.lineAt(from);
|
|||
|
|
if (line.number > upto) {
|
|||
|
|
content.push(line.text);
|
|||
|
|
ranges.push({ from: line.from, to: Math.min(state.doc.length, line.to + 1) });
|
|||
|
|
}
|
|||
|
|
upto = line.number;
|
|||
|
|
}
|
|||
|
|
linewise = true;
|
|||
|
|
}
|
|||
|
|
return { text: textFilter(state, clipboardOutputFilter, content.join(state.lineBreak)), ranges, linewise };
|
|||
|
|
}
|
|||
|
|
var lastLinewiseCopy = null;
|
|||
|
|
handlers.copy = handlers.cut = (view, event) => {
|
|||
|
|
if (!hasSelection(view.contentDOM, view.observer.selectionRange))
|
|||
|
|
return false;
|
|||
|
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|||
|
|
if (!text && !linewise)
|
|||
|
|
return false;
|
|||
|
|
lastLinewiseCopy = linewise ? text : null;
|
|||
|
|
if (event.type == "cut" && !view.state.readOnly)
|
|||
|
|
view.dispatch({
|
|||
|
|
changes: ranges,
|
|||
|
|
scrollIntoView: true,
|
|||
|
|
userEvent: "delete.cut"
|
|||
|
|
});
|
|||
|
|
let data = brokenClipboardAPI ? null : event.clipboardData;
|
|||
|
|
if (data) {
|
|||
|
|
data.clearData();
|
|||
|
|
data.setData("text/plain", text);
|
|||
|
|
return true;
|
|||
|
|
} else {
|
|||
|
|
captureCopy(view, text);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var isFocusChange = Annotation.define();
|
|||
|
|
function focusChangeTransaction(state, focus) {
|
|||
|
|
let effects = [];
|
|||
|
|
for (let getEffect of state.facet(focusChangeEffect)) {
|
|||
|
|
let effect = getEffect(state, focus);
|
|||
|
|
if (effect)
|
|||
|
|
effects.push(effect);
|
|||
|
|
}
|
|||
|
|
return effects.length ? state.update({ effects, annotations: isFocusChange.of(true) }) : null;
|
|||
|
|
}
|
|||
|
|
function updateForFocusChange(view) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
let focus = view.hasFocus;
|
|||
|
|
if (focus != view.inputState.notifiedFocused) {
|
|||
|
|
let tr = focusChangeTransaction(view.state, focus);
|
|||
|
|
if (tr)
|
|||
|
|
view.dispatch(tr);
|
|||
|
|
else
|
|||
|
|
view.update([]);
|
|||
|
|
}
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
observers.focus = (view) => {
|
|||
|
|
view.inputState.lastFocusTime = Date.now();
|
|||
|
|
if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
|
|||
|
|
view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
|
|||
|
|
view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
|
|||
|
|
}
|
|||
|
|
updateForFocusChange(view);
|
|||
|
|
};
|
|||
|
|
observers.blur = (view) => {
|
|||
|
|
view.observer.clearSelectionRange();
|
|||
|
|
updateForFocusChange(view);
|
|||
|
|
};
|
|||
|
|
observers.compositionstart = observers.compositionupdate = (view) => {
|
|||
|
|
if (view.observer.editContext)
|
|||
|
|
return;
|
|||
|
|
if (view.inputState.compositionFirstChange == null)
|
|||
|
|
view.inputState.compositionFirstChange = true;
|
|||
|
|
if (view.inputState.composing < 0) {
|
|||
|
|
view.inputState.composing = 0;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
observers.compositionend = (view) => {
|
|||
|
|
if (view.observer.editContext)
|
|||
|
|
return;
|
|||
|
|
view.inputState.composing = -1;
|
|||
|
|
view.inputState.compositionEndedAt = Date.now();
|
|||
|
|
view.inputState.compositionPendingKey = true;
|
|||
|
|
view.inputState.compositionPendingChange = view.observer.pendingRecords().length > 0;
|
|||
|
|
view.inputState.compositionFirstChange = null;
|
|||
|
|
if (browser.chrome && browser.android) {
|
|||
|
|
view.observer.flushSoon();
|
|||
|
|
} else if (view.inputState.compositionPendingChange) {
|
|||
|
|
Promise.resolve().then(() => view.observer.flush());
|
|||
|
|
} else {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (view.inputState.composing < 0 && view.docView.hasComposition)
|
|||
|
|
view.update([]);
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
observers.contextmenu = (view) => {
|
|||
|
|
view.inputState.lastContextMenu = Date.now();
|
|||
|
|
};
|
|||
|
|
handlers.beforeinput = (view, event) => {
|
|||
|
|
var _a, _b;
|
|||
|
|
if (event.inputType == "insertText" || event.inputType == "insertCompositionText") {
|
|||
|
|
view.inputState.insertingText = event.data;
|
|||
|
|
view.inputState.insertingTextAt = Date.now();
|
|||
|
|
}
|
|||
|
|
if (event.inputType == "insertReplacementText" && view.observer.editContext) {
|
|||
|
|
let text = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text/plain"), ranges = event.getTargetRanges();
|
|||
|
|
if (text && ranges.length) {
|
|||
|
|
let r = ranges[0];
|
|||
|
|
let from = view.posAtDOM(r.startContainer, r.startOffset), to = view.posAtDOM(r.endContainer, r.endOffset);
|
|||
|
|
applyDOMChangeInner(view, { from, to, insert: view.state.toText(text) }, null);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let pending;
|
|||
|
|
if (browser.chrome && browser.android && (pending = PendingKeys.find((key) => key.inputType == event.inputType))) {
|
|||
|
|
view.observer.delayAndroidKey(pending.key, pending.keyCode);
|
|||
|
|
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|||
|
|
let startViewHeight = ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || 0;
|
|||
|
|
setTimeout(() => {
|
|||
|
|
var _a2;
|
|||
|
|
if ((((_a2 = window.visualViewport) === null || _a2 === void 0 ? void 0 : _a2.height) || 0) > startViewHeight + 10 && view.hasFocus) {
|
|||
|
|
view.contentDOM.blur();
|
|||
|
|
view.focus();
|
|||
|
|
}
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (browser.ios && event.inputType == "deleteContentForward") {
|
|||
|
|
view.observer.flushSoon();
|
|||
|
|
}
|
|||
|
|
if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
|
|||
|
|
setTimeout(() => observers.compositionend(view, event), 20);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
var appliedFirefoxHack = /* @__PURE__ */ new Set();
|
|||
|
|
function firefoxCopyCutHack(doc2) {
|
|||
|
|
if (!appliedFirefoxHack.has(doc2)) {
|
|||
|
|
appliedFirefoxHack.add(doc2);
|
|||
|
|
doc2.addEventListener("copy", () => {
|
|||
|
|
});
|
|||
|
|
doc2.addEventListener("cut", () => {
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
|
|||
|
|
var heightChangeFlag = false;
|
|||
|
|
function clearHeightChangeFlag() {
|
|||
|
|
heightChangeFlag = false;
|
|||
|
|
}
|
|||
|
|
var HeightOracle = class {
|
|||
|
|
constructor(lineWrapping) {
|
|||
|
|
this.lineWrapping = lineWrapping;
|
|||
|
|
this.doc = Text.empty;
|
|||
|
|
this.heightSamples = {};
|
|||
|
|
this.lineHeight = 14;
|
|||
|
|
this.charWidth = 7;
|
|||
|
|
this.textHeight = 14;
|
|||
|
|
this.lineLength = 30;
|
|||
|
|
}
|
|||
|
|
heightForGap(from, to) {
|
|||
|
|
let lines = this.doc.lineAt(to).number - this.doc.lineAt(from).number + 1;
|
|||
|
|
if (this.lineWrapping)
|
|||
|
|
lines += Math.max(0, Math.ceil((to - from - lines * this.lineLength * 0.5) / this.lineLength));
|
|||
|
|
return this.lineHeight * lines;
|
|||
|
|
}
|
|||
|
|
heightForLine(length) {
|
|||
|
|
if (!this.lineWrapping)
|
|||
|
|
return this.lineHeight;
|
|||
|
|
let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / Math.max(1, this.lineLength - 5)));
|
|||
|
|
return lines * this.lineHeight;
|
|||
|
|
}
|
|||
|
|
setDoc(doc2) {
|
|||
|
|
this.doc = doc2;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
mustRefreshForWrapping(whiteSpace) {
|
|||
|
|
return wrappingWhiteSpace.indexOf(whiteSpace) > -1 != this.lineWrapping;
|
|||
|
|
}
|
|||
|
|
mustRefreshForHeights(lineHeights) {
|
|||
|
|
let newHeight = false;
|
|||
|
|
for (let i = 0; i < lineHeights.length; i++) {
|
|||
|
|
let h = lineHeights[i];
|
|||
|
|
if (h < 0) {
|
|||
|
|
i++;
|
|||
|
|
} else if (!this.heightSamples[Math.floor(h * 10)]) {
|
|||
|
|
newHeight = true;
|
|||
|
|
this.heightSamples[Math.floor(h * 10)] = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return newHeight;
|
|||
|
|
}
|
|||
|
|
refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
|
|||
|
|
let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
|
|||
|
|
let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping;
|
|||
|
|
this.lineWrapping = lineWrapping;
|
|||
|
|
this.lineHeight = lineHeight;
|
|||
|
|
this.charWidth = charWidth;
|
|||
|
|
this.textHeight = textHeight;
|
|||
|
|
this.lineLength = lineLength;
|
|||
|
|
if (changed) {
|
|||
|
|
this.heightSamples = {};
|
|||
|
|
for (let i = 0; i < knownHeights.length; i++) {
|
|||
|
|
let h = knownHeights[i];
|
|||
|
|
if (h < 0)
|
|||
|
|
i++;
|
|||
|
|
else
|
|||
|
|
this.heightSamples[Math.floor(h * 10)] = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return changed;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var MeasuredHeights = class {
|
|||
|
|
constructor(from, heights) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.heights = heights;
|
|||
|
|
this.index = 0;
|
|||
|
|
}
|
|||
|
|
get more() {
|
|||
|
|
return this.index < this.heights.length;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var BlockInfo = class _BlockInfo {
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
constructor(from, length, top2, height, _content) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.length = length;
|
|||
|
|
this.top = top2;
|
|||
|
|
this.height = height;
|
|||
|
|
this._content = _content;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The type of element this is. When querying lines, this may be
|
|||
|
|
an array of all the blocks that make up the line.
|
|||
|
|
*/
|
|||
|
|
get type() {
|
|||
|
|
return typeof this._content == "number" ? BlockType.Text : Array.isArray(this._content) ? this._content : this._content.type;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The end of the element as a document position.
|
|||
|
|
*/
|
|||
|
|
get to() {
|
|||
|
|
return this.from + this.length;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The bottom position of the element.
|
|||
|
|
*/
|
|||
|
|
get bottom() {
|
|||
|
|
return this.top + this.height;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
If this is a widget block, this will return the widget
|
|||
|
|
associated with it.
|
|||
|
|
*/
|
|||
|
|
get widget() {
|
|||
|
|
return this._content instanceof PointDecoration ? this._content.widget : null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
If this is a textblock, this holds the number of line breaks
|
|||
|
|
that appear in widgets inside the block.
|
|||
|
|
*/
|
|||
|
|
get widgetLineBreaks() {
|
|||
|
|
return typeof this._content == "number" ? this._content : 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
join(other) {
|
|||
|
|
let content = (Array.isArray(this._content) ? this._content : [this]).concat(Array.isArray(other._content) ? other._content : [other]);
|
|||
|
|
return new _BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, content);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var QueryType = function(QueryType2) {
|
|||
|
|
QueryType2[QueryType2["ByPos"] = 0] = "ByPos";
|
|||
|
|
QueryType2[QueryType2["ByHeight"] = 1] = "ByHeight";
|
|||
|
|
QueryType2[QueryType2["ByPosNoHeight"] = 2] = "ByPosNoHeight";
|
|||
|
|
return QueryType2;
|
|||
|
|
}(QueryType || (QueryType = {}));
|
|||
|
|
var Epsilon = 1e-3;
|
|||
|
|
var HeightMap = class _HeightMap {
|
|||
|
|
constructor(length, height, flags = 2) {
|
|||
|
|
this.length = length;
|
|||
|
|
this.height = height;
|
|||
|
|
this.flags = flags;
|
|||
|
|
}
|
|||
|
|
get outdated() {
|
|||
|
|
return (this.flags & 2) > 0;
|
|||
|
|
}
|
|||
|
|
set outdated(value) {
|
|||
|
|
this.flags = (value ? 2 : 0) | this.flags & ~2;
|
|||
|
|
}
|
|||
|
|
setHeight(height) {
|
|||
|
|
if (this.height != height) {
|
|||
|
|
if (Math.abs(this.height - height) > Epsilon)
|
|||
|
|
heightChangeFlag = true;
|
|||
|
|
this.height = height;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Base case is to replace a leaf node, which simply builds a tree
|
|||
|
|
// from the new nodes and returns that (HeightMapBranch and
|
|||
|
|
// HeightMapGap override this to actually use from/to)
|
|||
|
|
replace(_from, _to, nodes) {
|
|||
|
|
return _HeightMap.of(nodes);
|
|||
|
|
}
|
|||
|
|
// Again, these are base cases, and are overridden for branch and gap nodes.
|
|||
|
|
decomposeLeft(_to, result) {
|
|||
|
|
result.push(this);
|
|||
|
|
}
|
|||
|
|
decomposeRight(_from, result) {
|
|||
|
|
result.push(this);
|
|||
|
|
}
|
|||
|
|
applyChanges(decorations2, oldDoc, oracle, changes) {
|
|||
|
|
let me = this, doc2 = oracle.doc;
|
|||
|
|
for (let i = changes.length - 1; i >= 0; i--) {
|
|||
|
|
let { fromA, toA, fromB, toB } = changes[i];
|
|||
|
|
let start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle.setDoc(oldDoc), 0, 0);
|
|||
|
|
let end = start.to >= toA ? start : me.lineAt(toA, QueryType.ByPosNoHeight, oracle, 0, 0);
|
|||
|
|
toB += end.to - toA;
|
|||
|
|
toA = end.to;
|
|||
|
|
while (i > 0 && start.from <= changes[i - 1].toA) {
|
|||
|
|
fromA = changes[i - 1].fromA;
|
|||
|
|
fromB = changes[i - 1].fromB;
|
|||
|
|
i--;
|
|||
|
|
if (fromA < start.from)
|
|||
|
|
start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle, 0, 0);
|
|||
|
|
}
|
|||
|
|
fromB += start.from - fromA;
|
|||
|
|
fromA = start.from;
|
|||
|
|
let nodes = NodeBuilder.build(oracle.setDoc(doc2), decorations2, fromB, toB);
|
|||
|
|
me = replace(me, me.replace(fromA, toA, nodes));
|
|||
|
|
}
|
|||
|
|
return me.updateHeight(oracle, 0);
|
|||
|
|
}
|
|||
|
|
static empty() {
|
|||
|
|
return new HeightMapText(0, 0, 0);
|
|||
|
|
}
|
|||
|
|
// nodes uses null values to indicate the position of line breaks.
|
|||
|
|
// There are never line breaks at the start or end of the array, or
|
|||
|
|
// two line breaks next to each other, and the array isn't allowed
|
|||
|
|
// to be empty (same restrictions as return value from the builder).
|
|||
|
|
static of(nodes) {
|
|||
|
|
if (nodes.length == 1)
|
|||
|
|
return nodes[0];
|
|||
|
|
let i = 0, j = nodes.length, before = 0, after = 0;
|
|||
|
|
for (; ; ) {
|
|||
|
|
if (i == j) {
|
|||
|
|
if (before > after * 2) {
|
|||
|
|
let split = nodes[i - 1];
|
|||
|
|
if (split.break)
|
|||
|
|
nodes.splice(--i, 1, split.left, null, split.right);
|
|||
|
|
else
|
|||
|
|
nodes.splice(--i, 1, split.left, split.right);
|
|||
|
|
j += 1 + split.break;
|
|||
|
|
before -= split.size;
|
|||
|
|
} else if (after > before * 2) {
|
|||
|
|
let split = nodes[j];
|
|||
|
|
if (split.break)
|
|||
|
|
nodes.splice(j, 1, split.left, null, split.right);
|
|||
|
|
else
|
|||
|
|
nodes.splice(j, 1, split.left, split.right);
|
|||
|
|
j += 2 + split.break;
|
|||
|
|
after -= split.size;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} else if (before < after) {
|
|||
|
|
let next = nodes[i++];
|
|||
|
|
if (next)
|
|||
|
|
before += next.size;
|
|||
|
|
} else {
|
|||
|
|
let next = nodes[--j];
|
|||
|
|
if (next)
|
|||
|
|
after += next.size;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let brk = 0;
|
|||
|
|
if (nodes[i - 1] == null) {
|
|||
|
|
brk = 1;
|
|||
|
|
i--;
|
|||
|
|
} else if (nodes[i] == null) {
|
|||
|
|
brk = 1;
|
|||
|
|
j++;
|
|||
|
|
}
|
|||
|
|
return new HeightMapBranch(_HeightMap.of(nodes.slice(0, i)), brk, _HeightMap.of(nodes.slice(j)));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function replace(old, val) {
|
|||
|
|
if (old == val)
|
|||
|
|
return old;
|
|||
|
|
if (old.constructor != val.constructor)
|
|||
|
|
heightChangeFlag = true;
|
|||
|
|
return val;
|
|||
|
|
}
|
|||
|
|
HeightMap.prototype.size = 1;
|
|||
|
|
var SpaceDeco = Decoration.replace({});
|
|||
|
|
var HeightMapBlock = class extends HeightMap {
|
|||
|
|
constructor(length, height, deco) {
|
|||
|
|
super(length, height);
|
|||
|
|
this.deco = deco;
|
|||
|
|
this.spaceAbove = 0;
|
|||
|
|
}
|
|||
|
|
mainBlock(top2, offset) {
|
|||
|
|
return new BlockInfo(offset, this.length, top2 + this.spaceAbove, this.height - this.spaceAbove, this.deco || 0);
|
|||
|
|
}
|
|||
|
|
blockAt(height, _oracle, top2, offset) {
|
|||
|
|
return this.spaceAbove && height < top2 + this.spaceAbove ? new BlockInfo(offset, 0, top2, this.spaceAbove, SpaceDeco) : this.mainBlock(top2, offset);
|
|||
|
|
}
|
|||
|
|
lineAt(_value, _type, oracle, top2, offset) {
|
|||
|
|
let main = this.mainBlock(top2, offset);
|
|||
|
|
return this.spaceAbove ? this.blockAt(0, oracle, top2, offset).join(main) : main;
|
|||
|
|
}
|
|||
|
|
forEachLine(from, to, oracle, top2, offset, f) {
|
|||
|
|
if (from <= offset + this.length && to >= offset)
|
|||
|
|
f(this.lineAt(0, QueryType.ByPos, oracle, top2, offset));
|
|||
|
|
}
|
|||
|
|
setMeasuredHeight(measured) {
|
|||
|
|
let next = measured.heights[measured.index++];
|
|||
|
|
if (next < 0) {
|
|||
|
|
this.spaceAbove = -next;
|
|||
|
|
next = measured.heights[measured.index++];
|
|||
|
|
} else {
|
|||
|
|
this.spaceAbove = 0;
|
|||
|
|
}
|
|||
|
|
this.setHeight(next);
|
|||
|
|
}
|
|||
|
|
updateHeight(oracle, offset = 0, _force = false, measured) {
|
|||
|
|
if (measured && measured.from <= offset && measured.more)
|
|||
|
|
this.setMeasuredHeight(measured);
|
|||
|
|
this.outdated = false;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return `block(${this.length})`;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var HeightMapText = class _HeightMapText extends HeightMapBlock {
|
|||
|
|
constructor(length, height, above) {
|
|||
|
|
super(length, height, null);
|
|||
|
|
this.collapsed = 0;
|
|||
|
|
this.widgetHeight = 0;
|
|||
|
|
this.breaks = 0;
|
|||
|
|
this.spaceAbove = above;
|
|||
|
|
}
|
|||
|
|
mainBlock(top2, offset) {
|
|||
|
|
return new BlockInfo(offset, this.length, top2 + this.spaceAbove, this.height - this.spaceAbove, this.breaks);
|
|||
|
|
}
|
|||
|
|
replace(_from, _to, nodes) {
|
|||
|
|
let node = nodes[0];
|
|||
|
|
if (nodes.length == 1 && (node instanceof _HeightMapText || node instanceof HeightMapGap && node.flags & 4) && Math.abs(this.length - node.length) < 10) {
|
|||
|
|
if (node instanceof HeightMapGap)
|
|||
|
|
node = new _HeightMapText(node.length, this.height, this.spaceAbove);
|
|||
|
|
else
|
|||
|
|
node.height = this.height;
|
|||
|
|
if (!this.outdated)
|
|||
|
|
node.outdated = false;
|
|||
|
|
return node;
|
|||
|
|
} else {
|
|||
|
|
return HeightMap.of(nodes);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
updateHeight(oracle, offset = 0, force = false, measured) {
|
|||
|
|
if (measured && measured.from <= offset && measured.more) {
|
|||
|
|
this.setMeasuredHeight(measured);
|
|||
|
|
} else if (force || this.outdated) {
|
|||
|
|
this.spaceAbove = 0;
|
|||
|
|
this.setHeight(Math.max(this.widgetHeight, oracle.heightForLine(this.length - this.collapsed)) + this.breaks * oracle.lineHeight);
|
|||
|
|
}
|
|||
|
|
this.outdated = false;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return `line(${this.length}${this.collapsed ? -this.collapsed : ""}${this.widgetHeight ? ":" + this.widgetHeight : ""})`;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var HeightMapGap = class _HeightMapGap extends HeightMap {
|
|||
|
|
constructor(length) {
|
|||
|
|
super(length, 0);
|
|||
|
|
}
|
|||
|
|
heightMetrics(oracle, offset) {
|
|||
|
|
let firstLine = oracle.doc.lineAt(offset).number, lastLine = oracle.doc.lineAt(offset + this.length).number;
|
|||
|
|
let lines = lastLine - firstLine + 1;
|
|||
|
|
let perLine, perChar = 0;
|
|||
|
|
if (oracle.lineWrapping) {
|
|||
|
|
let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
|
|||
|
|
perLine = totalPerLine / lines;
|
|||
|
|
if (this.length > lines + 1)
|
|||
|
|
perChar = (this.height - totalPerLine) / (this.length - lines - 1);
|
|||
|
|
} else {
|
|||
|
|
perLine = this.height / lines;
|
|||
|
|
}
|
|||
|
|
return { firstLine, lastLine, perLine, perChar };
|
|||
|
|
}
|
|||
|
|
blockAt(height, oracle, top2, offset) {
|
|||
|
|
let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
|
|||
|
|
if (oracle.lineWrapping) {
|
|||
|
|
let guess = offset + (height < oracle.lineHeight ? 0 : Math.round(Math.max(0, Math.min(1, (height - top2) / this.height)) * this.length));
|
|||
|
|
let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
|
|||
|
|
let lineTop = Math.max(top2, height - lineHeight / 2);
|
|||
|
|
return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
|
|||
|
|
} else {
|
|||
|
|
let line = Math.max(0, Math.min(lastLine - firstLine, Math.floor((height - top2) / perLine)));
|
|||
|
|
let { from, length } = oracle.doc.line(firstLine + line);
|
|||
|
|
return new BlockInfo(from, length, top2 + perLine * line, perLine, 0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
lineAt(value, type, oracle, top2, offset) {
|
|||
|
|
if (type == QueryType.ByHeight)
|
|||
|
|
return this.blockAt(value, oracle, top2, offset);
|
|||
|
|
if (type == QueryType.ByPosNoHeight) {
|
|||
|
|
let { from, to } = oracle.doc.lineAt(value);
|
|||
|
|
return new BlockInfo(from, to - from, 0, 0, 0);
|
|||
|
|
}
|
|||
|
|
let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
|
|||
|
|
let line = oracle.doc.lineAt(value), lineHeight = perLine + line.length * perChar;
|
|||
|
|
let linesAbove = line.number - firstLine;
|
|||
|
|
let lineTop = top2 + perLine * linesAbove + perChar * (line.from - offset - linesAbove);
|
|||
|
|
return new BlockInfo(line.from, line.length, Math.max(top2, Math.min(lineTop, top2 + this.height - lineHeight)), lineHeight, 0);
|
|||
|
|
}
|
|||
|
|
forEachLine(from, to, oracle, top2, offset, f) {
|
|||
|
|
from = Math.max(from, offset);
|
|||
|
|
to = Math.min(to, offset + this.length);
|
|||
|
|
let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
|
|||
|
|
for (let pos = from, lineTop = top2; pos <= to; ) {
|
|||
|
|
let line = oracle.doc.lineAt(pos);
|
|||
|
|
if (pos == from) {
|
|||
|
|
let linesAbove = line.number - firstLine;
|
|||
|
|
lineTop += perLine * linesAbove + perChar * (from - offset - linesAbove);
|
|||
|
|
}
|
|||
|
|
let lineHeight = perLine + perChar * line.length;
|
|||
|
|
f(new BlockInfo(line.from, line.length, lineTop, lineHeight, 0));
|
|||
|
|
lineTop += lineHeight;
|
|||
|
|
pos = line.to + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
replace(from, to, nodes) {
|
|||
|
|
let after = this.length - to;
|
|||
|
|
if (after > 0) {
|
|||
|
|
let last = nodes[nodes.length - 1];
|
|||
|
|
if (last instanceof _HeightMapGap)
|
|||
|
|
nodes[nodes.length - 1] = new _HeightMapGap(last.length + after);
|
|||
|
|
else
|
|||
|
|
nodes.push(null, new _HeightMapGap(after - 1));
|
|||
|
|
}
|
|||
|
|
if (from > 0) {
|
|||
|
|
let first = nodes[0];
|
|||
|
|
if (first instanceof _HeightMapGap)
|
|||
|
|
nodes[0] = new _HeightMapGap(from + first.length);
|
|||
|
|
else
|
|||
|
|
nodes.unshift(new _HeightMapGap(from - 1), null);
|
|||
|
|
}
|
|||
|
|
return HeightMap.of(nodes);
|
|||
|
|
}
|
|||
|
|
decomposeLeft(to, result) {
|
|||
|
|
result.push(new _HeightMapGap(to - 1), null);
|
|||
|
|
}
|
|||
|
|
decomposeRight(from, result) {
|
|||
|
|
result.push(null, new _HeightMapGap(this.length - from - 1));
|
|||
|
|
}
|
|||
|
|
updateHeight(oracle, offset = 0, force = false, measured) {
|
|||
|
|
let end = offset + this.length;
|
|||
|
|
if (measured && measured.from <= offset + this.length && measured.more) {
|
|||
|
|
let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
|
|||
|
|
if (measured.from > offset)
|
|||
|
|
nodes.push(new _HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
|
|||
|
|
while (pos <= end && measured.more) {
|
|||
|
|
let len = oracle.doc.lineAt(pos).length;
|
|||
|
|
if (nodes.length)
|
|||
|
|
nodes.push(null);
|
|||
|
|
let height = measured.heights[measured.index++], above = 0;
|
|||
|
|
if (height < 0) {
|
|||
|
|
above = -height;
|
|||
|
|
height = measured.heights[measured.index++];
|
|||
|
|
}
|
|||
|
|
if (singleHeight == -1)
|
|||
|
|
singleHeight = height;
|
|||
|
|
else if (Math.abs(height - singleHeight) >= Epsilon)
|
|||
|
|
singleHeight = -2;
|
|||
|
|
let line = new HeightMapText(len, height, above);
|
|||
|
|
line.outdated = false;
|
|||
|
|
nodes.push(line);
|
|||
|
|
pos += len + 1;
|
|||
|
|
}
|
|||
|
|
if (pos <= end)
|
|||
|
|
nodes.push(null, new _HeightMapGap(end - pos).updateHeight(oracle, pos));
|
|||
|
|
let result = HeightMap.of(nodes);
|
|||
|
|
if (singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon || Math.abs(singleHeight - this.heightMetrics(oracle, offset).perLine) >= Epsilon)
|
|||
|
|
heightChangeFlag = true;
|
|||
|
|
return replace(this, result);
|
|||
|
|
} else if (force || this.outdated) {
|
|||
|
|
this.setHeight(oracle.heightForGap(offset, offset + this.length));
|
|||
|
|
this.outdated = false;
|
|||
|
|
}
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return `gap(${this.length})`;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var HeightMapBranch = class extends HeightMap {
|
|||
|
|
constructor(left, brk, right) {
|
|||
|
|
super(left.length + brk + right.length, left.height + right.height, brk | (left.outdated || right.outdated ? 2 : 0));
|
|||
|
|
this.left = left;
|
|||
|
|
this.right = right;
|
|||
|
|
this.size = left.size + right.size;
|
|||
|
|
}
|
|||
|
|
get break() {
|
|||
|
|
return this.flags & 1;
|
|||
|
|
}
|
|||
|
|
blockAt(height, oracle, top2, offset) {
|
|||
|
|
let mid = top2 + this.left.height;
|
|||
|
|
return height < mid ? this.left.blockAt(height, oracle, top2, offset) : this.right.blockAt(height, oracle, mid, offset + this.left.length + this.break);
|
|||
|
|
}
|
|||
|
|
lineAt(value, type, oracle, top2, offset) {
|
|||
|
|
let rightTop = top2 + this.left.height, rightOffset = offset + this.left.length + this.break;
|
|||
|
|
let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
|
|||
|
|
let base2 = left ? this.left.lineAt(value, type, oracle, top2, offset) : this.right.lineAt(value, type, oracle, rightTop, rightOffset);
|
|||
|
|
if (this.break || (left ? base2.to < rightOffset : base2.from > rightOffset))
|
|||
|
|
return base2;
|
|||
|
|
let subQuery = type == QueryType.ByPosNoHeight ? QueryType.ByPosNoHeight : QueryType.ByPos;
|
|||
|
|
if (left)
|
|||
|
|
return base2.join(this.right.lineAt(rightOffset, subQuery, oracle, rightTop, rightOffset));
|
|||
|
|
else
|
|||
|
|
return this.left.lineAt(rightOffset, subQuery, oracle, top2, offset).join(base2);
|
|||
|
|
}
|
|||
|
|
forEachLine(from, to, oracle, top2, offset, f) {
|
|||
|
|
let rightTop = top2 + this.left.height, rightOffset = offset + this.left.length + this.break;
|
|||
|
|
if (this.break) {
|
|||
|
|
if (from < rightOffset)
|
|||
|
|
this.left.forEachLine(from, to, oracle, top2, offset, f);
|
|||
|
|
if (to >= rightOffset)
|
|||
|
|
this.right.forEachLine(from, to, oracle, rightTop, rightOffset, f);
|
|||
|
|
} else {
|
|||
|
|
let mid = this.lineAt(rightOffset, QueryType.ByPos, oracle, top2, offset);
|
|||
|
|
if (from < mid.from)
|
|||
|
|
this.left.forEachLine(from, mid.from - 1, oracle, top2, offset, f);
|
|||
|
|
if (mid.to >= from && mid.from <= to)
|
|||
|
|
f(mid);
|
|||
|
|
if (to > mid.to)
|
|||
|
|
this.right.forEachLine(mid.to + 1, to, oracle, rightTop, rightOffset, f);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
replace(from, to, nodes) {
|
|||
|
|
let rightStart = this.left.length + this.break;
|
|||
|
|
if (to < rightStart)
|
|||
|
|
return this.balanced(this.left.replace(from, to, nodes), this.right);
|
|||
|
|
if (from > this.left.length)
|
|||
|
|
return this.balanced(this.left, this.right.replace(from - rightStart, to - rightStart, nodes));
|
|||
|
|
let result = [];
|
|||
|
|
if (from > 0)
|
|||
|
|
this.decomposeLeft(from, result);
|
|||
|
|
let left = result.length;
|
|||
|
|
for (let node of nodes)
|
|||
|
|
result.push(node);
|
|||
|
|
if (from > 0)
|
|||
|
|
mergeGaps(result, left - 1);
|
|||
|
|
if (to < this.length) {
|
|||
|
|
let right = result.length;
|
|||
|
|
this.decomposeRight(to, result);
|
|||
|
|
mergeGaps(result, right);
|
|||
|
|
}
|
|||
|
|
return HeightMap.of(result);
|
|||
|
|
}
|
|||
|
|
decomposeLeft(to, result) {
|
|||
|
|
let left = this.left.length;
|
|||
|
|
if (to <= left)
|
|||
|
|
return this.left.decomposeLeft(to, result);
|
|||
|
|
result.push(this.left);
|
|||
|
|
if (this.break) {
|
|||
|
|
left++;
|
|||
|
|
if (to >= left)
|
|||
|
|
result.push(null);
|
|||
|
|
}
|
|||
|
|
if (to > left)
|
|||
|
|
this.right.decomposeLeft(to - left, result);
|
|||
|
|
}
|
|||
|
|
decomposeRight(from, result) {
|
|||
|
|
let left = this.left.length, right = left + this.break;
|
|||
|
|
if (from >= right)
|
|||
|
|
return this.right.decomposeRight(from - right, result);
|
|||
|
|
if (from < left)
|
|||
|
|
this.left.decomposeRight(from, result);
|
|||
|
|
if (this.break && from < right)
|
|||
|
|
result.push(null);
|
|||
|
|
result.push(this.right);
|
|||
|
|
}
|
|||
|
|
balanced(left, right) {
|
|||
|
|
if (left.size > 2 * right.size || right.size > 2 * left.size)
|
|||
|
|
return HeightMap.of(this.break ? [left, null, right] : [left, right]);
|
|||
|
|
this.left = replace(this.left, left);
|
|||
|
|
this.right = replace(this.right, right);
|
|||
|
|
this.setHeight(left.height + right.height);
|
|||
|
|
this.outdated = left.outdated || right.outdated;
|
|||
|
|
this.size = left.size + right.size;
|
|||
|
|
this.length = left.length + this.break + right.length;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
updateHeight(oracle, offset = 0, force = false, measured) {
|
|||
|
|
let { left, right } = this, rightStart = offset + left.length + this.break, rebalance = null;
|
|||
|
|
if (measured && measured.from <= offset + left.length && measured.more)
|
|||
|
|
rebalance = left = left.updateHeight(oracle, offset, force, measured);
|
|||
|
|
else
|
|||
|
|
left.updateHeight(oracle, offset, force);
|
|||
|
|
if (measured && measured.from <= rightStart + right.length && measured.more)
|
|||
|
|
rebalance = right = right.updateHeight(oracle, rightStart, force, measured);
|
|||
|
|
else
|
|||
|
|
right.updateHeight(oracle, rightStart, force);
|
|||
|
|
if (rebalance)
|
|||
|
|
return this.balanced(left, right);
|
|||
|
|
this.height = this.left.height + this.right.height;
|
|||
|
|
this.outdated = false;
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
toString() {
|
|||
|
|
return this.left + (this.break ? " " : "-") + this.right;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function mergeGaps(nodes, around) {
|
|||
|
|
let before, after;
|
|||
|
|
if (nodes[around] == null && (before = nodes[around - 1]) instanceof HeightMapGap && (after = nodes[around + 1]) instanceof HeightMapGap)
|
|||
|
|
nodes.splice(around - 1, 3, new HeightMapGap(before.length + 1 + after.length));
|
|||
|
|
}
|
|||
|
|
var relevantWidgetHeight = 5;
|
|||
|
|
var NodeBuilder = class _NodeBuilder {
|
|||
|
|
constructor(pos, oracle) {
|
|||
|
|
this.pos = pos;
|
|||
|
|
this.oracle = oracle;
|
|||
|
|
this.nodes = [];
|
|||
|
|
this.lineStart = -1;
|
|||
|
|
this.lineEnd = -1;
|
|||
|
|
this.covering = null;
|
|||
|
|
this.writtenTo = pos;
|
|||
|
|
}
|
|||
|
|
get isCovered() {
|
|||
|
|
return this.covering && this.nodes[this.nodes.length - 1] == this.covering;
|
|||
|
|
}
|
|||
|
|
span(_from, to) {
|
|||
|
|
if (this.lineStart > -1) {
|
|||
|
|
let end = Math.min(to, this.lineEnd), last = this.nodes[this.nodes.length - 1];
|
|||
|
|
if (last instanceof HeightMapText)
|
|||
|
|
last.length += end - this.pos;
|
|||
|
|
else if (end > this.pos || !this.isCovered)
|
|||
|
|
this.nodes.push(new HeightMapText(end - this.pos, -1, 0));
|
|||
|
|
this.writtenTo = end;
|
|||
|
|
if (to > end) {
|
|||
|
|
this.nodes.push(null);
|
|||
|
|
this.writtenTo++;
|
|||
|
|
this.lineStart = -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.pos = to;
|
|||
|
|
}
|
|||
|
|
point(from, to, deco) {
|
|||
|
|
if (from < to || deco.heightRelevant) {
|
|||
|
|
let height = deco.widget ? deco.widget.estimatedHeight : 0;
|
|||
|
|
let breaks = deco.widget ? deco.widget.lineBreaks : 0;
|
|||
|
|
if (height < 0)
|
|||
|
|
height = this.oracle.lineHeight;
|
|||
|
|
let len = to - from;
|
|||
|
|
if (deco.block) {
|
|||
|
|
this.addBlock(new HeightMapBlock(len, height, deco));
|
|||
|
|
} else if (len || breaks || height >= relevantWidgetHeight) {
|
|||
|
|
this.addLineDeco(height, breaks, len);
|
|||
|
|
}
|
|||
|
|
} else if (to > from) {
|
|||
|
|
this.span(from, to);
|
|||
|
|
}
|
|||
|
|
if (this.lineEnd > -1 && this.lineEnd < this.pos)
|
|||
|
|
this.lineEnd = this.oracle.doc.lineAt(this.pos).to;
|
|||
|
|
}
|
|||
|
|
enterLine() {
|
|||
|
|
if (this.lineStart > -1)
|
|||
|
|
return;
|
|||
|
|
let { from, to } = this.oracle.doc.lineAt(this.pos);
|
|||
|
|
this.lineStart = from;
|
|||
|
|
this.lineEnd = to;
|
|||
|
|
if (this.writtenTo < from) {
|
|||
|
|
if (this.writtenTo < from - 1 || this.nodes[this.nodes.length - 1] == null)
|
|||
|
|
this.nodes.push(this.blankContent(this.writtenTo, from - 1));
|
|||
|
|
this.nodes.push(null);
|
|||
|
|
}
|
|||
|
|
if (this.pos > from)
|
|||
|
|
this.nodes.push(new HeightMapText(this.pos - from, -1, 0));
|
|||
|
|
this.writtenTo = this.pos;
|
|||
|
|
}
|
|||
|
|
blankContent(from, to) {
|
|||
|
|
let gap = new HeightMapGap(to - from);
|
|||
|
|
if (this.oracle.doc.lineAt(from).to == to)
|
|||
|
|
gap.flags |= 4;
|
|||
|
|
return gap;
|
|||
|
|
}
|
|||
|
|
ensureLine() {
|
|||
|
|
this.enterLine();
|
|||
|
|
let last = this.nodes.length ? this.nodes[this.nodes.length - 1] : null;
|
|||
|
|
if (last instanceof HeightMapText)
|
|||
|
|
return last;
|
|||
|
|
let line = new HeightMapText(0, -1, 0);
|
|||
|
|
this.nodes.push(line);
|
|||
|
|
return line;
|
|||
|
|
}
|
|||
|
|
addBlock(block) {
|
|||
|
|
this.enterLine();
|
|||
|
|
let deco = block.deco;
|
|||
|
|
if (deco && deco.startSide > 0 && !this.isCovered)
|
|||
|
|
this.ensureLine();
|
|||
|
|
this.nodes.push(block);
|
|||
|
|
this.writtenTo = this.pos = this.pos + block.length;
|
|||
|
|
if (deco && deco.endSide > 0)
|
|||
|
|
this.covering = block;
|
|||
|
|
}
|
|||
|
|
addLineDeco(height, breaks, length) {
|
|||
|
|
let line = this.ensureLine();
|
|||
|
|
line.length += length;
|
|||
|
|
line.collapsed += length;
|
|||
|
|
line.widgetHeight = Math.max(line.widgetHeight, height);
|
|||
|
|
line.breaks += breaks;
|
|||
|
|
this.writtenTo = this.pos = this.pos + length;
|
|||
|
|
}
|
|||
|
|
finish(from) {
|
|||
|
|
let last = this.nodes.length == 0 ? null : this.nodes[this.nodes.length - 1];
|
|||
|
|
if (this.lineStart > -1 && !(last instanceof HeightMapText) && !this.isCovered)
|
|||
|
|
this.nodes.push(new HeightMapText(0, -1, 0));
|
|||
|
|
else if (this.writtenTo < this.pos || last == null)
|
|||
|
|
this.nodes.push(this.blankContent(this.writtenTo, this.pos));
|
|||
|
|
let pos = from;
|
|||
|
|
for (let node of this.nodes) {
|
|||
|
|
if (node instanceof HeightMapText)
|
|||
|
|
node.updateHeight(this.oracle, pos);
|
|||
|
|
pos += node ? node.length : 1;
|
|||
|
|
}
|
|||
|
|
return this.nodes;
|
|||
|
|
}
|
|||
|
|
// Always called with a region that on both sides either stretches
|
|||
|
|
// to a line break or the end of the document.
|
|||
|
|
// The returned array uses null to indicate line breaks, but never
|
|||
|
|
// starts or ends in a line break, or has multiple line breaks next
|
|||
|
|
// to each other.
|
|||
|
|
static build(oracle, decorations2, from, to) {
|
|||
|
|
let builder = new _NodeBuilder(from, oracle);
|
|||
|
|
RangeSet.spans(decorations2, from, to, builder, 0);
|
|||
|
|
return builder.finish(from);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function heightRelevantDecoChanges(a, b, diff) {
|
|||
|
|
let comp = new DecorationComparator2();
|
|||
|
|
RangeSet.compare(a, b, diff, comp, 0);
|
|||
|
|
return comp.changes;
|
|||
|
|
}
|
|||
|
|
var DecorationComparator2 = class {
|
|||
|
|
constructor() {
|
|||
|
|
this.changes = [];
|
|||
|
|
}
|
|||
|
|
compareRange() {
|
|||
|
|
}
|
|||
|
|
comparePoint(from, to, a, b) {
|
|||
|
|
if (from < to || a && a.heightRelevant || b && b.heightRelevant)
|
|||
|
|
addRange(from, to, this.changes, 5);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function visiblePixelRange(dom, paddingTop) {
|
|||
|
|
let rect = dom.getBoundingClientRect();
|
|||
|
|
let doc2 = dom.ownerDocument, win = doc2.defaultView || window;
|
|||
|
|
let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
|
|||
|
|
let top2 = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
|
|||
|
|
for (let parent = dom.parentNode; parent && parent != doc2.body; ) {
|
|||
|
|
if (parent.nodeType == 1) {
|
|||
|
|
let elt = parent;
|
|||
|
|
let style = window.getComputedStyle(elt);
|
|||
|
|
if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) && style.overflow != "visible") {
|
|||
|
|
let parentRect = elt.getBoundingClientRect();
|
|||
|
|
left = Math.max(left, parentRect.left);
|
|||
|
|
right = Math.min(right, parentRect.right);
|
|||
|
|
top2 = Math.max(top2, parentRect.top);
|
|||
|
|
bottom = Math.min(parent == dom.parentNode ? win.innerHeight : bottom, parentRect.bottom);
|
|||
|
|
}
|
|||
|
|
parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
|
|||
|
|
} else if (parent.nodeType == 11) {
|
|||
|
|
parent = parent.host;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
left: left - rect.left,
|
|||
|
|
right: Math.max(left, right) - rect.left,
|
|||
|
|
top: top2 - (rect.top + paddingTop),
|
|||
|
|
bottom: Math.max(top2, bottom) - (rect.top + paddingTop)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function inWindow(elt) {
|
|||
|
|
let rect = elt.getBoundingClientRect(), win = elt.ownerDocument.defaultView || window;
|
|||
|
|
return rect.left < win.innerWidth && rect.right > 0 && rect.top < win.innerHeight && rect.bottom > 0;
|
|||
|
|
}
|
|||
|
|
function fullPixelRange(dom, paddingTop) {
|
|||
|
|
let rect = dom.getBoundingClientRect();
|
|||
|
|
return {
|
|||
|
|
left: 0,
|
|||
|
|
right: rect.right - rect.left,
|
|||
|
|
top: paddingTop,
|
|||
|
|
bottom: rect.bottom - (rect.top + paddingTop)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
var LineGap = class {
|
|||
|
|
constructor(from, to, size, displaySize) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
this.size = size;
|
|||
|
|
this.displaySize = displaySize;
|
|||
|
|
}
|
|||
|
|
static same(a, b) {
|
|||
|
|
if (a.length != b.length)
|
|||
|
|
return false;
|
|||
|
|
for (let i = 0; i < a.length; i++) {
|
|||
|
|
let gA = a[i], gB = b[i];
|
|||
|
|
if (gA.from != gB.from || gA.to != gB.to || gA.size != gB.size)
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
draw(viewState, wrapping) {
|
|||
|
|
return Decoration.replace({
|
|||
|
|
widget: new LineGapWidget(this.displaySize * (wrapping ? viewState.scaleY : viewState.scaleX), wrapping)
|
|||
|
|
}).range(this.from, this.to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var LineGapWidget = class extends WidgetType {
|
|||
|
|
constructor(size, vertical) {
|
|||
|
|
super();
|
|||
|
|
this.size = size;
|
|||
|
|
this.vertical = vertical;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.size == this.size && other.vertical == this.vertical;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
let elt = document.createElement("div");
|
|||
|
|
if (this.vertical) {
|
|||
|
|
elt.style.height = this.size + "px";
|
|||
|
|
} else {
|
|||
|
|
elt.style.width = this.size + "px";
|
|||
|
|
elt.style.height = "2px";
|
|||
|
|
elt.style.display = "inline-block";
|
|||
|
|
}
|
|||
|
|
return elt;
|
|||
|
|
}
|
|||
|
|
get estimatedHeight() {
|
|||
|
|
return this.vertical ? this.size : -1;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var ViewState = class {
|
|||
|
|
constructor(view, state) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.state = state;
|
|||
|
|
this.pixelViewport = { left: 0, right: window.innerWidth, top: 0, bottom: 0 };
|
|||
|
|
this.inView = true;
|
|||
|
|
this.paddingTop = 0;
|
|||
|
|
this.paddingBottom = 0;
|
|||
|
|
this.contentDOMWidth = 0;
|
|||
|
|
this.contentDOMHeight = 0;
|
|||
|
|
this.editorHeight = 0;
|
|||
|
|
this.editorWidth = 0;
|
|||
|
|
this.scaleX = 1;
|
|||
|
|
this.scaleY = 1;
|
|||
|
|
this.scrollOffset = 0;
|
|||
|
|
this.scrolledToBottom = false;
|
|||
|
|
this.scrollAnchorPos = 0;
|
|||
|
|
this.scrollAnchorHeight = -1;
|
|||
|
|
this.scaler = IdScaler;
|
|||
|
|
this.scrollTarget = null;
|
|||
|
|
this.printing = false;
|
|||
|
|
this.mustMeasureContent = true;
|
|||
|
|
this.defaultTextDirection = Direction.LTR;
|
|||
|
|
this.visibleRanges = [];
|
|||
|
|
this.mustEnforceCursorAssoc = false;
|
|||
|
|
let guessWrapping = state.facet(contentAttributes).some((v) => typeof v != "function" && v.class == "cm-lineWrapping");
|
|||
|
|
this.heightOracle = new HeightOracle(guessWrapping);
|
|||
|
|
this.stateDeco = staticDeco(state);
|
|||
|
|
this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
|
|||
|
|
for (let i = 0; i < 2; i++) {
|
|||
|
|
this.viewport = this.getViewport(0, null);
|
|||
|
|
if (!this.updateForViewport())
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
this.updateViewportLines();
|
|||
|
|
this.lineGaps = this.ensureLineGaps([]);
|
|||
|
|
this.lineGapDeco = Decoration.set(this.lineGaps.map((gap) => gap.draw(this, false)));
|
|||
|
|
this.scrollParent = view.scrollDOM;
|
|||
|
|
this.computeVisibleRanges();
|
|||
|
|
}
|
|||
|
|
updateForViewport() {
|
|||
|
|
let viewports = [this.viewport], { main } = this.state.selection;
|
|||
|
|
for (let i = 0; i <= 1; i++) {
|
|||
|
|
let pos = i ? main.head : main.anchor;
|
|||
|
|
if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
|
|||
|
|
let { from, to } = this.lineBlockAt(pos);
|
|||
|
|
viewports.push(new Viewport(from, to));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.viewports = viewports.sort((a, b) => a.from - b.from);
|
|||
|
|
return this.updateScaler();
|
|||
|
|
}
|
|||
|
|
updateScaler() {
|
|||
|
|
let scaler = this.scaler;
|
|||
|
|
this.scaler = this.heightMap.height <= 7e6 ? IdScaler : new BigScaler(this.heightOracle, this.heightMap, this.viewports);
|
|||
|
|
return scaler.eq(this.scaler) ? 0 : 2;
|
|||
|
|
}
|
|||
|
|
updateViewportLines() {
|
|||
|
|
this.viewportLines = [];
|
|||
|
|
this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.heightOracle.setDoc(this.state.doc), 0, 0, (block) => {
|
|||
|
|
this.viewportLines.push(scaleBlock(block, this.scaler));
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
update(update, scrollTarget = null) {
|
|||
|
|
this.state = update.state;
|
|||
|
|
let prevDeco = this.stateDeco;
|
|||
|
|
this.stateDeco = staticDeco(this.state);
|
|||
|
|
let contentChanges = update.changedRanges;
|
|||
|
|
let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : ChangeSet.empty(this.state.doc.length)));
|
|||
|
|
let prevHeight = this.heightMap.height;
|
|||
|
|
let scrollAnchor = this.scrolledToBottom ? null : this.scrollAnchorAt(this.scrollOffset);
|
|||
|
|
clearHeightChangeFlag();
|
|||
|
|
this.heightMap = this.heightMap.applyChanges(this.stateDeco, update.startState.doc, this.heightOracle.setDoc(this.state.doc), heightChanges);
|
|||
|
|
if (this.heightMap.height != prevHeight || heightChangeFlag)
|
|||
|
|
update.flags |= 2;
|
|||
|
|
if (scrollAnchor) {
|
|||
|
|
this.scrollAnchorPos = update.changes.mapPos(scrollAnchor.from, -1);
|
|||
|
|
this.scrollAnchorHeight = scrollAnchor.top;
|
|||
|
|
} else {
|
|||
|
|
this.scrollAnchorPos = -1;
|
|||
|
|
this.scrollAnchorHeight = prevHeight;
|
|||
|
|
}
|
|||
|
|
let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
|
|||
|
|
if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) || !this.viewportIsAppropriate(viewport))
|
|||
|
|
viewport = this.getViewport(0, scrollTarget);
|
|||
|
|
let viewportChange = viewport.from != this.viewport.from || viewport.to != this.viewport.to;
|
|||
|
|
this.viewport = viewport;
|
|||
|
|
update.flags |= this.updateForViewport();
|
|||
|
|
if (viewportChange || !update.changes.empty || update.flags & 2)
|
|||
|
|
this.updateViewportLines();
|
|||
|
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 2e3 << 1)
|
|||
|
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|||
|
|
update.flags |= this.computeVisibleRanges(update.changes);
|
|||
|
|
if (scrollTarget)
|
|||
|
|
this.scrollTarget = scrollTarget;
|
|||
|
|
if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping && update.state.selection.main.empty && update.state.selection.main.assoc && !update.state.facet(nativeSelectionHidden))
|
|||
|
|
this.mustEnforceCursorAssoc = true;
|
|||
|
|
}
|
|||
|
|
measure() {
|
|||
|
|
let { view } = this, dom = view.contentDOM, style = window.getComputedStyle(dom);
|
|||
|
|
let oracle = this.heightOracle;
|
|||
|
|
let whiteSpace = style.whiteSpace;
|
|||
|
|
this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
|
|||
|
|
let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent === "refresh";
|
|||
|
|
let domRect = dom.getBoundingClientRect();
|
|||
|
|
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
|
|||
|
|
this.contentDOMHeight = domRect.height;
|
|||
|
|
this.mustMeasureContent = false;
|
|||
|
|
let result = 0, bias = 0;
|
|||
|
|
if (domRect.width && domRect.height) {
|
|||
|
|
let { scaleX, scaleY } = getScale(dom, domRect);
|
|||
|
|
if (scaleX > 5e-3 && Math.abs(this.scaleX - scaleX) > 5e-3 || scaleY > 5e-3 && Math.abs(this.scaleY - scaleY) > 5e-3) {
|
|||
|
|
this.scaleX = scaleX;
|
|||
|
|
this.scaleY = scaleY;
|
|||
|
|
result |= 16;
|
|||
|
|
refresh = measureContent = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let paddingTop = (parseInt(style.paddingTop) || 0) * this.scaleY;
|
|||
|
|
let paddingBottom = (parseInt(style.paddingBottom) || 0) * this.scaleY;
|
|||
|
|
if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
|
|||
|
|
this.paddingTop = paddingTop;
|
|||
|
|
this.paddingBottom = paddingBottom;
|
|||
|
|
result |= 16 | 2;
|
|||
|
|
}
|
|||
|
|
if (this.editorWidth != view.scrollDOM.clientWidth) {
|
|||
|
|
if (oracle.lineWrapping)
|
|||
|
|
measureContent = true;
|
|||
|
|
this.editorWidth = view.scrollDOM.clientWidth;
|
|||
|
|
result |= 16;
|
|||
|
|
}
|
|||
|
|
let scrollParent = scrollableParents(this.view.contentDOM, false).y;
|
|||
|
|
if (scrollParent != this.scrollParent) {
|
|||
|
|
this.scrollParent = scrollParent;
|
|||
|
|
this.scrollAnchorHeight = -1;
|
|||
|
|
this.scrollOffset = 0;
|
|||
|
|
}
|
|||
|
|
let scrollOffset = this.getScrollOffset();
|
|||
|
|
if (this.scrollOffset != scrollOffset) {
|
|||
|
|
this.scrollAnchorHeight = -1;
|
|||
|
|
this.scrollOffset = scrollOffset;
|
|||
|
|
}
|
|||
|
|
this.scrolledToBottom = isScrolledToBottom(this.scrollParent || view.win);
|
|||
|
|
let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
|
|||
|
|
let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
|
|||
|
|
this.pixelViewport = pixelViewport;
|
|||
|
|
let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|||
|
|
if (inView != this.inView) {
|
|||
|
|
this.inView = inView;
|
|||
|
|
if (inView)
|
|||
|
|
measureContent = true;
|
|||
|
|
}
|
|||
|
|
if (!this.inView && !this.scrollTarget && !inWindow(view.dom))
|
|||
|
|
return 0;
|
|||
|
|
let contentWidth = domRect.width;
|
|||
|
|
if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
|
|||
|
|
this.contentDOMWidth = domRect.width;
|
|||
|
|
this.editorHeight = view.scrollDOM.clientHeight;
|
|||
|
|
result |= 16;
|
|||
|
|
}
|
|||
|
|
if (measureContent) {
|
|||
|
|
let lineHeights = view.docView.measureVisibleLineHeights(this.viewport);
|
|||
|
|
if (oracle.mustRefreshForHeights(lineHeights))
|
|||
|
|
refresh = true;
|
|||
|
|
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|||
|
|
let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
|
|||
|
|
refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, Math.max(5, contentWidth / charWidth), lineHeights);
|
|||
|
|
if (refresh) {
|
|||
|
|
view.docView.minWidth = 0;
|
|||
|
|
result |= 16;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (dTop > 0 && dBottom > 0)
|
|||
|
|
bias = Math.max(dTop, dBottom);
|
|||
|
|
else if (dTop < 0 && dBottom < 0)
|
|||
|
|
bias = Math.min(dTop, dBottom);
|
|||
|
|
clearHeightChangeFlag();
|
|||
|
|
for (let vp of this.viewports) {
|
|||
|
|
let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
|
|||
|
|
this.heightMap = (refresh ? HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle, [new ChangedRange(0, 0, 0, view.state.doc.length)]) : this.heightMap).updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
|
|||
|
|
}
|
|||
|
|
if (heightChangeFlag)
|
|||
|
|
result |= 2;
|
|||
|
|
}
|
|||
|
|
let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) || this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
|
|||
|
|
if (viewportChange) {
|
|||
|
|
if (result & 2)
|
|||
|
|
result |= this.updateScaler();
|
|||
|
|
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|||
|
|
result |= this.updateForViewport();
|
|||
|
|
}
|
|||
|
|
if (result & 2 || viewportChange)
|
|||
|
|
this.updateViewportLines();
|
|||
|
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 2e3 << 1)
|
|||
|
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
|
|||
|
|
result |= this.computeVisibleRanges();
|
|||
|
|
if (this.mustEnforceCursorAssoc) {
|
|||
|
|
this.mustEnforceCursorAssoc = false;
|
|||
|
|
view.docView.enforceCursorAssoc();
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
get visibleTop() {
|
|||
|
|
return this.scaler.fromDOM(this.pixelViewport.top);
|
|||
|
|
}
|
|||
|
|
get visibleBottom() {
|
|||
|
|
return this.scaler.fromDOM(this.pixelViewport.bottom);
|
|||
|
|
}
|
|||
|
|
getViewport(bias, scrollTarget) {
|
|||
|
|
let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1e3 / 2));
|
|||
|
|
let map = this.heightMap, oracle = this.heightOracle;
|
|||
|
|
let { visibleTop, visibleBottom } = this;
|
|||
|
|
let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1e3, QueryType.ByHeight, oracle, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1e3, QueryType.ByHeight, oracle, 0, 0).to);
|
|||
|
|
if (scrollTarget) {
|
|||
|
|
let { head } = scrollTarget.range;
|
|||
|
|
if (head < viewport.from || head > viewport.to) {
|
|||
|
|
let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
|
|||
|
|
let block = map.lineAt(head, QueryType.ByPos, oracle, 0, 0), topPos;
|
|||
|
|
if (scrollTarget.y == "center")
|
|||
|
|
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|||
|
|
else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
|
|||
|
|
topPos = block.top;
|
|||
|
|
else
|
|||
|
|
topPos = block.bottom - viewHeight;
|
|||
|
|
viewport = new Viewport(map.lineAt(topPos - 1e3 / 2, QueryType.ByHeight, oracle, 0, 0).from, map.lineAt(topPos + viewHeight + 1e3 / 2, QueryType.ByHeight, oracle, 0, 0).to);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return viewport;
|
|||
|
|
}
|
|||
|
|
mapViewport(viewport, changes) {
|
|||
|
|
let from = changes.mapPos(viewport.from, -1), to = changes.mapPos(viewport.to, 1);
|
|||
|
|
return new Viewport(this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0).from, this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0).to);
|
|||
|
|
}
|
|||
|
|
// Checks if a given viewport covers the visible part of the
|
|||
|
|
// document and not too much beyond that.
|
|||
|
|
viewportIsAppropriate({ from, to }, bias = 0) {
|
|||
|
|
if (!this.inView)
|
|||
|
|
return true;
|
|||
|
|
let { top: top2 } = this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0);
|
|||
|
|
let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0);
|
|||
|
|
let { visibleTop, visibleBottom } = this;
|
|||
|
|
return (from == 0 || top2 <= visibleTop - Math.max(10, Math.min(
|
|||
|
|
-bias,
|
|||
|
|
250
|
|||
|
|
/* VP.MaxCoverMargin */
|
|||
|
|
))) && (to == this.state.doc.length || bottom >= visibleBottom + Math.max(10, Math.min(
|
|||
|
|
bias,
|
|||
|
|
250
|
|||
|
|
/* VP.MaxCoverMargin */
|
|||
|
|
))) && (top2 > visibleTop - 2 * 1e3 && bottom < visibleBottom + 2 * 1e3);
|
|||
|
|
}
|
|||
|
|
mapLineGaps(gaps, changes) {
|
|||
|
|
if (!gaps.length || changes.empty)
|
|||
|
|
return gaps;
|
|||
|
|
let mapped = [];
|
|||
|
|
for (let gap of gaps)
|
|||
|
|
if (!changes.touchesRange(gap.from, gap.to))
|
|||
|
|
mapped.push(new LineGap(changes.mapPos(gap.from), changes.mapPos(gap.to), gap.size, gap.displaySize));
|
|||
|
|
return mapped;
|
|||
|
|
}
|
|||
|
|
// Computes positions in the viewport where the start or end of a
|
|||
|
|
// line should be hidden, trying to reuse existing line gaps when
|
|||
|
|
// appropriate to avoid unneccesary redraws.
|
|||
|
|
// Uses crude character-counting for the positioning and sizing,
|
|||
|
|
// since actual DOM coordinates aren't always available and
|
|||
|
|
// predictable. Relies on generous margins (see LG.Margin) to hide
|
|||
|
|
// the artifacts this might produce from the user.
|
|||
|
|
ensureLineGaps(current, mayMeasure) {
|
|||
|
|
let wrapping = this.heightOracle.lineWrapping;
|
|||
|
|
let margin = wrapping ? 1e4 : 2e3, halfMargin = margin >> 1, doubleMargin = margin << 1;
|
|||
|
|
if (this.defaultTextDirection != Direction.LTR && !wrapping)
|
|||
|
|
return [];
|
|||
|
|
let gaps = [];
|
|||
|
|
let addGap = (from, to, line, structure) => {
|
|||
|
|
if (to - from < halfMargin)
|
|||
|
|
return;
|
|||
|
|
let sel = this.state.selection.main, avoid = [sel.from];
|
|||
|
|
if (!sel.empty)
|
|||
|
|
avoid.push(sel.to);
|
|||
|
|
for (let pos of avoid) {
|
|||
|
|
if (pos > from && pos < to) {
|
|||
|
|
addGap(from, pos - 10, line, structure);
|
|||
|
|
addGap(pos + 10, to, line, structure);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let gap = find(current, (gap2) => gap2.from >= line.from && gap2.to <= line.to && Math.abs(gap2.from - from) < halfMargin && Math.abs(gap2.to - to) < halfMargin && !avoid.some((pos) => gap2.from < pos && gap2.to > pos));
|
|||
|
|
if (!gap) {
|
|||
|
|
if (to < line.to && mayMeasure && wrapping && mayMeasure.visibleRanges.some((r) => r.from <= to && r.to >= to)) {
|
|||
|
|
let lineStart = mayMeasure.moveToLineBoundary(EditorSelection.cursor(to), false, true).head;
|
|||
|
|
if (lineStart > from)
|
|||
|
|
to = lineStart;
|
|||
|
|
}
|
|||
|
|
let size = this.gapSize(line, from, to, structure);
|
|||
|
|
let displaySize = wrapping || size < 2e6 ? size : 2e6;
|
|||
|
|
gap = new LineGap(from, to, size, displaySize);
|
|||
|
|
}
|
|||
|
|
gaps.push(gap);
|
|||
|
|
};
|
|||
|
|
let checkLine = (line) => {
|
|||
|
|
if (line.length < doubleMargin || line.type != BlockType.Text)
|
|||
|
|
return;
|
|||
|
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|||
|
|
if (structure.total < doubleMargin)
|
|||
|
|
return;
|
|||
|
|
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|||
|
|
let viewFrom, viewTo;
|
|||
|
|
if (wrapping) {
|
|||
|
|
let marginHeight = margin / this.heightOracle.lineLength * this.heightOracle.lineHeight;
|
|||
|
|
let top2, bot;
|
|||
|
|
if (target != null) {
|
|||
|
|
let targetFrac = findFraction(structure, target);
|
|||
|
|
let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
|
|||
|
|
top2 = targetFrac - spaceFrac;
|
|||
|
|
bot = targetFrac + spaceFrac;
|
|||
|
|
} else {
|
|||
|
|
top2 = (this.visibleTop - line.top - marginHeight) / line.height;
|
|||
|
|
bot = (this.visibleBottom - line.top + marginHeight) / line.height;
|
|||
|
|
}
|
|||
|
|
viewFrom = findPosition(structure, top2);
|
|||
|
|
viewTo = findPosition(structure, bot);
|
|||
|
|
} else {
|
|||
|
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|||
|
|
let marginWidth = margin * this.heightOracle.charWidth;
|
|||
|
|
let horizOffset = 0;
|
|||
|
|
if (totalWidth > 2e6)
|
|||
|
|
for (let old of current) {
|
|||
|
|
if (old.from >= line.from && old.from < line.to && old.size != old.displaySize && old.from * this.heightOracle.charWidth + horizOffset < this.pixelViewport.left)
|
|||
|
|
horizOffset = old.size - old.displaySize;
|
|||
|
|
}
|
|||
|
|
let pxLeft = this.pixelViewport.left + horizOffset, pxRight = this.pixelViewport.right + horizOffset;
|
|||
|
|
let left, right;
|
|||
|
|
if (target != null) {
|
|||
|
|
let targetFrac = findFraction(structure, target);
|
|||
|
|
let spaceFrac = ((pxRight - pxLeft) / 2 + marginWidth) / totalWidth;
|
|||
|
|
left = targetFrac - spaceFrac;
|
|||
|
|
right = targetFrac + spaceFrac;
|
|||
|
|
} else {
|
|||
|
|
left = (pxLeft - marginWidth) / totalWidth;
|
|||
|
|
right = (pxRight + marginWidth) / totalWidth;
|
|||
|
|
}
|
|||
|
|
viewFrom = findPosition(structure, left);
|
|||
|
|
viewTo = findPosition(structure, right);
|
|||
|
|
}
|
|||
|
|
if (viewFrom > line.from)
|
|||
|
|
addGap(line.from, viewFrom, line, structure);
|
|||
|
|
if (viewTo < line.to)
|
|||
|
|
addGap(viewTo, line.to, line, structure);
|
|||
|
|
};
|
|||
|
|
for (let line of this.viewportLines) {
|
|||
|
|
if (Array.isArray(line.type))
|
|||
|
|
line.type.forEach(checkLine);
|
|||
|
|
else
|
|||
|
|
checkLine(line);
|
|||
|
|
}
|
|||
|
|
return gaps;
|
|||
|
|
}
|
|||
|
|
gapSize(line, from, to, structure) {
|
|||
|
|
let fraction = findFraction(structure, to) - findFraction(structure, from);
|
|||
|
|
if (this.heightOracle.lineWrapping) {
|
|||
|
|
return line.height * fraction;
|
|||
|
|
} else {
|
|||
|
|
return structure.total * this.heightOracle.charWidth * fraction;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
updateLineGaps(gaps) {
|
|||
|
|
if (!LineGap.same(gaps, this.lineGaps)) {
|
|||
|
|
this.lineGaps = gaps;
|
|||
|
|
this.lineGapDeco = Decoration.set(gaps.map((gap) => gap.draw(this, this.heightOracle.lineWrapping)));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
computeVisibleRanges(changes) {
|
|||
|
|
let deco = this.stateDeco;
|
|||
|
|
if (this.lineGaps.length)
|
|||
|
|
deco = deco.concat(this.lineGapDeco);
|
|||
|
|
let ranges = [];
|
|||
|
|
RangeSet.spans(deco, this.viewport.from, this.viewport.to, {
|
|||
|
|
span(from, to) {
|
|||
|
|
ranges.push({ from, to });
|
|||
|
|
},
|
|||
|
|
point() {
|
|||
|
|
}
|
|||
|
|
}, 20);
|
|||
|
|
let changed = 0;
|
|||
|
|
if (ranges.length != this.visibleRanges.length) {
|
|||
|
|
changed = 8 | 4;
|
|||
|
|
} else {
|
|||
|
|
for (let i = 0; i < ranges.length && !(changed & 8); i++) {
|
|||
|
|
let old = this.visibleRanges[i], nw = ranges[i];
|
|||
|
|
if (old.from != nw.from || old.to != nw.to) {
|
|||
|
|
changed |= 4;
|
|||
|
|
if (!(changes && changes.mapPos(old.from, -1) == nw.from && changes.mapPos(old.to, 1) == nw.to))
|
|||
|
|
changed |= 8;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.visibleRanges = ranges;
|
|||
|
|
return changed;
|
|||
|
|
}
|
|||
|
|
lineBlockAt(pos) {
|
|||
|
|
return pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find((b) => b.from <= pos && b.to >= pos) || scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.heightOracle, 0, 0), this.scaler);
|
|||
|
|
}
|
|||
|
|
lineBlockAtHeight(height) {
|
|||
|
|
return height >= this.viewportLines[0].top && height <= this.viewportLines[this.viewportLines.length - 1].bottom && this.viewportLines.find((l) => l.top <= height && l.bottom >= height) || scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
|
|||
|
|
}
|
|||
|
|
getScrollOffset() {
|
|||
|
|
let base2 = this.scrollParent == this.view.scrollDOM ? this.scrollParent.scrollTop : (this.scrollParent ? this.scrollParent.getBoundingClientRect().top : 0) - this.view.contentDOM.getBoundingClientRect().top;
|
|||
|
|
return base2 * this.scaleY;
|
|||
|
|
}
|
|||
|
|
scrollAnchorAt(scrollOffset) {
|
|||
|
|
let block = this.lineBlockAtHeight(scrollOffset + 8);
|
|||
|
|
return block.from >= this.viewport.from || this.viewportLines[0].top - scrollOffset > 200 ? block : this.viewportLines[0];
|
|||
|
|
}
|
|||
|
|
elementAtHeight(height) {
|
|||
|
|
return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.heightOracle, 0, 0), this.scaler);
|
|||
|
|
}
|
|||
|
|
get docHeight() {
|
|||
|
|
return this.scaler.toDOM(this.heightMap.height);
|
|||
|
|
}
|
|||
|
|
get contentHeight() {
|
|||
|
|
return this.docHeight + this.paddingTop + this.paddingBottom;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var Viewport = class {
|
|||
|
|
constructor(from, to) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function lineStructure(from, to, stateDeco) {
|
|||
|
|
let ranges = [], pos = from, total = 0;
|
|||
|
|
RangeSet.spans(stateDeco, from, to, {
|
|||
|
|
span() {
|
|||
|
|
},
|
|||
|
|
point(from2, to2) {
|
|||
|
|
if (from2 > pos) {
|
|||
|
|
ranges.push({ from: pos, to: from2 });
|
|||
|
|
total += from2 - pos;
|
|||
|
|
}
|
|||
|
|
pos = to2;
|
|||
|
|
}
|
|||
|
|
}, 20);
|
|||
|
|
if (pos < to) {
|
|||
|
|
ranges.push({ from: pos, to });
|
|||
|
|
total += to - pos;
|
|||
|
|
}
|
|||
|
|
return { total, ranges };
|
|||
|
|
}
|
|||
|
|
function findPosition({ total, ranges }, ratio) {
|
|||
|
|
if (ratio <= 0)
|
|||
|
|
return ranges[0].from;
|
|||
|
|
if (ratio >= 1)
|
|||
|
|
return ranges[ranges.length - 1].to;
|
|||
|
|
let dist2 = Math.floor(total * ratio);
|
|||
|
|
for (let i = 0; ; i++) {
|
|||
|
|
let { from, to } = ranges[i], size = to - from;
|
|||
|
|
if (dist2 <= size)
|
|||
|
|
return from + dist2;
|
|||
|
|
dist2 -= size;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function findFraction(structure, pos) {
|
|||
|
|
let counted = 0;
|
|||
|
|
for (let { from, to } of structure.ranges) {
|
|||
|
|
if (pos <= to) {
|
|||
|
|
counted += pos - from;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
counted += to - from;
|
|||
|
|
}
|
|||
|
|
return counted / structure.total;
|
|||
|
|
}
|
|||
|
|
function find(array, f) {
|
|||
|
|
for (let val of array)
|
|||
|
|
if (f(val))
|
|||
|
|
return val;
|
|||
|
|
return void 0;
|
|||
|
|
}
|
|||
|
|
var IdScaler = {
|
|||
|
|
toDOM(n) {
|
|||
|
|
return n;
|
|||
|
|
},
|
|||
|
|
fromDOM(n) {
|
|||
|
|
return n;
|
|||
|
|
},
|
|||
|
|
scale: 1,
|
|||
|
|
eq(other) {
|
|||
|
|
return other == this;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function staticDeco(state) {
|
|||
|
|
let deco = state.facet(decorations).filter((d) => typeof d != "function");
|
|||
|
|
let outer = state.facet(outerDecorations).filter((d) => typeof d != "function");
|
|||
|
|
if (outer.length)
|
|||
|
|
deco.push(RangeSet.join(outer));
|
|||
|
|
return deco;
|
|||
|
|
}
|
|||
|
|
var BigScaler = class _BigScaler {
|
|||
|
|
constructor(oracle, heightMap, viewports) {
|
|||
|
|
let vpHeight = 0, base2 = 0, domBase = 0;
|
|||
|
|
this.viewports = viewports.map(({ from, to }) => {
|
|||
|
|
let top2 = heightMap.lineAt(from, QueryType.ByPos, oracle, 0, 0).top;
|
|||
|
|
let bottom = heightMap.lineAt(to, QueryType.ByPos, oracle, 0, 0).bottom;
|
|||
|
|
vpHeight += bottom - top2;
|
|||
|
|
return { from, to, top: top2, bottom, domTop: 0, domBottom: 0 };
|
|||
|
|
});
|
|||
|
|
this.scale = (7e6 - vpHeight) / (heightMap.height - vpHeight);
|
|||
|
|
for (let obj of this.viewports) {
|
|||
|
|
obj.domTop = domBase + (obj.top - base2) * this.scale;
|
|||
|
|
domBase = obj.domBottom = obj.domTop + (obj.bottom - obj.top);
|
|||
|
|
base2 = obj.bottom;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
toDOM(n) {
|
|||
|
|
for (let i = 0, base2 = 0, domBase = 0; ; i++) {
|
|||
|
|
let vp = i < this.viewports.length ? this.viewports[i] : null;
|
|||
|
|
if (!vp || n < vp.top)
|
|||
|
|
return domBase + (n - base2) * this.scale;
|
|||
|
|
if (n <= vp.bottom)
|
|||
|
|
return vp.domTop + (n - vp.top);
|
|||
|
|
base2 = vp.bottom;
|
|||
|
|
domBase = vp.domBottom;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
fromDOM(n) {
|
|||
|
|
for (let i = 0, base2 = 0, domBase = 0; ; i++) {
|
|||
|
|
let vp = i < this.viewports.length ? this.viewports[i] : null;
|
|||
|
|
if (!vp || n < vp.domTop)
|
|||
|
|
return base2 + (n - domBase) / this.scale;
|
|||
|
|
if (n <= vp.domBottom)
|
|||
|
|
return vp.top + (n - vp.domTop);
|
|||
|
|
base2 = vp.bottom;
|
|||
|
|
domBase = vp.domBottom;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
if (!(other instanceof _BigScaler))
|
|||
|
|
return false;
|
|||
|
|
return this.scale == other.scale && this.viewports.length == other.viewports.length && this.viewports.every((vp, i) => vp.from == other.viewports[i].from && vp.to == other.viewports[i].to);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function scaleBlock(block, scaler) {
|
|||
|
|
if (scaler.scale == 1)
|
|||
|
|
return block;
|
|||
|
|
let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
|
|||
|
|
return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block._content) ? block._content.map((b) => scaleBlock(b, scaler)) : block._content);
|
|||
|
|
}
|
|||
|
|
var theme = Facet.define({ combine: (strs) => strs.join(" ") });
|
|||
|
|
var darkTheme = Facet.define({ combine: (values) => values.indexOf(true) > -1 });
|
|||
|
|
var baseThemeID = StyleModule.newName();
|
|||
|
|
var baseLightID = StyleModule.newName();
|
|||
|
|
var baseDarkID = StyleModule.newName();
|
|||
|
|
var lightDarkIDs = { "&light": "." + baseLightID, "&dark": "." + baseDarkID };
|
|||
|
|
function buildTheme(main, spec, scopes) {
|
|||
|
|
return new StyleModule(spec, {
|
|||
|
|
finish(sel) {
|
|||
|
|
return /&/.test(sel) ? sel.replace(/&\w*/, (m) => {
|
|||
|
|
if (m == "&")
|
|||
|
|
return main;
|
|||
|
|
if (!scopes || !scopes[m])
|
|||
|
|
throw new RangeError(`Unsupported selector: ${m}`);
|
|||
|
|
return scopes[m];
|
|||
|
|
}) : main + " " + sel;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
var baseTheme$1 = buildTheme("." + baseThemeID, {
|
|||
|
|
"&": {
|
|||
|
|
position: "relative !important",
|
|||
|
|
boxSizing: "border-box",
|
|||
|
|
"&.cm-focused": {
|
|||
|
|
// Provide a simple default outline to make sure a focused
|
|||
|
|
// editor is visually distinct. Can't leave the default behavior
|
|||
|
|
// because that will apply to the content element, which is
|
|||
|
|
// inside the scrollable container and doesn't include the
|
|||
|
|
// gutters. We also can't use an 'auto' outline, since those
|
|||
|
|
// are, for some reason, drawn behind the element content, which
|
|||
|
|
// will cause things like the active line background to cover
|
|||
|
|
// the outline (#297).
|
|||
|
|
outline: "1px dotted #212121"
|
|||
|
|
},
|
|||
|
|
display: "flex !important",
|
|||
|
|
flexDirection: "column"
|
|||
|
|
},
|
|||
|
|
".cm-scroller": {
|
|||
|
|
display: "flex !important",
|
|||
|
|
alignItems: "flex-start !important",
|
|||
|
|
fontFamily: "monospace",
|
|||
|
|
lineHeight: 1.4,
|
|||
|
|
height: "100%",
|
|||
|
|
overflowX: "auto",
|
|||
|
|
position: "relative",
|
|||
|
|
zIndex: 0,
|
|||
|
|
overflowAnchor: "none"
|
|||
|
|
},
|
|||
|
|
".cm-content": {
|
|||
|
|
margin: 0,
|
|||
|
|
flexGrow: 2,
|
|||
|
|
flexShrink: 0,
|
|||
|
|
display: "block",
|
|||
|
|
whiteSpace: "pre",
|
|||
|
|
wordWrap: "normal",
|
|||
|
|
// Issue #456
|
|||
|
|
boxSizing: "border-box",
|
|||
|
|
minHeight: "100%",
|
|||
|
|
padding: "4px 0",
|
|||
|
|
outline: "none",
|
|||
|
|
"&[contenteditable=true]": {
|
|||
|
|
WebkitUserModify: "read-write-plaintext-only"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
".cm-lineWrapping": {
|
|||
|
|
whiteSpace_fallback: "pre-wrap",
|
|||
|
|
// For IE
|
|||
|
|
whiteSpace: "break-spaces",
|
|||
|
|
wordBreak: "break-word",
|
|||
|
|
// For Safari, which doesn't support overflow-wrap: anywhere
|
|||
|
|
overflowWrap: "anywhere",
|
|||
|
|
flexShrink: 1
|
|||
|
|
},
|
|||
|
|
"&light .cm-content": { caretColor: "black" },
|
|||
|
|
"&dark .cm-content": { caretColor: "white" },
|
|||
|
|
".cm-line": {
|
|||
|
|
display: "block",
|
|||
|
|
padding: "0 2px 0 6px"
|
|||
|
|
},
|
|||
|
|
".cm-layer": {
|
|||
|
|
position: "absolute",
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
contain: "size style",
|
|||
|
|
"& > *": {
|
|||
|
|
position: "absolute"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"&light .cm-selectionBackground": {
|
|||
|
|
background: "#d9d9d9"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-selectionBackground": {
|
|||
|
|
background: "#222"
|
|||
|
|
},
|
|||
|
|
"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
|
|||
|
|
background: "#d7d4f0"
|
|||
|
|
},
|
|||
|
|
"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
|
|||
|
|
background: "#233"
|
|||
|
|
},
|
|||
|
|
".cm-cursorLayer": {
|
|||
|
|
pointerEvents: "none"
|
|||
|
|
},
|
|||
|
|
"&.cm-focused > .cm-scroller > .cm-cursorLayer": {
|
|||
|
|
animation: "steps(1) cm-blink 1.2s infinite"
|
|||
|
|
},
|
|||
|
|
// Two animations defined so that we can switch between them to
|
|||
|
|
// restart the animation without forcing another style
|
|||
|
|
// recomputation.
|
|||
|
|
"@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|||
|
|
"@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
|
|||
|
|
".cm-cursor, .cm-dropCursor": {
|
|||
|
|
borderLeft: "1.2px solid black",
|
|||
|
|
marginLeft: "-0.6px",
|
|||
|
|
pointerEvents: "none"
|
|||
|
|
},
|
|||
|
|
".cm-cursor": {
|
|||
|
|
display: "none"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-cursor": {
|
|||
|
|
borderLeftColor: "#ddd"
|
|||
|
|
},
|
|||
|
|
".cm-selectionHandle": {
|
|||
|
|
backgroundColor: "currentColor",
|
|||
|
|
width: "1.5px"
|
|||
|
|
},
|
|||
|
|
".cm-selectionHandle-start::before, .cm-selectionHandle-end::before": {
|
|||
|
|
content: '""',
|
|||
|
|
backgroundColor: "inherit",
|
|||
|
|
borderRadius: "50%",
|
|||
|
|
width: "8px",
|
|||
|
|
height: "8px",
|
|||
|
|
position: "absolute",
|
|||
|
|
left: "-3.25px"
|
|||
|
|
},
|
|||
|
|
".cm-selectionHandle-start::before": { top: "-8px" },
|
|||
|
|
".cm-selectionHandle-end::before": { bottom: "-8px" },
|
|||
|
|
".cm-dropCursor": {
|
|||
|
|
position: "absolute"
|
|||
|
|
},
|
|||
|
|
"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
|
|||
|
|
display: "block"
|
|||
|
|
},
|
|||
|
|
".cm-iso": {
|
|||
|
|
unicodeBidi: "isolate"
|
|||
|
|
},
|
|||
|
|
".cm-announced": {
|
|||
|
|
position: "fixed",
|
|||
|
|
top: "-10000px"
|
|||
|
|
},
|
|||
|
|
"@media print": {
|
|||
|
|
".cm-announced": { display: "none" }
|
|||
|
|
},
|
|||
|
|
"&light .cm-activeLine": { backgroundColor: "#cceeff44" },
|
|||
|
|
"&dark .cm-activeLine": { backgroundColor: "#99eeff33" },
|
|||
|
|
"&light .cm-specialChar": { color: "red" },
|
|||
|
|
"&dark .cm-specialChar": { color: "#f78" },
|
|||
|
|
".cm-gutters": {
|
|||
|
|
flexShrink: 0,
|
|||
|
|
display: "flex",
|
|||
|
|
height: "100%",
|
|||
|
|
boxSizing: "border-box",
|
|||
|
|
zIndex: 200
|
|||
|
|
},
|
|||
|
|
".cm-gutters-before": { insetInlineStart: 0 },
|
|||
|
|
".cm-gutters-after": { insetInlineEnd: 0 },
|
|||
|
|
"&light .cm-gutters": {
|
|||
|
|
backgroundColor: "#f5f5f5",
|
|||
|
|
color: "#6c6c6c",
|
|||
|
|
border: "0px solid #ddd",
|
|||
|
|
"&.cm-gutters-before": { borderRightWidth: "1px" },
|
|||
|
|
"&.cm-gutters-after": { borderLeftWidth: "1px" }
|
|||
|
|
},
|
|||
|
|
"&dark .cm-gutters": {
|
|||
|
|
backgroundColor: "#333338",
|
|||
|
|
color: "#ccc"
|
|||
|
|
},
|
|||
|
|
".cm-gutter": {
|
|||
|
|
display: "flex !important",
|
|||
|
|
// Necessary -- prevents margin collapsing
|
|||
|
|
flexDirection: "column",
|
|||
|
|
flexShrink: 0,
|
|||
|
|
boxSizing: "border-box",
|
|||
|
|
minHeight: "100%",
|
|||
|
|
overflow: "hidden"
|
|||
|
|
},
|
|||
|
|
".cm-gutterElement": {
|
|||
|
|
boxSizing: "border-box"
|
|||
|
|
},
|
|||
|
|
".cm-lineNumbers .cm-gutterElement": {
|
|||
|
|
padding: "0 3px 0 5px",
|
|||
|
|
minWidth: "20px",
|
|||
|
|
textAlign: "right",
|
|||
|
|
whiteSpace: "nowrap"
|
|||
|
|
},
|
|||
|
|
"&light .cm-activeLineGutter": {
|
|||
|
|
backgroundColor: "#e2f2ff"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-activeLineGutter": {
|
|||
|
|
backgroundColor: "#222227"
|
|||
|
|
},
|
|||
|
|
".cm-panels": {
|
|||
|
|
boxSizing: "border-box",
|
|||
|
|
position: "sticky",
|
|||
|
|
left: 0,
|
|||
|
|
right: 0,
|
|||
|
|
zIndex: 300
|
|||
|
|
},
|
|||
|
|
"&light .cm-panels": {
|
|||
|
|
backgroundColor: "#f5f5f5",
|
|||
|
|
color: "black"
|
|||
|
|
},
|
|||
|
|
"&light .cm-panels-top": {
|
|||
|
|
borderBottom: "1px solid #ddd"
|
|||
|
|
},
|
|||
|
|
"&light .cm-panels-bottom": {
|
|||
|
|
borderTop: "1px solid #ddd"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-panels": {
|
|||
|
|
backgroundColor: "#333338",
|
|||
|
|
color: "white"
|
|||
|
|
},
|
|||
|
|
".cm-dialog": {
|
|||
|
|
padding: "2px 19px 4px 6px",
|
|||
|
|
position: "relative",
|
|||
|
|
"& label": { fontSize: "80%" }
|
|||
|
|
},
|
|||
|
|
".cm-dialog-close": {
|
|||
|
|
position: "absolute",
|
|||
|
|
top: "3px",
|
|||
|
|
right: "4px",
|
|||
|
|
backgroundColor: "inherit",
|
|||
|
|
border: "none",
|
|||
|
|
font: "inherit",
|
|||
|
|
fontSize: "14px",
|
|||
|
|
padding: "0"
|
|||
|
|
},
|
|||
|
|
".cm-tab": {
|
|||
|
|
display: "inline-block",
|
|||
|
|
overflow: "hidden",
|
|||
|
|
verticalAlign: "bottom"
|
|||
|
|
},
|
|||
|
|
".cm-widgetBuffer": {
|
|||
|
|
verticalAlign: "text-top",
|
|||
|
|
height: "1em",
|
|||
|
|
width: 0,
|
|||
|
|
display: "inline"
|
|||
|
|
},
|
|||
|
|
".cm-placeholder": {
|
|||
|
|
color: "#888",
|
|||
|
|
display: "inline-block",
|
|||
|
|
verticalAlign: "top",
|
|||
|
|
userSelect: "none"
|
|||
|
|
},
|
|||
|
|
".cm-highlightSpace": {
|
|||
|
|
backgroundImage: "radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",
|
|||
|
|
backgroundPosition: "center"
|
|||
|
|
},
|
|||
|
|
".cm-highlightTab": {
|
|||
|
|
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
|
|||
|
|
backgroundSize: "auto 100%",
|
|||
|
|
backgroundPosition: "right 90%",
|
|||
|
|
backgroundRepeat: "no-repeat"
|
|||
|
|
},
|
|||
|
|
".cm-trailingSpace": {
|
|||
|
|
backgroundColor: "#ff332255"
|
|||
|
|
},
|
|||
|
|
".cm-button": {
|
|||
|
|
verticalAlign: "middle",
|
|||
|
|
color: "inherit",
|
|||
|
|
fontSize: "70%",
|
|||
|
|
padding: ".2em 1em",
|
|||
|
|
borderRadius: "1px"
|
|||
|
|
},
|
|||
|
|
"&light .cm-button": {
|
|||
|
|
backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
|
|||
|
|
border: "1px solid #888",
|
|||
|
|
"&:active": {
|
|||
|
|
backgroundImage: "linear-gradient(#b4b4b4, #d0d3d6)"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"&dark .cm-button": {
|
|||
|
|
backgroundImage: "linear-gradient(#393939, #111)",
|
|||
|
|
border: "1px solid #888",
|
|||
|
|
"&:active": {
|
|||
|
|
backgroundImage: "linear-gradient(#111, #333)"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
".cm-textfield": {
|
|||
|
|
verticalAlign: "middle",
|
|||
|
|
color: "inherit",
|
|||
|
|
fontSize: "70%",
|
|||
|
|
border: "1px solid silver",
|
|||
|
|
padding: ".2em .5em"
|
|||
|
|
},
|
|||
|
|
"&light .cm-textfield": {
|
|||
|
|
backgroundColor: "white"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-textfield": {
|
|||
|
|
border: "1px solid #555",
|
|||
|
|
backgroundColor: "inherit"
|
|||
|
|
}
|
|||
|
|
}, lightDarkIDs);
|
|||
|
|
var observeOptions = {
|
|||
|
|
childList: true,
|
|||
|
|
characterData: true,
|
|||
|
|
subtree: true,
|
|||
|
|
attributes: true,
|
|||
|
|
characterDataOldValue: true
|
|||
|
|
};
|
|||
|
|
var useCharData = browser.ie && browser.ie_version <= 11;
|
|||
|
|
var DOMObserver = class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.active = false;
|
|||
|
|
this.editContext = null;
|
|||
|
|
this.selectionRange = new DOMSelectionState();
|
|||
|
|
this.selectionChanged = false;
|
|||
|
|
this.delayedFlush = -1;
|
|||
|
|
this.resizeTimeout = -1;
|
|||
|
|
this.queue = [];
|
|||
|
|
this.delayedAndroidKey = null;
|
|||
|
|
this.flushingAndroidKey = -1;
|
|||
|
|
this.lastChange = 0;
|
|||
|
|
this.scrollTargets = [];
|
|||
|
|
this.intersection = null;
|
|||
|
|
this.resizeScroll = null;
|
|||
|
|
this.intersecting = false;
|
|||
|
|
this.gapIntersection = null;
|
|||
|
|
this.gaps = [];
|
|||
|
|
this.printQuery = null;
|
|||
|
|
this.parentCheck = -1;
|
|||
|
|
this.dom = view.contentDOM;
|
|||
|
|
this.observer = new MutationObserver((mutations) => {
|
|||
|
|
for (let mut of mutations)
|
|||
|
|
this.queue.push(mut);
|
|||
|
|
if ((browser.ie && browser.ie_version <= 11 || browser.ios && view.composing) && mutations.some((m) => m.type == "childList" && m.removedNodes.length || m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
|
|||
|
|
this.flushSoon();
|
|||
|
|
else
|
|||
|
|
this.flush();
|
|||
|
|
});
|
|||
|
|
if (window.EditContext && browser.android && view.constructor.EDIT_CONTEXT !== false && // Chrome <126 doesn't support inverted selections in edit context (#1392)
|
|||
|
|
!(browser.chrome && browser.chrome_version < 126)) {
|
|||
|
|
this.editContext = new EditContextManager(view);
|
|||
|
|
if (view.state.facet(editable))
|
|||
|
|
view.contentDOM.editContext = this.editContext.editContext;
|
|||
|
|
}
|
|||
|
|
if (useCharData)
|
|||
|
|
this.onCharData = (event) => {
|
|||
|
|
this.queue.push({
|
|||
|
|
target: event.target,
|
|||
|
|
type: "characterData",
|
|||
|
|
oldValue: event.prevValue
|
|||
|
|
});
|
|||
|
|
this.flushSoon();
|
|||
|
|
};
|
|||
|
|
this.onSelectionChange = this.onSelectionChange.bind(this);
|
|||
|
|
this.onResize = this.onResize.bind(this);
|
|||
|
|
this.onPrint = this.onPrint.bind(this);
|
|||
|
|
this.onScroll = this.onScroll.bind(this);
|
|||
|
|
if (window.matchMedia)
|
|||
|
|
this.printQuery = window.matchMedia("print");
|
|||
|
|
if (typeof ResizeObserver == "function") {
|
|||
|
|
this.resizeScroll = new ResizeObserver(() => {
|
|||
|
|
var _a;
|
|||
|
|
if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
|
|||
|
|
this.onResize();
|
|||
|
|
});
|
|||
|
|
this.resizeScroll.observe(view.scrollDOM);
|
|||
|
|
}
|
|||
|
|
this.addWindowListeners(this.win = view.win);
|
|||
|
|
this.start();
|
|||
|
|
if (typeof IntersectionObserver == "function") {
|
|||
|
|
this.intersection = new IntersectionObserver((entries) => {
|
|||
|
|
if (this.parentCheck < 0)
|
|||
|
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1e3);
|
|||
|
|
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|||
|
|
this.intersecting = !this.intersecting;
|
|||
|
|
if (this.intersecting != this.view.inView)
|
|||
|
|
this.onScrollChanged(document.createEvent("Event"));
|
|||
|
|
}
|
|||
|
|
}, { threshold: [0, 1e-3] });
|
|||
|
|
this.intersection.observe(this.dom);
|
|||
|
|
this.gapIntersection = new IntersectionObserver((entries) => {
|
|||
|
|
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
|
|||
|
|
this.onScrollChanged(document.createEvent("Event"));
|
|||
|
|
}, {});
|
|||
|
|
}
|
|||
|
|
this.listenForScroll();
|
|||
|
|
this.readSelectionRange();
|
|||
|
|
}
|
|||
|
|
onScrollChanged(e) {
|
|||
|
|
this.view.inputState.runHandlers("scroll", e);
|
|||
|
|
if (this.intersecting)
|
|||
|
|
this.view.measure();
|
|||
|
|
}
|
|||
|
|
onScroll(e) {
|
|||
|
|
if (this.intersecting)
|
|||
|
|
this.flush(false);
|
|||
|
|
if (this.editContext)
|
|||
|
|
this.view.requestMeasure(this.editContext.measureReq);
|
|||
|
|
this.onScrollChanged(e);
|
|||
|
|
}
|
|||
|
|
onResize() {
|
|||
|
|
if (this.resizeTimeout < 0)
|
|||
|
|
this.resizeTimeout = setTimeout(() => {
|
|||
|
|
this.resizeTimeout = -1;
|
|||
|
|
this.view.requestMeasure();
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
onPrint(event) {
|
|||
|
|
if ((event.type == "change" || !event.type) && !event.matches)
|
|||
|
|
return;
|
|||
|
|
this.view.viewState.printing = true;
|
|||
|
|
this.view.measure();
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.view.viewState.printing = false;
|
|||
|
|
this.view.requestMeasure();
|
|||
|
|
}, 500);
|
|||
|
|
}
|
|||
|
|
updateGaps(gaps) {
|
|||
|
|
if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
|
|||
|
|
this.gapIntersection.disconnect();
|
|||
|
|
for (let gap of gaps)
|
|||
|
|
this.gapIntersection.observe(gap);
|
|||
|
|
this.gaps = gaps;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
onSelectionChange(event) {
|
|||
|
|
let wasChanged = this.selectionChanged;
|
|||
|
|
if (!this.readSelectionRange() || this.delayedAndroidKey)
|
|||
|
|
return;
|
|||
|
|
let { view } = this, sel = this.selectionRange;
|
|||
|
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(this.dom, sel))
|
|||
|
|
return;
|
|||
|
|
let context = sel.anchorNode && view.docView.tile.nearest(sel.anchorNode);
|
|||
|
|
if (context && context.isWidget() && context.widget.ignoreEvent(event)) {
|
|||
|
|
if (!wasChanged)
|
|||
|
|
this.selectionChanged = false;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty && // (Selection.isCollapsed isn't reliable on IE)
|
|||
|
|
sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
|
|||
|
|
this.flushSoon();
|
|||
|
|
else
|
|||
|
|
this.flush(false);
|
|||
|
|
}
|
|||
|
|
readSelectionRange() {
|
|||
|
|
let { view } = this;
|
|||
|
|
let selection = getSelection(view.root);
|
|||
|
|
if (!selection)
|
|||
|
|
return false;
|
|||
|
|
let range = browser.safari && view.root.nodeType == 11 && view.root.activeElement == this.dom && safariSelectionRangeHack(this.view, selection) || selection;
|
|||
|
|
if (!range || this.selectionRange.eq(range))
|
|||
|
|
return false;
|
|||
|
|
let local = hasSelection(this.dom, range);
|
|||
|
|
if (local && !this.selectionChanged && view.inputState.lastFocusTime > Date.now() - 200 && view.inputState.lastTouchTime < Date.now() - 300 && atElementStart(this.dom, range)) {
|
|||
|
|
this.view.inputState.lastFocusTime = 0;
|
|||
|
|
view.docView.updateSelection();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
this.selectionRange.setRange(range);
|
|||
|
|
if (local)
|
|||
|
|
this.selectionChanged = true;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
setSelectionRange(anchor, head) {
|
|||
|
|
this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
|
|||
|
|
this.selectionChanged = false;
|
|||
|
|
}
|
|||
|
|
clearSelectionRange() {
|
|||
|
|
this.selectionRange.set(null, 0, null, 0);
|
|||
|
|
}
|
|||
|
|
listenForScroll() {
|
|||
|
|
this.parentCheck = -1;
|
|||
|
|
let i = 0, changed = null;
|
|||
|
|
for (let dom = this.dom; dom; ) {
|
|||
|
|
if (dom.nodeType == 1) {
|
|||
|
|
if (!changed && i < this.scrollTargets.length && this.scrollTargets[i] == dom)
|
|||
|
|
i++;
|
|||
|
|
else if (!changed)
|
|||
|
|
changed = this.scrollTargets.slice(0, i);
|
|||
|
|
if (changed)
|
|||
|
|
changed.push(dom);
|
|||
|
|
dom = dom.assignedSlot || dom.parentNode;
|
|||
|
|
} else if (dom.nodeType == 11) {
|
|||
|
|
dom = dom.host;
|
|||
|
|
} else {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (i < this.scrollTargets.length && !changed)
|
|||
|
|
changed = this.scrollTargets.slice(0, i);
|
|||
|
|
if (changed) {
|
|||
|
|
for (let dom of this.scrollTargets)
|
|||
|
|
dom.removeEventListener("scroll", this.onScroll);
|
|||
|
|
for (let dom of this.scrollTargets = changed)
|
|||
|
|
dom.addEventListener("scroll", this.onScroll);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
ignore(f) {
|
|||
|
|
if (!this.active)
|
|||
|
|
return f();
|
|||
|
|
try {
|
|||
|
|
this.stop();
|
|||
|
|
return f();
|
|||
|
|
} finally {
|
|||
|
|
this.start();
|
|||
|
|
this.clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
start() {
|
|||
|
|
if (this.active)
|
|||
|
|
return;
|
|||
|
|
this.observer.observe(this.dom, observeOptions);
|
|||
|
|
if (useCharData)
|
|||
|
|
this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
|
|||
|
|
this.active = true;
|
|||
|
|
}
|
|||
|
|
stop() {
|
|||
|
|
if (!this.active)
|
|||
|
|
return;
|
|||
|
|
this.active = false;
|
|||
|
|
this.observer.disconnect();
|
|||
|
|
if (useCharData)
|
|||
|
|
this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
|
|||
|
|
}
|
|||
|
|
// Throw away any pending changes
|
|||
|
|
clear() {
|
|||
|
|
this.processRecords();
|
|||
|
|
this.queue.length = 0;
|
|||
|
|
this.selectionChanged = false;
|
|||
|
|
}
|
|||
|
|
// Chrome Android, especially in combination with GBoard, not only
|
|||
|
|
// doesn't reliably fire regular key events, but also often
|
|||
|
|
// surrounds the effect of enter or backspace with a bunch of
|
|||
|
|
// composition events that, when interrupted, cause text duplication
|
|||
|
|
// or other kinds of corruption. This hack makes the editor back off
|
|||
|
|
// from handling DOM changes for a moment when such a key is
|
|||
|
|
// detected (via beforeinput or keydown), and then tries to flush
|
|||
|
|
// them or, if that has no effect, dispatches the given key.
|
|||
|
|
delayAndroidKey(key, keyCode) {
|
|||
|
|
var _a;
|
|||
|
|
if (!this.delayedAndroidKey) {
|
|||
|
|
let flush = () => {
|
|||
|
|
let key2 = this.delayedAndroidKey;
|
|||
|
|
if (key2) {
|
|||
|
|
this.clearDelayedAndroidKey();
|
|||
|
|
this.view.inputState.lastKeyCode = key2.keyCode;
|
|||
|
|
this.view.inputState.lastKeyTime = Date.now();
|
|||
|
|
let flushed = this.flush();
|
|||
|
|
if (!flushed && key2.force)
|
|||
|
|
dispatchKey(this.dom, key2.key, key2.keyCode);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
|
|||
|
|
}
|
|||
|
|
if (!this.delayedAndroidKey || key == "Enter")
|
|||
|
|
this.delayedAndroidKey = {
|
|||
|
|
key,
|
|||
|
|
keyCode,
|
|||
|
|
// Only run the key handler when no changes are detected if
|
|||
|
|
// this isn't coming right after another change, in which case
|
|||
|
|
// it is probably part of a weird chain of updates, and should
|
|||
|
|
// be ignored if it returns the DOM to its previous state.
|
|||
|
|
force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
clearDelayedAndroidKey() {
|
|||
|
|
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|||
|
|
this.delayedAndroidKey = null;
|
|||
|
|
this.flushingAndroidKey = -1;
|
|||
|
|
}
|
|||
|
|
flushSoon() {
|
|||
|
|
if (this.delayedFlush < 0)
|
|||
|
|
this.delayedFlush = this.view.win.requestAnimationFrame(() => {
|
|||
|
|
this.delayedFlush = -1;
|
|||
|
|
this.flush();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
forceFlush() {
|
|||
|
|
if (this.delayedFlush >= 0) {
|
|||
|
|
this.view.win.cancelAnimationFrame(this.delayedFlush);
|
|||
|
|
this.delayedFlush = -1;
|
|||
|
|
}
|
|||
|
|
this.flush();
|
|||
|
|
}
|
|||
|
|
pendingRecords() {
|
|||
|
|
for (let mut of this.observer.takeRecords())
|
|||
|
|
this.queue.push(mut);
|
|||
|
|
return this.queue;
|
|||
|
|
}
|
|||
|
|
processRecords() {
|
|||
|
|
let records = this.pendingRecords();
|
|||
|
|
if (records.length)
|
|||
|
|
this.queue = [];
|
|||
|
|
let from = -1, to = -1, typeOver = false;
|
|||
|
|
for (let record of records) {
|
|||
|
|
let range = this.readMutation(record);
|
|||
|
|
if (!range)
|
|||
|
|
continue;
|
|||
|
|
if (range.typeOver)
|
|||
|
|
typeOver = true;
|
|||
|
|
if (from == -1) {
|
|||
|
|
({ from, to } = range);
|
|||
|
|
} else {
|
|||
|
|
from = Math.min(range.from, from);
|
|||
|
|
to = Math.max(range.to, to);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { from, to, typeOver };
|
|||
|
|
}
|
|||
|
|
readChange() {
|
|||
|
|
let { from, to, typeOver } = this.processRecords();
|
|||
|
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|||
|
|
if (from < 0 && !newSel)
|
|||
|
|
return null;
|
|||
|
|
if (from > -1)
|
|||
|
|
this.lastChange = Date.now();
|
|||
|
|
this.view.inputState.lastFocusTime = 0;
|
|||
|
|
this.selectionChanged = false;
|
|||
|
|
let change = new DOMChange(this.view, from, to, typeOver);
|
|||
|
|
this.view.docView.domChanged = { newSel: change.newSel ? change.newSel.main : null };
|
|||
|
|
return change;
|
|||
|
|
}
|
|||
|
|
// Apply pending changes, if any
|
|||
|
|
flush(readSelection = true) {
|
|||
|
|
if (this.delayedFlush >= 0 || this.delayedAndroidKey)
|
|||
|
|
return false;
|
|||
|
|
if (readSelection)
|
|||
|
|
this.readSelectionRange();
|
|||
|
|
let domChange = this.readChange();
|
|||
|
|
if (!domChange) {
|
|||
|
|
this.view.requestMeasure();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
let startState = this.view.state;
|
|||
|
|
let handled = applyDOMChange(this.view, domChange);
|
|||
|
|
if (this.view.state == startState && (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
|
|||
|
|
this.view.update([]);
|
|||
|
|
return handled;
|
|||
|
|
}
|
|||
|
|
readMutation(rec) {
|
|||
|
|
let tile = this.view.docView.tile.nearest(rec.target);
|
|||
|
|
if (!tile || tile.isWidget())
|
|||
|
|
return null;
|
|||
|
|
tile.markDirty(rec.type == "attributes");
|
|||
|
|
if (rec.type == "childList") {
|
|||
|
|
let childBefore = findChild(tile, rec.previousSibling || rec.target.previousSibling, -1);
|
|||
|
|
let childAfter = findChild(tile, rec.nextSibling || rec.target.nextSibling, 1);
|
|||
|
|
return {
|
|||
|
|
from: childBefore ? tile.posAfter(childBefore) : tile.posAtStart,
|
|||
|
|
to: childAfter ? tile.posBefore(childAfter) : tile.posAtEnd,
|
|||
|
|
typeOver: false
|
|||
|
|
};
|
|||
|
|
} else if (rec.type == "characterData") {
|
|||
|
|
return { from: tile.posAtStart, to: tile.posAtEnd, typeOver: rec.target.nodeValue == rec.oldValue };
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
setWindow(win) {
|
|||
|
|
if (win != this.win) {
|
|||
|
|
this.removeWindowListeners(this.win);
|
|||
|
|
this.win = win;
|
|||
|
|
this.addWindowListeners(this.win);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
addWindowListeners(win) {
|
|||
|
|
win.addEventListener("resize", this.onResize);
|
|||
|
|
if (this.printQuery) {
|
|||
|
|
if (this.printQuery.addEventListener)
|
|||
|
|
this.printQuery.addEventListener("change", this.onPrint);
|
|||
|
|
else
|
|||
|
|
this.printQuery.addListener(this.onPrint);
|
|||
|
|
} else
|
|||
|
|
win.addEventListener("beforeprint", this.onPrint);
|
|||
|
|
win.addEventListener("scroll", this.onScroll);
|
|||
|
|
win.document.addEventListener("selectionchange", this.onSelectionChange);
|
|||
|
|
}
|
|||
|
|
removeWindowListeners(win) {
|
|||
|
|
win.removeEventListener("scroll", this.onScroll);
|
|||
|
|
win.removeEventListener("resize", this.onResize);
|
|||
|
|
if (this.printQuery) {
|
|||
|
|
if (this.printQuery.removeEventListener)
|
|||
|
|
this.printQuery.removeEventListener("change", this.onPrint);
|
|||
|
|
else
|
|||
|
|
this.printQuery.removeListener(this.onPrint);
|
|||
|
|
} else
|
|||
|
|
win.removeEventListener("beforeprint", this.onPrint);
|
|||
|
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (this.editContext) {
|
|||
|
|
this.editContext.update(update);
|
|||
|
|
if (update.startState.facet(editable) != update.state.facet(editable))
|
|||
|
|
update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
var _a, _b, _c;
|
|||
|
|
this.stop();
|
|||
|
|
(_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|||
|
|
(_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|||
|
|
(_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
|
|||
|
|
for (let dom of this.scrollTargets)
|
|||
|
|
dom.removeEventListener("scroll", this.onScroll);
|
|||
|
|
this.removeWindowListeners(this.win);
|
|||
|
|
clearTimeout(this.parentCheck);
|
|||
|
|
clearTimeout(this.resizeTimeout);
|
|||
|
|
this.win.cancelAnimationFrame(this.delayedFlush);
|
|||
|
|
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|||
|
|
if (this.editContext) {
|
|||
|
|
this.view.contentDOM.editContext = null;
|
|||
|
|
this.editContext.destroy();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function findChild(tile, dom, dir) {
|
|||
|
|
while (dom) {
|
|||
|
|
let curTile = Tile.get(dom);
|
|||
|
|
if (curTile && curTile.parent == tile)
|
|||
|
|
return curTile;
|
|||
|
|
let parent = dom.parentNode;
|
|||
|
|
dom = parent != tile.dom ? parent : dir > 0 ? dom.nextSibling : dom.previousSibling;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
function buildSelectionRangeFromRange(view, range) {
|
|||
|
|
let anchorNode = range.startContainer, anchorOffset = range.startOffset;
|
|||
|
|
let focusNode = range.endContainer, focusOffset = range.endOffset;
|
|||
|
|
let curAnchor = view.docView.domAtPos(view.state.selection.main.anchor, 1);
|
|||
|
|
if (isEquivalentPosition(curAnchor.node, curAnchor.offset, focusNode, focusOffset))
|
|||
|
|
[anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
|
|||
|
|
return { anchorNode, anchorOffset, focusNode, focusOffset };
|
|||
|
|
}
|
|||
|
|
function safariSelectionRangeHack(view, selection) {
|
|||
|
|
if (selection.getComposedRanges) {
|
|||
|
|
let range = selection.getComposedRanges(view.root)[0];
|
|||
|
|
if (range)
|
|||
|
|
return buildSelectionRangeFromRange(view, range);
|
|||
|
|
}
|
|||
|
|
let found = null;
|
|||
|
|
function read(event) {
|
|||
|
|
event.preventDefault();
|
|||
|
|
event.stopImmediatePropagation();
|
|||
|
|
found = event.getTargetRanges()[0];
|
|||
|
|
}
|
|||
|
|
view.contentDOM.addEventListener("beforeinput", read, true);
|
|||
|
|
view.dom.ownerDocument.execCommand("indent");
|
|||
|
|
view.contentDOM.removeEventListener("beforeinput", read, true);
|
|||
|
|
return found ? buildSelectionRangeFromRange(view, found) : null;
|
|||
|
|
}
|
|||
|
|
var EditContextManager = class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.from = 0;
|
|||
|
|
this.to = 0;
|
|||
|
|
this.pendingContextChange = null;
|
|||
|
|
this.handlers = /* @__PURE__ */ Object.create(null);
|
|||
|
|
this.composing = null;
|
|||
|
|
this.resetRange(view.state);
|
|||
|
|
let context = this.editContext = new window.EditContext({
|
|||
|
|
text: view.state.doc.sliceString(this.from, this.to),
|
|||
|
|
selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
|
|||
|
|
selectionEnd: this.toContextPos(view.state.selection.main.head)
|
|||
|
|
});
|
|||
|
|
this.handlers.textupdate = (e) => {
|
|||
|
|
let main = view.state.selection.main, { anchor, head } = main;
|
|||
|
|
let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd);
|
|||
|
|
if (view.inputState.composing >= 0 && !this.composing)
|
|||
|
|
this.composing = { contextBase: e.updateRangeStart, editorBase: from, drifted: false };
|
|||
|
|
let deletes = to - from > e.text.length;
|
|||
|
|
if (from == this.from && anchor < this.from)
|
|||
|
|
from = anchor;
|
|||
|
|
else if (to == this.to && anchor > this.to)
|
|||
|
|
to = anchor;
|
|||
|
|
let diff = findDiff(view.state.sliceDoc(from, to), e.text, (deletes ? main.from : main.to) - from, deletes ? "end" : null);
|
|||
|
|
if (!diff) {
|
|||
|
|
let newSel = EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
|
|||
|
|
if (!sameSelPos(newSel, main))
|
|||
|
|
view.dispatch({ selection: newSel, userEvent: "select" });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
let change = {
|
|||
|
|
from: diff.from + from,
|
|||
|
|
to: diff.toA + from,
|
|||
|
|
insert: Text.of(e.text.slice(diff.from, diff.toB).split("\n"))
|
|||
|
|
};
|
|||
|
|
if ((browser.mac || browser.android) && change.from == head - 1 && /^\. ?$/.test(e.text) && view.contentDOM.getAttribute("autocorrect") == "off")
|
|||
|
|
change = { from, to, insert: Text.of([e.text.replace(".", " ")]) };
|
|||
|
|
this.pendingContextChange = change;
|
|||
|
|
if (!view.state.readOnly) {
|
|||
|
|
let newLen = this.to - this.from + (change.to - change.from + change.insert.length);
|
|||
|
|
applyDOMChangeInner(view, change, EditorSelection.single(this.toEditorPos(e.selectionStart, newLen), this.toEditorPos(e.selectionEnd, newLen)));
|
|||
|
|
}
|
|||
|
|
if (this.pendingContextChange) {
|
|||
|
|
this.revertPending(view.state);
|
|||
|
|
this.setSelection(view.state);
|
|||
|
|
}
|
|||
|
|
if (change.from < change.to && !change.insert.length && view.inputState.composing >= 0 && !/[\\p{Alphabetic}\\p{Number}_]/.test(context.text.slice(Math.max(0, e.updateRangeStart - 1), Math.min(context.text.length, e.updateRangeStart + 1))))
|
|||
|
|
this.handlers.compositionend(e);
|
|||
|
|
};
|
|||
|
|
this.handlers.characterboundsupdate = (e) => {
|
|||
|
|
let rects = [], prev = null;
|
|||
|
|
for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
|
|||
|
|
let rect = view.coordsForChar(i);
|
|||
|
|
prev = rect && new DOMRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top) || prev || new DOMRect();
|
|||
|
|
rects.push(prev);
|
|||
|
|
}
|
|||
|
|
context.updateCharacterBounds(e.rangeStart, rects);
|
|||
|
|
};
|
|||
|
|
this.handlers.textformatupdate = (e) => {
|
|||
|
|
let deco = [];
|
|||
|
|
for (let format of e.getTextFormats()) {
|
|||
|
|
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|||
|
|
if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
|
|||
|
|
let from = this.toEditorPos(format.rangeStart), to = this.toEditorPos(format.rangeEnd);
|
|||
|
|
if (from < to) {
|
|||
|
|
let style = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
|
|||
|
|
deco.push(Decoration.mark({ attributes: { style } }).range(from, to));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
|
|||
|
|
};
|
|||
|
|
this.handlers.compositionstart = () => {
|
|||
|
|
if (view.inputState.composing < 0) {
|
|||
|
|
view.inputState.composing = 0;
|
|||
|
|
view.inputState.compositionFirstChange = true;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
this.handlers.compositionend = () => {
|
|||
|
|
view.inputState.composing = -1;
|
|||
|
|
view.inputState.compositionFirstChange = null;
|
|||
|
|
if (this.composing) {
|
|||
|
|
let { drifted } = this.composing;
|
|||
|
|
this.composing = null;
|
|||
|
|
if (drifted)
|
|||
|
|
this.reset(view.state);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
for (let event in this.handlers)
|
|||
|
|
context.addEventListener(event, this.handlers[event]);
|
|||
|
|
this.measureReq = { read: (view2) => {
|
|||
|
|
this.editContext.updateControlBounds(view2.contentDOM.getBoundingClientRect());
|
|||
|
|
let sel = getSelection(view2.root);
|
|||
|
|
if (sel && sel.rangeCount)
|
|||
|
|
this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
|
|||
|
|
} };
|
|||
|
|
}
|
|||
|
|
applyEdits(update) {
|
|||
|
|
let off = 0, abort = false, pending = this.pendingContextChange;
|
|||
|
|
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
|
|||
|
|
if (abort)
|
|||
|
|
return;
|
|||
|
|
let dLen = insert.length - (toA - fromA);
|
|||
|
|
if (pending && toA >= pending.to) {
|
|||
|
|
if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
|
|||
|
|
pending = this.pendingContextChange = null;
|
|||
|
|
off += dLen;
|
|||
|
|
this.to += dLen;
|
|||
|
|
return;
|
|||
|
|
} else {
|
|||
|
|
pending = null;
|
|||
|
|
this.revertPending(update.state);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
fromA += off;
|
|||
|
|
toA += off;
|
|||
|
|
if (toA <= this.from) {
|
|||
|
|
this.from += dLen;
|
|||
|
|
this.to += dLen;
|
|||
|
|
} else if (fromA < this.to) {
|
|||
|
|
if (fromA < this.from || toA > this.to || this.to - this.from + insert.length > 3e4) {
|
|||
|
|
abort = true;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
|
|||
|
|
this.to += dLen;
|
|||
|
|
}
|
|||
|
|
off += dLen;
|
|||
|
|
});
|
|||
|
|
if (pending && !abort)
|
|||
|
|
this.revertPending(update.state);
|
|||
|
|
return !abort;
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let reverted = this.pendingContextChange, startSel = update.startState.selection.main;
|
|||
|
|
if (this.composing && (this.composing.drifted || !update.changes.touchesRange(startSel.from, startSel.to) && update.transactions.some((tr) => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to)))) {
|
|||
|
|
this.composing.drifted = true;
|
|||
|
|
this.composing.editorBase = update.changes.mapPos(this.composing.editorBase);
|
|||
|
|
} else if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
|
|||
|
|
this.pendingContextChange = null;
|
|||
|
|
this.reset(update.state);
|
|||
|
|
} else if (update.docChanged || update.selectionSet || reverted) {
|
|||
|
|
this.setSelection(update.state);
|
|||
|
|
}
|
|||
|
|
if (update.geometryChanged || update.docChanged || update.selectionSet)
|
|||
|
|
update.view.requestMeasure(this.measureReq);
|
|||
|
|
}
|
|||
|
|
resetRange(state) {
|
|||
|
|
let { head } = state.selection.main;
|
|||
|
|
this.from = Math.max(
|
|||
|
|
0,
|
|||
|
|
head - 1e4
|
|||
|
|
/* CxVp.Margin */
|
|||
|
|
);
|
|||
|
|
this.to = Math.min(
|
|||
|
|
state.doc.length,
|
|||
|
|
head + 1e4
|
|||
|
|
/* CxVp.Margin */
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
reset(state) {
|
|||
|
|
this.resetRange(state);
|
|||
|
|
this.editContext.updateText(0, this.editContext.text.length, state.doc.sliceString(this.from, this.to));
|
|||
|
|
this.setSelection(state);
|
|||
|
|
}
|
|||
|
|
revertPending(state) {
|
|||
|
|
let pending = this.pendingContextChange;
|
|||
|
|
this.pendingContextChange = null;
|
|||
|
|
this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.from + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
|
|||
|
|
}
|
|||
|
|
setSelection(state) {
|
|||
|
|
let { main } = state.selection;
|
|||
|
|
let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
|
|||
|
|
let end = this.toContextPos(main.head);
|
|||
|
|
if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
|
|||
|
|
this.editContext.updateSelection(start, end);
|
|||
|
|
}
|
|||
|
|
rangeIsValid(state) {
|
|||
|
|
let { head } = state.selection.main;
|
|||
|
|
return !(this.from > 0 && head - this.from < 500 || this.to < state.doc.length && this.to - head < 500 || this.to - this.from > 1e4 * 3);
|
|||
|
|
}
|
|||
|
|
toEditorPos(contextPos, clipLen = this.to - this.from) {
|
|||
|
|
contextPos = Math.min(contextPos, clipLen);
|
|||
|
|
let c = this.composing;
|
|||
|
|
return c && c.drifted ? c.editorBase + (contextPos - c.contextBase) : contextPos + this.from;
|
|||
|
|
}
|
|||
|
|
toContextPos(editorPos) {
|
|||
|
|
let c = this.composing;
|
|||
|
|
return c && c.drifted ? c.contextBase + (editorPos - c.editorBase) : editorPos - this.from;
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
for (let event in this.handlers)
|
|||
|
|
this.editContext.removeEventListener(event, this.handlers[event]);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var EditorView = class _EditorView {
|
|||
|
|
/**
|
|||
|
|
The current editor state.
|
|||
|
|
*/
|
|||
|
|
get state() {
|
|||
|
|
return this.viewState.state;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
To be able to display large documents without consuming too much
|
|||
|
|
memory or overloading the browser, CodeMirror only draws the
|
|||
|
|
code that is visible (plus a margin around it) to the DOM. This
|
|||
|
|
property tells you the extent of the current drawn viewport, in
|
|||
|
|
document positions.
|
|||
|
|
*/
|
|||
|
|
get viewport() {
|
|||
|
|
return this.viewState.viewport;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
When there are, for example, large collapsed ranges in the
|
|||
|
|
viewport, its size can be a lot bigger than the actual visible
|
|||
|
|
content. Thus, if you are doing something like styling the
|
|||
|
|
content in the viewport, it is preferable to only do so for
|
|||
|
|
these ranges, which are the subset of the viewport that is
|
|||
|
|
actually drawn.
|
|||
|
|
*/
|
|||
|
|
get visibleRanges() {
|
|||
|
|
return this.viewState.visibleRanges;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns false when the editor is entirely scrolled out of view
|
|||
|
|
or otherwise hidden.
|
|||
|
|
*/
|
|||
|
|
get inView() {
|
|||
|
|
return this.viewState.inView;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Indicates whether the user is currently composing text via
|
|||
|
|
[IME](https://en.wikipedia.org/wiki/Input_method), and at least
|
|||
|
|
one change has been made in the current composition.
|
|||
|
|
*/
|
|||
|
|
get composing() {
|
|||
|
|
return !!this.inputState && this.inputState.composing > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Indicates whether the user is currently in composing state. Note
|
|||
|
|
that on some platforms, like Android, this will be the case a
|
|||
|
|
lot, since just putting the cursor on a word starts a
|
|||
|
|
composition there.
|
|||
|
|
*/
|
|||
|
|
get compositionStarted() {
|
|||
|
|
return !!this.inputState && this.inputState.composing >= 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The document or shadow root that the view lives in.
|
|||
|
|
*/
|
|||
|
|
get root() {
|
|||
|
|
return this._root;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
get win() {
|
|||
|
|
return this.dom.ownerDocument.defaultView || window;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Construct a new view. You'll want to either provide a `parent`
|
|||
|
|
option, or put `view.dom` into your document after creating a
|
|||
|
|
view, so that the user can see the editor.
|
|||
|
|
*/
|
|||
|
|
constructor(config = {}) {
|
|||
|
|
var _a;
|
|||
|
|
this.plugins = [];
|
|||
|
|
this.pluginMap = /* @__PURE__ */ new Map();
|
|||
|
|
this.editorAttrs = {};
|
|||
|
|
this.contentAttrs = {};
|
|||
|
|
this.bidiCache = [];
|
|||
|
|
this.destroyed = false;
|
|||
|
|
this.updateState = 2;
|
|||
|
|
this.measureScheduled = -1;
|
|||
|
|
this.measureRequests = [];
|
|||
|
|
this.contentDOM = document.createElement("div");
|
|||
|
|
this.scrollDOM = document.createElement("div");
|
|||
|
|
this.scrollDOM.tabIndex = -1;
|
|||
|
|
this.scrollDOM.className = "cm-scroller";
|
|||
|
|
this.scrollDOM.appendChild(this.contentDOM);
|
|||
|
|
this.announceDOM = document.createElement("div");
|
|||
|
|
this.announceDOM.className = "cm-announced";
|
|||
|
|
this.announceDOM.setAttribute("aria-live", "polite");
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.appendChild(this.announceDOM);
|
|||
|
|
this.dom.appendChild(this.scrollDOM);
|
|||
|
|
if (config.parent)
|
|||
|
|
config.parent.appendChild(this.dom);
|
|||
|
|
let { dispatch } = config;
|
|||
|
|
this.dispatchTransactions = config.dispatchTransactions || dispatch && ((trs) => trs.forEach((tr) => dispatch(tr, this))) || ((trs) => this.update(trs));
|
|||
|
|
this.dispatch = this.dispatch.bind(this);
|
|||
|
|
this._root = config.root || getRoot(config.parent) || document;
|
|||
|
|
this.viewState = new ViewState(this, config.state || EditorState.create(config));
|
|||
|
|
if (config.scrollTo && config.scrollTo.is(scrollIntoView))
|
|||
|
|
this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
|
|||
|
|
this.plugins = this.state.facet(viewPlugin).map((spec) => new PluginInstance(spec));
|
|||
|
|
for (let plugin2 of this.plugins)
|
|||
|
|
plugin2.update(this);
|
|||
|
|
this.observer = new DOMObserver(this);
|
|||
|
|
this.inputState = new InputState(this);
|
|||
|
|
this.inputState.ensureHandlers(this.plugins);
|
|||
|
|
this.docView = new DocView(this);
|
|||
|
|
this.mountStyles();
|
|||
|
|
this.updateAttrs();
|
|||
|
|
this.updateState = 0;
|
|||
|
|
this.requestMeasure();
|
|||
|
|
if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
|
|||
|
|
document.fonts.ready.then(() => {
|
|||
|
|
this.viewState.mustMeasureContent = "refresh";
|
|||
|
|
this.requestMeasure();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
dispatch(...input) {
|
|||
|
|
let trs = input.length == 1 && input[0] instanceof Transaction ? input : input.length == 1 && Array.isArray(input[0]) ? input[0] : [this.state.update(...input)];
|
|||
|
|
this.dispatchTransactions(trs, this);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Update the view for the given array of transactions. This will
|
|||
|
|
update the visible document and selection to match the state
|
|||
|
|
produced by the transactions, and notify view plugins of the
|
|||
|
|
change. You should usually call
|
|||
|
|
[`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead, which uses this
|
|||
|
|
as a primitive.
|
|||
|
|
*/
|
|||
|
|
update(transactions) {
|
|||
|
|
if (this.updateState != 0)
|
|||
|
|
throw new Error("Calls to EditorView.update are not allowed while an update is in progress");
|
|||
|
|
let redrawn = false, attrsChanged = false, update;
|
|||
|
|
let state = this.state;
|
|||
|
|
for (let tr of transactions) {
|
|||
|
|
if (tr.startState != state)
|
|||
|
|
throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
|
|||
|
|
state = tr.state;
|
|||
|
|
}
|
|||
|
|
if (this.destroyed) {
|
|||
|
|
this.viewState.state = state;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
let focus = this.hasFocus, focusFlag = 0, dispatchFocus = null;
|
|||
|
|
if (transactions.some((tr) => tr.annotation(isFocusChange))) {
|
|||
|
|
this.inputState.notifiedFocused = focus;
|
|||
|
|
focusFlag = 1;
|
|||
|
|
} else if (focus != this.inputState.notifiedFocused) {
|
|||
|
|
this.inputState.notifiedFocused = focus;
|
|||
|
|
dispatchFocus = focusChangeTransaction(state, focus);
|
|||
|
|
if (!dispatchFocus)
|
|||
|
|
focusFlag = 1;
|
|||
|
|
}
|
|||
|
|
let pendingKey = this.observer.delayedAndroidKey, domChange = null;
|
|||
|
|
if (pendingKey) {
|
|||
|
|
this.observer.clearDelayedAndroidKey();
|
|||
|
|
domChange = this.observer.readChange();
|
|||
|
|
if (domChange && !this.state.doc.eq(state.doc) || !this.state.selection.eq(state.selection))
|
|||
|
|
domChange = null;
|
|||
|
|
} else {
|
|||
|
|
this.observer.clear();
|
|||
|
|
}
|
|||
|
|
if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
|
|||
|
|
return this.setState(state);
|
|||
|
|
update = ViewUpdate.create(this, state, transactions);
|
|||
|
|
update.flags |= focusFlag;
|
|||
|
|
let scrollTarget = this.viewState.scrollTarget;
|
|||
|
|
try {
|
|||
|
|
this.updateState = 2;
|
|||
|
|
for (let tr of transactions) {
|
|||
|
|
if (scrollTarget)
|
|||
|
|
scrollTarget = scrollTarget.map(tr.changes);
|
|||
|
|
if (tr.scrollIntoView) {
|
|||
|
|
let { main } = tr.state.selection;
|
|||
|
|
let { x, y } = this.state.facet(_EditorView.cursorScrollMargin);
|
|||
|
|
scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1), "nearest", "nearest", y, x);
|
|||
|
|
}
|
|||
|
|
for (let e of tr.effects)
|
|||
|
|
if (e.is(scrollIntoView))
|
|||
|
|
scrollTarget = e.value.clip(this.state);
|
|||
|
|
}
|
|||
|
|
this.viewState.update(update, scrollTarget);
|
|||
|
|
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
|
|||
|
|
if (!update.empty) {
|
|||
|
|
this.updatePlugins(update);
|
|||
|
|
this.inputState.update(update);
|
|||
|
|
}
|
|||
|
|
redrawn = this.docView.update(update);
|
|||
|
|
if (this.state.facet(styleModule) != this.styleModules)
|
|||
|
|
this.mountStyles();
|
|||
|
|
attrsChanged = this.updateAttrs();
|
|||
|
|
this.showAnnouncements(transactions);
|
|||
|
|
this.docView.updateSelection(redrawn, transactions.some((tr) => tr.isUserEvent("select.pointer")));
|
|||
|
|
} finally {
|
|||
|
|
this.updateState = 0;
|
|||
|
|
}
|
|||
|
|
if (update.startState.facet(theme) != update.state.facet(theme))
|
|||
|
|
this.viewState.mustMeasureContent = true;
|
|||
|
|
if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
|
|||
|
|
this.requestMeasure();
|
|||
|
|
if (redrawn)
|
|||
|
|
this.docViewUpdate();
|
|||
|
|
if (!update.empty)
|
|||
|
|
for (let listener of this.state.facet(updateListener)) {
|
|||
|
|
try {
|
|||
|
|
listener(update);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(this.state, e, "update listener");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (dispatchFocus || domChange)
|
|||
|
|
Promise.resolve().then(() => {
|
|||
|
|
if (dispatchFocus && this.state == dispatchFocus.startState)
|
|||
|
|
this.dispatch(dispatchFocus);
|
|||
|
|
if (domChange) {
|
|||
|
|
if (!applyDOMChange(this, domChange) && pendingKey.force)
|
|||
|
|
dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Reset the view to the given state. (This will cause the entire
|
|||
|
|
document to be redrawn and all view plugins to be reinitialized,
|
|||
|
|
so you should probably only use it when the new state isn't
|
|||
|
|
derived from the old state. Otherwise, use
|
|||
|
|
[`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead.)
|
|||
|
|
*/
|
|||
|
|
setState(newState) {
|
|||
|
|
if (this.updateState != 0)
|
|||
|
|
throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
|
|||
|
|
if (this.destroyed) {
|
|||
|
|
this.viewState.state = newState;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
this.updateState = 2;
|
|||
|
|
let hadFocus = this.hasFocus;
|
|||
|
|
try {
|
|||
|
|
for (let plugin2 of this.plugins)
|
|||
|
|
plugin2.destroy(this);
|
|||
|
|
this.viewState = new ViewState(this, newState);
|
|||
|
|
this.plugins = newState.facet(viewPlugin).map((spec) => new PluginInstance(spec));
|
|||
|
|
this.pluginMap.clear();
|
|||
|
|
for (let plugin2 of this.plugins)
|
|||
|
|
plugin2.update(this);
|
|||
|
|
this.docView.destroy();
|
|||
|
|
this.docView = new DocView(this);
|
|||
|
|
this.inputState.ensureHandlers(this.plugins);
|
|||
|
|
this.mountStyles();
|
|||
|
|
this.updateAttrs();
|
|||
|
|
this.bidiCache = [];
|
|||
|
|
} finally {
|
|||
|
|
this.updateState = 0;
|
|||
|
|
}
|
|||
|
|
if (hadFocus)
|
|||
|
|
this.focus();
|
|||
|
|
this.requestMeasure();
|
|||
|
|
}
|
|||
|
|
updatePlugins(update) {
|
|||
|
|
let prevSpecs = update.startState.facet(viewPlugin), specs = update.state.facet(viewPlugin);
|
|||
|
|
if (prevSpecs != specs) {
|
|||
|
|
let newPlugins = [];
|
|||
|
|
for (let spec of specs) {
|
|||
|
|
let found = prevSpecs.indexOf(spec);
|
|||
|
|
if (found < 0) {
|
|||
|
|
newPlugins.push(new PluginInstance(spec));
|
|||
|
|
} else {
|
|||
|
|
let plugin2 = this.plugins[found];
|
|||
|
|
plugin2.mustUpdate = update;
|
|||
|
|
newPlugins.push(plugin2);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let plugin2 of this.plugins)
|
|||
|
|
if (plugin2.mustUpdate != update)
|
|||
|
|
plugin2.destroy(this);
|
|||
|
|
this.plugins = newPlugins;
|
|||
|
|
this.pluginMap.clear();
|
|||
|
|
} else {
|
|||
|
|
for (let p of this.plugins)
|
|||
|
|
p.mustUpdate = update;
|
|||
|
|
}
|
|||
|
|
for (let i = 0; i < this.plugins.length; i++)
|
|||
|
|
this.plugins[i].update(this);
|
|||
|
|
if (prevSpecs != specs)
|
|||
|
|
this.inputState.ensureHandlers(this.plugins);
|
|||
|
|
}
|
|||
|
|
docViewUpdate() {
|
|||
|
|
for (let plugin2 of this.plugins) {
|
|||
|
|
let val = plugin2.value;
|
|||
|
|
if (val && val.docViewUpdate) {
|
|||
|
|
try {
|
|||
|
|
val.docViewUpdate(this);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(this.state, e, "doc view update listener");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
measure(flush = true) {
|
|||
|
|
if (this.destroyed)
|
|||
|
|
return;
|
|||
|
|
if (this.measureScheduled > -1)
|
|||
|
|
this.win.cancelAnimationFrame(this.measureScheduled);
|
|||
|
|
if (this.observer.delayedAndroidKey) {
|
|||
|
|
this.measureScheduled = -1;
|
|||
|
|
this.requestMeasure();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
this.measureScheduled = 0;
|
|||
|
|
if (flush)
|
|||
|
|
this.observer.forceFlush();
|
|||
|
|
let updated = null;
|
|||
|
|
let scroll = this.viewState.scrollParent, scrollOffset = this.viewState.getScrollOffset();
|
|||
|
|
let { scrollAnchorPos, scrollAnchorHeight } = this.viewState;
|
|||
|
|
if (Math.abs(scrollOffset - this.viewState.scrollOffset) > 1)
|
|||
|
|
scrollAnchorHeight = -1;
|
|||
|
|
this.viewState.scrollAnchorHeight = -1;
|
|||
|
|
try {
|
|||
|
|
for (let i = 0; ; i++) {
|
|||
|
|
if (scrollAnchorHeight < 0) {
|
|||
|
|
if (isScrolledToBottom(scroll || this.win)) {
|
|||
|
|
scrollAnchorPos = -1;
|
|||
|
|
scrollAnchorHeight = this.viewState.heightMap.height;
|
|||
|
|
} else {
|
|||
|
|
let block = this.viewState.scrollAnchorAt(scrollOffset);
|
|||
|
|
scrollAnchorPos = block.from;
|
|||
|
|
scrollAnchorHeight = block.top;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.updateState = 1;
|
|||
|
|
let changed = this.viewState.measure();
|
|||
|
|
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|||
|
|
break;
|
|||
|
|
if (i > 5) {
|
|||
|
|
console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
let measuring = [];
|
|||
|
|
if (!(changed & 4))
|
|||
|
|
[this.measureRequests, measuring] = [measuring, this.measureRequests];
|
|||
|
|
let measured = measuring.map((m) => {
|
|||
|
|
try {
|
|||
|
|
return m.read(this);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(this.state, e);
|
|||
|
|
return BadMeasure;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
let update = ViewUpdate.create(this, this.state, []), redrawn = false;
|
|||
|
|
update.flags |= changed;
|
|||
|
|
if (!updated)
|
|||
|
|
updated = update;
|
|||
|
|
else
|
|||
|
|
updated.flags |= changed;
|
|||
|
|
this.updateState = 2;
|
|||
|
|
if (!update.empty) {
|
|||
|
|
this.updatePlugins(update);
|
|||
|
|
this.inputState.update(update);
|
|||
|
|
this.updateAttrs();
|
|||
|
|
redrawn = this.docView.update(update);
|
|||
|
|
if (redrawn)
|
|||
|
|
this.docViewUpdate();
|
|||
|
|
}
|
|||
|
|
for (let i2 = 0; i2 < measuring.length; i2++)
|
|||
|
|
if (measured[i2] != BadMeasure) {
|
|||
|
|
try {
|
|||
|
|
let m = measuring[i2];
|
|||
|
|
if (m.write)
|
|||
|
|
m.write(measured[i2], this);
|
|||
|
|
} catch (e) {
|
|||
|
|
logException(this.state, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (redrawn)
|
|||
|
|
this.docView.updateSelection(true);
|
|||
|
|
if (!update.viewportChanged && this.measureRequests.length == 0) {
|
|||
|
|
if (this.viewState.editorHeight) {
|
|||
|
|
if (this.viewState.scrollTarget) {
|
|||
|
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|||
|
|
this.viewState.scrollTarget = null;
|
|||
|
|
scrollAnchorHeight = -1;
|
|||
|
|
continue;
|
|||
|
|
} else {
|
|||
|
|
let newAnchorHeight = scrollAnchorPos < 0 ? this.viewState.heightMap.height : this.viewState.lineBlockAt(scrollAnchorPos).top;
|
|||
|
|
let diff = (newAnchorHeight - scrollAnchorHeight) / this.scaleY;
|
|||
|
|
if ((diff > 1 || diff < -1) && (scroll == this.scrollDOM || this.hasFocus || Math.max(this.inputState.lastWheelEvent, this.inputState.lastTouchTime) > Date.now() - 100)) {
|
|||
|
|
scrollOffset = scrollOffset + diff;
|
|||
|
|
if (scroll)
|
|||
|
|
scroll.scrollTop += diff;
|
|||
|
|
else
|
|||
|
|
this.win.scrollBy(0, diff);
|
|||
|
|
scrollAnchorHeight = -1;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
this.updateState = 0;
|
|||
|
|
this.measureScheduled = -1;
|
|||
|
|
}
|
|||
|
|
if (updated && !updated.empty)
|
|||
|
|
for (let listener of this.state.facet(updateListener))
|
|||
|
|
listener(updated);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get the CSS classes for the currently active editor themes.
|
|||
|
|
*/
|
|||
|
|
get themeClasses() {
|
|||
|
|
return baseThemeID + " " + (this.state.facet(darkTheme) ? baseDarkID : baseLightID) + " " + this.state.facet(theme);
|
|||
|
|
}
|
|||
|
|
updateAttrs() {
|
|||
|
|
let editorAttrs = attrsFromFacet(this, editorAttributes, {
|
|||
|
|
class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
|
|||
|
|
});
|
|||
|
|
let contentAttrs = {
|
|||
|
|
spellcheck: "false",
|
|||
|
|
autocorrect: "off",
|
|||
|
|
autocapitalize: "off",
|
|||
|
|
writingsuggestions: "false",
|
|||
|
|
translate: "no",
|
|||
|
|
contenteditable: !this.state.facet(editable) ? "false" : "true",
|
|||
|
|
class: "cm-content",
|
|||
|
|
style: `${browser.tabSize}: ${this.state.tabSize}`,
|
|||
|
|
role: "textbox",
|
|||
|
|
"aria-multiline": "true"
|
|||
|
|
};
|
|||
|
|
if (this.state.readOnly)
|
|||
|
|
contentAttrs["aria-readonly"] = "true";
|
|||
|
|
attrsFromFacet(this, contentAttributes, contentAttrs);
|
|||
|
|
let changed = this.observer.ignore(() => {
|
|||
|
|
let changedContent = updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
|
|||
|
|
let changedEditor = updateAttrs(this.dom, this.editorAttrs, editorAttrs);
|
|||
|
|
return changedContent || changedEditor;
|
|||
|
|
});
|
|||
|
|
this.editorAttrs = editorAttrs;
|
|||
|
|
this.contentAttrs = contentAttrs;
|
|||
|
|
return changed;
|
|||
|
|
}
|
|||
|
|
showAnnouncements(trs) {
|
|||
|
|
let first = true;
|
|||
|
|
for (let tr of trs)
|
|||
|
|
for (let effect of tr.effects)
|
|||
|
|
if (effect.is(_EditorView.announce)) {
|
|||
|
|
if (first)
|
|||
|
|
this.announceDOM.textContent = "";
|
|||
|
|
first = false;
|
|||
|
|
let div = this.announceDOM.appendChild(document.createElement("div"));
|
|||
|
|
div.textContent = effect.value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
mountStyles() {
|
|||
|
|
this.styleModules = this.state.facet(styleModule);
|
|||
|
|
let nonce = this.state.facet(_EditorView.cspNonce);
|
|||
|
|
StyleModule.mount(this.root, this.styleModules.concat(baseTheme$1).reverse(), nonce ? { nonce } : void 0);
|
|||
|
|
}
|
|||
|
|
readMeasured() {
|
|||
|
|
if (this.updateState == 2)
|
|||
|
|
throw new Error("Reading the editor layout isn't allowed during an update");
|
|||
|
|
if (this.updateState == 0 && this.measureScheduled > -1)
|
|||
|
|
this.measure(false);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Schedule a layout measurement, optionally providing callbacks to
|
|||
|
|
do custom DOM measuring followed by a DOM write phase. Using
|
|||
|
|
this is preferable reading DOM layout directly from, for
|
|||
|
|
example, an event handler, because it'll make sure measuring and
|
|||
|
|
drawing done by other components is synchronized, avoiding
|
|||
|
|
unnecessary DOM layout computations.
|
|||
|
|
*/
|
|||
|
|
requestMeasure(request) {
|
|||
|
|
if (this.measureScheduled < 0)
|
|||
|
|
this.measureScheduled = this.win.requestAnimationFrame(() => this.measure());
|
|||
|
|
if (request) {
|
|||
|
|
if (this.measureRequests.indexOf(request) > -1)
|
|||
|
|
return;
|
|||
|
|
if (request.key != null)
|
|||
|
|
for (let i = 0; i < this.measureRequests.length; i++) {
|
|||
|
|
if (this.measureRequests[i].key === request.key) {
|
|||
|
|
this.measureRequests[i] = request;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.measureRequests.push(request);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get the value of a specific plugin, if present. Note that
|
|||
|
|
plugins that crash can be dropped from a view, so even when you
|
|||
|
|
know you registered a given plugin, it is recommended to check
|
|||
|
|
the return value of this method.
|
|||
|
|
*/
|
|||
|
|
plugin(plugin2) {
|
|||
|
|
let known = this.pluginMap.get(plugin2);
|
|||
|
|
if (known === void 0 || known && known.plugin != plugin2)
|
|||
|
|
this.pluginMap.set(plugin2, known = this.plugins.find((p) => p.plugin == plugin2) || null);
|
|||
|
|
return known && known.update(this).value;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The top position of the document, in screen coordinates. This
|
|||
|
|
may be negative when the editor is scrolled down. Points
|
|||
|
|
directly to the top of the first line, not above the padding.
|
|||
|
|
*/
|
|||
|
|
get documentTop() {
|
|||
|
|
return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Reports the padding above and below the document.
|
|||
|
|
*/
|
|||
|
|
get documentPadding() {
|
|||
|
|
return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
If the editor is transformed with CSS, this provides the scale
|
|||
|
|
along the X axis. Otherwise, it will just be 1. Note that
|
|||
|
|
transforms other than translation and scaling are not supported.
|
|||
|
|
*/
|
|||
|
|
get scaleX() {
|
|||
|
|
return this.viewState.scaleX;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Provide the CSS transformed scale along the Y axis.
|
|||
|
|
*/
|
|||
|
|
get scaleY() {
|
|||
|
|
return this.viewState.scaleY;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the text line or block widget at the given vertical
|
|||
|
|
position (which is interpreted as relative to the [top of the
|
|||
|
|
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)).
|
|||
|
|
*/
|
|||
|
|
elementAtHeight(height) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
return this.viewState.elementAtHeight(height);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the line block (see
|
|||
|
|
[`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt)) at the given
|
|||
|
|
height, again interpreted relative to the [top of the
|
|||
|
|
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
|
|||
|
|
*/
|
|||
|
|
lineBlockAtHeight(height) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
return this.viewState.lineBlockAtHeight(height);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get the extent and vertical position of all [line
|
|||
|
|
blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
|
|||
|
|
are relative to the [top of the
|
|||
|
|
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
|
|||
|
|
*/
|
|||
|
|
get viewportLineBlocks() {
|
|||
|
|
return this.viewState.viewportLines;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the line block around the given document position. A line
|
|||
|
|
block is a range delimited on both sides by either a
|
|||
|
|
non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^replace) line break, or the
|
|||
|
|
start/end of the document. It will usually just hold a line of
|
|||
|
|
text, but may be broken into multiple textblocks by block
|
|||
|
|
widgets.
|
|||
|
|
*/
|
|||
|
|
lineBlockAt(pos) {
|
|||
|
|
return this.viewState.lineBlockAt(pos);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The editor's total content height.
|
|||
|
|
*/
|
|||
|
|
get contentHeight() {
|
|||
|
|
return this.viewState.contentHeight;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Move a cursor position by [grapheme
|
|||
|
|
cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak). `forward` determines whether
|
|||
|
|
the motion is away from the line start, or towards it. In
|
|||
|
|
bidirectional text, the line is traversed in visual order, using
|
|||
|
|
the editor's [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
|
|||
|
|
When the start position was the last one on the line, the
|
|||
|
|
returned position will be across the line break. If there is no
|
|||
|
|
further line, the original position is returned.
|
|||
|
|
|
|||
|
|
By default, this method moves over a single cluster. The
|
|||
|
|
optional `by` argument can be used to move across more. It will
|
|||
|
|
be called with the first cluster as argument, and should return
|
|||
|
|
a predicate that determines, for each subsequent cluster,
|
|||
|
|
whether it should also be moved over.
|
|||
|
|
*/
|
|||
|
|
moveByChar(start, forward, by) {
|
|||
|
|
return skipAtoms(this, start, moveByChar(this, start, forward, by));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Move a cursor position across the next group of either
|
|||
|
|
[letters](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) or non-letter
|
|||
|
|
non-whitespace characters.
|
|||
|
|
*/
|
|||
|
|
moveByGroup(start, forward) {
|
|||
|
|
return skipAtoms(this, start, moveByChar(this, start, forward, (initial) => byGroup(this, start.head, initial)));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get the cursor position visually at the start or end of a line.
|
|||
|
|
Note that this may differ from the _logical_ position at its
|
|||
|
|
start or end (which is simply at `line.from`/`line.to`) if text
|
|||
|
|
at the start or end goes against the line's base text direction.
|
|||
|
|
*/
|
|||
|
|
visualLineSide(line, end) {
|
|||
|
|
let order = this.bidiSpans(line), dir = this.textDirectionAt(line.from);
|
|||
|
|
let span = order[end ? order.length - 1 : 0];
|
|||
|
|
return EditorSelection.cursor(span.side(end, dir) + line.from, span.forward(!end, dir) ? 1 : -1);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Move to the next line boundary in the given direction. If
|
|||
|
|
`includeWrap` is true, line wrapping is on, and there is a
|
|||
|
|
further wrap point on the current line, the wrap point will be
|
|||
|
|
returned. Otherwise this function will return the start or end
|
|||
|
|
of the line.
|
|||
|
|
*/
|
|||
|
|
moveToLineBoundary(start, forward, includeWrap = true) {
|
|||
|
|
return moveToLineBoundary(this, start, forward, includeWrap);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Move a cursor position vertically. When `distance` isn't given,
|
|||
|
|
it defaults to moving to the next line (including wrapped
|
|||
|
|
lines). Otherwise, `distance` should provide a positive distance
|
|||
|
|
in pixels.
|
|||
|
|
|
|||
|
|
When `start` has a
|
|||
|
|
[`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical
|
|||
|
|
motion will use that as a target horizontal position. Otherwise,
|
|||
|
|
the cursor's own horizontal position is used. The returned
|
|||
|
|
cursor will have its goal column set to whichever column was
|
|||
|
|
used.
|
|||
|
|
*/
|
|||
|
|
moveVertically(start, forward, distance) {
|
|||
|
|
return skipAtoms(this, start, moveVertically(this, start, forward, distance));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the DOM parent node and offset (child offset if `node` is
|
|||
|
|
an element, character offset when it is a text node) at the
|
|||
|
|
given document position.
|
|||
|
|
|
|||
|
|
Note that for positions that aren't currently in
|
|||
|
|
`visibleRanges`, the resulting DOM position isn't necessarily
|
|||
|
|
meaningful (it may just point before or after a placeholder
|
|||
|
|
element).
|
|||
|
|
*/
|
|||
|
|
domAtPos(pos, side = 1) {
|
|||
|
|
return this.docView.domAtPos(pos, side);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the document position at the given DOM node. Can be useful
|
|||
|
|
for associating positions with DOM events. Will raise an error
|
|||
|
|
when `node` isn't part of the editor content.
|
|||
|
|
*/
|
|||
|
|
posAtDOM(node, offset = 0) {
|
|||
|
|
return this.docView.posFromDOM(node, offset);
|
|||
|
|
}
|
|||
|
|
posAtCoords(coords, precise = true) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
let found = posAtCoords(this, coords, precise);
|
|||
|
|
return found && found.pos;
|
|||
|
|
}
|
|||
|
|
posAndSideAtCoords(coords, precise = true) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
return posAtCoords(this, coords, precise);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Get the screen coordinates at the given document position.
|
|||
|
|
`side` determines whether the coordinates are based on the
|
|||
|
|
element before (-1) or after (1) the position (if no element is
|
|||
|
|
available on the given side, the method will transparently use
|
|||
|
|
another strategy to get reasonable coordinates).
|
|||
|
|
*/
|
|||
|
|
coordsAtPos(pos, side = 1) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
let rect = this.docView.coordsAt(pos, side);
|
|||
|
|
if (!rect || rect.left == rect.right)
|
|||
|
|
return rect;
|
|||
|
|
let line = this.state.doc.lineAt(pos), order = this.bidiSpans(line);
|
|||
|
|
let span = order[BidiSpan.find(order, pos - line.from, -1, side)];
|
|||
|
|
return flattenRect(rect, span.dir == Direction.LTR == side > 0);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Return the rectangle around a given character. If `pos` does not
|
|||
|
|
point in front of a character that is in the viewport and
|
|||
|
|
rendered (i.e. not replaced, not a line break), this will return
|
|||
|
|
null. For space characters that are a line wrap point, this will
|
|||
|
|
return the position before the line break.
|
|||
|
|
*/
|
|||
|
|
coordsForChar(pos) {
|
|||
|
|
this.readMeasured();
|
|||
|
|
return this.docView.coordsForChar(pos);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The default width of a character in the editor. May not
|
|||
|
|
accurately reflect the width of all characters (given variable
|
|||
|
|
width fonts or styling of invididual ranges).
|
|||
|
|
*/
|
|||
|
|
get defaultCharacterWidth() {
|
|||
|
|
return this.viewState.heightOracle.charWidth;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The default height of a line in the editor. May not be accurate
|
|||
|
|
for all lines.
|
|||
|
|
*/
|
|||
|
|
get defaultLineHeight() {
|
|||
|
|
return this.viewState.heightOracle.lineHeight;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
The text direction
|
|||
|
|
([`direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)
|
|||
|
|
CSS property) of the editor's content element.
|
|||
|
|
*/
|
|||
|
|
get textDirection() {
|
|||
|
|
return this.viewState.defaultTextDirection;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Find the text direction of the block at the given position, as
|
|||
|
|
assigned by CSS. If
|
|||
|
|
[`perLineTextDirection`](https://codemirror.net/6/docs/ref/#view.EditorView^perLineTextDirection)
|
|||
|
|
isn't enabled, or the given position is outside of the viewport,
|
|||
|
|
this will always return the same as
|
|||
|
|
[`textDirection`](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection). Note that
|
|||
|
|
this may trigger a DOM layout.
|
|||
|
|
*/
|
|||
|
|
textDirectionAt(pos) {
|
|||
|
|
let perLine = this.state.facet(perLineTextDirection);
|
|||
|
|
if (!perLine || pos < this.viewport.from || pos > this.viewport.to)
|
|||
|
|
return this.textDirection;
|
|||
|
|
this.readMeasured();
|
|||
|
|
return this.docView.textDirectionAt(pos);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Whether this editor [wraps lines](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)
|
|||
|
|
(as determined by the
|
|||
|
|
[`white-space`](https://developer.mozilla.org/en-US/docs/Web/CSS/white-space)
|
|||
|
|
CSS property of its content element).
|
|||
|
|
*/
|
|||
|
|
get lineWrapping() {
|
|||
|
|
return this.viewState.heightOracle.lineWrapping;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns the bidirectional text structure of the given line
|
|||
|
|
(which should be in the current document) as an array of span
|
|||
|
|
objects. The order of these spans matches the [text
|
|||
|
|
direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection)—if that is
|
|||
|
|
left-to-right, the leftmost spans come first, otherwise the
|
|||
|
|
rightmost spans come first.
|
|||
|
|
*/
|
|||
|
|
bidiSpans(line) {
|
|||
|
|
if (line.length > MaxBidiLine)
|
|||
|
|
return trivialOrder(line.length);
|
|||
|
|
let dir = this.textDirectionAt(line.from), isolates;
|
|||
|
|
for (let entry of this.bidiCache) {
|
|||
|
|
if (entry.from == line.from && entry.dir == dir && (entry.fresh || isolatesEq(entry.isolates, isolates = getIsolatedRanges(this, line))))
|
|||
|
|
return entry.order;
|
|||
|
|
}
|
|||
|
|
if (!isolates)
|
|||
|
|
isolates = getIsolatedRanges(this, line);
|
|||
|
|
let order = computeOrder(line.text, dir, isolates);
|
|||
|
|
this.bidiCache.push(new CachedOrder(line.from, line.to, dir, isolates, true, order));
|
|||
|
|
return order;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Check whether the editor has focus.
|
|||
|
|
*/
|
|||
|
|
get hasFocus() {
|
|||
|
|
var _a;
|
|||
|
|
return (this.dom.ownerDocument.hasFocus() || browser.safari && ((_a = this.inputState) === null || _a === void 0 ? void 0 : _a.lastContextMenu) > Date.now() - 3e4) && this.root.activeElement == this.contentDOM;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Put focus on the editor.
|
|||
|
|
*/
|
|||
|
|
focus() {
|
|||
|
|
this.observer.ignore(() => {
|
|||
|
|
focusPreventScroll(this.contentDOM);
|
|||
|
|
this.docView.updateSelection();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Update the [root](https://codemirror.net/6/docs/ref/##view.EditorViewConfig.root) in which the editor lives. This is only
|
|||
|
|
necessary when moving the editor's existing DOM to a new window or shadow root.
|
|||
|
|
*/
|
|||
|
|
setRoot(root) {
|
|||
|
|
if (this._root != root) {
|
|||
|
|
this._root = root;
|
|||
|
|
this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
|
|||
|
|
this.mountStyles();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Clean up this editor view, removing its element from the
|
|||
|
|
document, unregistering event handlers, and notifying
|
|||
|
|
plugins. The view instance can no longer be used after
|
|||
|
|
calling this.
|
|||
|
|
*/
|
|||
|
|
destroy() {
|
|||
|
|
if (this.root.activeElement == this.contentDOM)
|
|||
|
|
this.contentDOM.blur();
|
|||
|
|
for (let plugin2 of this.plugins)
|
|||
|
|
plugin2.destroy(this);
|
|||
|
|
this.plugins = [];
|
|||
|
|
this.inputState.destroy();
|
|||
|
|
this.docView.destroy();
|
|||
|
|
this.dom.remove();
|
|||
|
|
this.observer.destroy();
|
|||
|
|
if (this.measureScheduled > -1)
|
|||
|
|
this.win.cancelAnimationFrame(this.measureScheduled);
|
|||
|
|
this.destroyed = true;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns an effect that can be
|
|||
|
|
[added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
|
|||
|
|
cause it to scroll the given position or range into view.
|
|||
|
|
*/
|
|||
|
|
static scrollIntoView(pos, options = {}) {
|
|||
|
|
var _a, _b, _c, _d;
|
|||
|
|
return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, (_a = options.y) !== null && _a !== void 0 ? _a : "nearest", (_b = options.x) !== null && _b !== void 0 ? _b : "nearest", (_c = options.yMargin) !== null && _c !== void 0 ? _c : 5, (_d = options.xMargin) !== null && _d !== void 0 ? _d : 5));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Return an effect that resets the editor to its current (at the
|
|||
|
|
time this method was called) scroll position. Note that this
|
|||
|
|
only affects the editor's own scrollable element, not parents.
|
|||
|
|
See also
|
|||
|
|
[`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
|
|||
|
|
|
|||
|
|
The effect should be used with a document identical to the one
|
|||
|
|
it was created for. Failing to do so is not an error, but may
|
|||
|
|
not scroll to the expected position. You can
|
|||
|
|
[map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
|
|||
|
|
*/
|
|||
|
|
scrollSnapshot() {
|
|||
|
|
let { scrollTop, scrollLeft } = this.scrollDOM;
|
|||
|
|
let ref = this.viewState.scrollAnchorAt(scrollTop);
|
|||
|
|
return scrollIntoView.of(new ScrollTarget(EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Enable or disable tab-focus mode, which disables key bindings
|
|||
|
|
for Tab and Shift-Tab, letting the browser's default
|
|||
|
|
focus-changing behavior go through instead. This is useful to
|
|||
|
|
prevent trapping keyboard users in your editor.
|
|||
|
|
|
|||
|
|
Without argument, this toggles the mode. With a boolean, it
|
|||
|
|
enables (true) or disables it (false). Given a number, it
|
|||
|
|
temporarily enables the mode until that number of milliseconds
|
|||
|
|
have passed or another non-Tab key is pressed.
|
|||
|
|
*/
|
|||
|
|
setTabFocusMode(to) {
|
|||
|
|
if (to == null)
|
|||
|
|
this.inputState.tabFocusMode = this.inputState.tabFocusMode < 0 ? 0 : -1;
|
|||
|
|
else if (typeof to == "boolean")
|
|||
|
|
this.inputState.tabFocusMode = to ? 0 : -1;
|
|||
|
|
else if (this.inputState.tabFocusMode != 0)
|
|||
|
|
this.inputState.tabFocusMode = Date.now() + to;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Returns an extension that can be used to add DOM event handlers.
|
|||
|
|
The value should be an object mapping event names to handler
|
|||
|
|
functions. For any given event, such functions are ordered by
|
|||
|
|
extension precedence, and the first handler to return true will
|
|||
|
|
be assumed to have handled that event, and no other handlers or
|
|||
|
|
built-in behavior will be activated for it. These are registered
|
|||
|
|
on the [content element](https://codemirror.net/6/docs/ref/#view.EditorView.contentDOM), except
|
|||
|
|
for `scroll` handlers, which will be called any time the
|
|||
|
|
editor's [scroll element](https://codemirror.net/6/docs/ref/#view.EditorView.scrollDOM) or one of
|
|||
|
|
its parent nodes is scrolled.
|
|||
|
|
*/
|
|||
|
|
static domEventHandlers(handlers2) {
|
|||
|
|
return ViewPlugin.define(() => ({}), { eventHandlers: handlers2 });
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create an extension that registers DOM event observers. Contrary
|
|||
|
|
to event [handlers](https://codemirror.net/6/docs/ref/#view.EditorView^domEventHandlers),
|
|||
|
|
observers can't be prevented from running by a higher-precedence
|
|||
|
|
handler returning true. They also don't prevent other handlers
|
|||
|
|
and observers from running when they return true, and should not
|
|||
|
|
call `preventDefault`.
|
|||
|
|
*/
|
|||
|
|
static domEventObservers(observers2) {
|
|||
|
|
return ViewPlugin.define(() => ({}), { eventObservers: observers2 });
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a theme extension. The first argument can be a
|
|||
|
|
[`style-mod`](https://code.haverbeke.berlin/marijn/style-mod#documentation)
|
|||
|
|
style spec providing the styles for the theme. These will be
|
|||
|
|
prefixed with a generated class for the style.
|
|||
|
|
|
|||
|
|
Because the selectors will be prefixed with a scope class, rule
|
|||
|
|
that directly match the editor's [wrapper
|
|||
|
|
element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be
|
|||
|
|
added—need to be explicitly differentiated by adding an `&` to
|
|||
|
|
the selector for that element—for example
|
|||
|
|
`&.cm-focused`.
|
|||
|
|
|
|||
|
|
When `dark` is set to true, the theme will be marked as dark,
|
|||
|
|
which will cause the `&dark` rules from [base
|
|||
|
|
themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to
|
|||
|
|
`&light` when a light theme is active).
|
|||
|
|
*/
|
|||
|
|
static theme(spec, options) {
|
|||
|
|
let prefix = StyleModule.newName();
|
|||
|
|
let result = [theme.of(prefix), styleModule.of(buildTheme(`.${prefix}`, spec))];
|
|||
|
|
if (options && options.dark)
|
|||
|
|
result.push(darkTheme.of(true));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create an extension that adds styles to the base theme. Like
|
|||
|
|
with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
|
|||
|
|
place of the editor wrapper element when directly targeting
|
|||
|
|
that. You can also use `&dark` or `&light` instead to only
|
|||
|
|
target editors with a dark or light theme.
|
|||
|
|
*/
|
|||
|
|
static baseTheme(spec) {
|
|||
|
|
return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Retrieve an editor view instance from the view's DOM
|
|||
|
|
representation.
|
|||
|
|
*/
|
|||
|
|
static findFromDOM(dom) {
|
|||
|
|
var _a;
|
|||
|
|
let content = dom.querySelector(".cm-content");
|
|||
|
|
let tile = content && Tile.get(content) || Tile.get(dom);
|
|||
|
|
return ((_a = tile === null || tile === void 0 ? void 0 : tile.root) === null || _a === void 0 ? void 0 : _a.view) || null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
EditorView.styleModule = styleModule;
|
|||
|
|
EditorView.inputHandler = inputHandler;
|
|||
|
|
EditorView.clipboardInputFilter = clipboardInputFilter;
|
|||
|
|
EditorView.clipboardOutputFilter = clipboardOutputFilter;
|
|||
|
|
EditorView.scrollHandler = scrollHandler;
|
|||
|
|
EditorView.focusChangeEffect = focusChangeEffect;
|
|||
|
|
EditorView.perLineTextDirection = perLineTextDirection;
|
|||
|
|
EditorView.exceptionSink = exceptionSink;
|
|||
|
|
EditorView.updateListener = updateListener;
|
|||
|
|
EditorView.editable = editable;
|
|||
|
|
EditorView.mouseSelectionStyle = mouseSelectionStyle;
|
|||
|
|
EditorView.dragMovesSelection = dragMovesSelection$1;
|
|||
|
|
EditorView.clickAddsSelectionRange = clickAddsSelectionRange;
|
|||
|
|
EditorView.decorations = decorations;
|
|||
|
|
EditorView.blockWrappers = blockWrappers;
|
|||
|
|
EditorView.outerDecorations = outerDecorations;
|
|||
|
|
EditorView.atomicRanges = atomicRanges;
|
|||
|
|
EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
|
|||
|
|
EditorView.cursorScrollMargin = Facet.define({
|
|||
|
|
combine: (inputs) => {
|
|||
|
|
let x = 5, y = 5;
|
|||
|
|
for (let i of inputs) {
|
|||
|
|
if (typeof i == "number")
|
|||
|
|
x = y = i;
|
|||
|
|
else
|
|||
|
|
({ x, y } = i);
|
|||
|
|
}
|
|||
|
|
return { x, y };
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
EditorView.scrollMargins = scrollMargins;
|
|||
|
|
EditorView.darkTheme = darkTheme;
|
|||
|
|
EditorView.cspNonce = Facet.define({ combine: (values) => values.length ? values[0] : "" });
|
|||
|
|
EditorView.contentAttributes = contentAttributes;
|
|||
|
|
EditorView.editorAttributes = editorAttributes;
|
|||
|
|
EditorView.lineWrapping = EditorView.contentAttributes.of({ "class": "cm-lineWrapping" });
|
|||
|
|
EditorView.announce = StateEffect.define();
|
|||
|
|
var MaxBidiLine = 4096;
|
|||
|
|
var BadMeasure = {};
|
|||
|
|
var CachedOrder = class _CachedOrder {
|
|||
|
|
constructor(from, to, dir, isolates, fresh, order) {
|
|||
|
|
this.from = from;
|
|||
|
|
this.to = to;
|
|||
|
|
this.dir = dir;
|
|||
|
|
this.isolates = isolates;
|
|||
|
|
this.fresh = fresh;
|
|||
|
|
this.order = order;
|
|||
|
|
}
|
|||
|
|
static update(cache, changes) {
|
|||
|
|
if (changes.empty && !cache.some((c) => c.fresh))
|
|||
|
|
return cache;
|
|||
|
|
let result = [], lastDir = cache.length ? cache[cache.length - 1].dir : Direction.LTR;
|
|||
|
|
for (let i = Math.max(0, cache.length - 10); i < cache.length; i++) {
|
|||
|
|
let entry = cache[i];
|
|||
|
|
if (entry.dir == lastDir && !changes.touchesRange(entry.from, entry.to))
|
|||
|
|
result.push(new _CachedOrder(changes.mapPos(entry.from, 1), changes.mapPos(entry.to, -1), entry.dir, entry.isolates, false, entry.order));
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function attrsFromFacet(view, facet, base2) {
|
|||
|
|
for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
|
|||
|
|
let source = sources[i], value = typeof source == "function" ? source(view) : source;
|
|||
|
|
if (value)
|
|||
|
|
combineAttrs(value, base2);
|
|||
|
|
}
|
|||
|
|
return base2;
|
|||
|
|
}
|
|||
|
|
var currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
|
|||
|
|
function normalizeKeyName(name, platform) {
|
|||
|
|
const parts = name.split(/-(?!$)/);
|
|||
|
|
let result = parts[parts.length - 1];
|
|||
|
|
if (result == "Space")
|
|||
|
|
result = " ";
|
|||
|
|
let alt, ctrl, shift2, meta;
|
|||
|
|
for (let i = 0; i < parts.length - 1; ++i) {
|
|||
|
|
const mod = parts[i];
|
|||
|
|
if (/^(cmd|meta|m)$/i.test(mod))
|
|||
|
|
meta = true;
|
|||
|
|
else if (/^a(lt)?$/i.test(mod))
|
|||
|
|
alt = true;
|
|||
|
|
else if (/^(c|ctrl|control)$/i.test(mod))
|
|||
|
|
ctrl = true;
|
|||
|
|
else if (/^s(hift)?$/i.test(mod))
|
|||
|
|
shift2 = true;
|
|||
|
|
else if (/^mod$/i.test(mod)) {
|
|||
|
|
if (platform == "mac")
|
|||
|
|
meta = true;
|
|||
|
|
else
|
|||
|
|
ctrl = true;
|
|||
|
|
} else
|
|||
|
|
throw new Error("Unrecognized modifier name: " + mod);
|
|||
|
|
}
|
|||
|
|
if (alt)
|
|||
|
|
result = "Alt-" + result;
|
|||
|
|
if (ctrl)
|
|||
|
|
result = "Ctrl-" + result;
|
|||
|
|
if (meta)
|
|||
|
|
result = "Meta-" + result;
|
|||
|
|
if (shift2)
|
|||
|
|
result = "Shift-" + result;
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
function modifiers(name, event, shift2) {
|
|||
|
|
if (event.altKey)
|
|||
|
|
name = "Alt-" + name;
|
|||
|
|
if (event.ctrlKey)
|
|||
|
|
name = "Ctrl-" + name;
|
|||
|
|
if (event.metaKey)
|
|||
|
|
name = "Meta-" + name;
|
|||
|
|
if (shift2 !== false && event.shiftKey)
|
|||
|
|
name = "Shift-" + name;
|
|||
|
|
return name;
|
|||
|
|
}
|
|||
|
|
var handleKeyEvents = Prec.default(EditorView.domEventHandlers({
|
|||
|
|
keydown(event, view) {
|
|||
|
|
return runHandlers(getKeymap(view.state), event, view, "editor");
|
|||
|
|
}
|
|||
|
|
}));
|
|||
|
|
var keymap = Facet.define({ enables: handleKeyEvents });
|
|||
|
|
var Keymaps = /* @__PURE__ */ new WeakMap();
|
|||
|
|
function getKeymap(state) {
|
|||
|
|
let bindings = state.facet(keymap);
|
|||
|
|
let map = Keymaps.get(bindings);
|
|||
|
|
if (!map)
|
|||
|
|
Keymaps.set(bindings, map = buildKeymap(bindings.reduce((a, b) => a.concat(b), [])));
|
|||
|
|
return map;
|
|||
|
|
}
|
|||
|
|
function runScopeHandlers(view, event, scope) {
|
|||
|
|
return runHandlers(getKeymap(view.state), event, view, scope);
|
|||
|
|
}
|
|||
|
|
var storedPrefix = null;
|
|||
|
|
var PrefixTimeout = 4e3;
|
|||
|
|
function buildKeymap(bindings, platform = currentPlatform) {
|
|||
|
|
let bound = /* @__PURE__ */ Object.create(null);
|
|||
|
|
let isPrefix = /* @__PURE__ */ Object.create(null);
|
|||
|
|
let checkPrefix = (name, is) => {
|
|||
|
|
let current = isPrefix[name];
|
|||
|
|
if (current == null)
|
|||
|
|
isPrefix[name] = is;
|
|||
|
|
else if (current != is)
|
|||
|
|
throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
|
|||
|
|
};
|
|||
|
|
let add2 = (scope, key, command, preventDefault, stopPropagation) => {
|
|||
|
|
var _a, _b;
|
|||
|
|
let scopeObj = bound[scope] || (bound[scope] = /* @__PURE__ */ Object.create(null));
|
|||
|
|
let parts = key.split(/ (?!$)/).map((k) => normalizeKeyName(k, platform));
|
|||
|
|
for (let i = 1; i < parts.length; i++) {
|
|||
|
|
let prefix = parts.slice(0, i).join(" ");
|
|||
|
|
checkPrefix(prefix, true);
|
|||
|
|
if (!scopeObj[prefix])
|
|||
|
|
scopeObj[prefix] = {
|
|||
|
|
preventDefault: true,
|
|||
|
|
stopPropagation: false,
|
|||
|
|
run: [(view) => {
|
|||
|
|
let ourObj = storedPrefix = { view, prefix, scope };
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (storedPrefix == ourObj)
|
|||
|
|
storedPrefix = null;
|
|||
|
|
}, PrefixTimeout);
|
|||
|
|
return true;
|
|||
|
|
}]
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
let full = parts.join(" ");
|
|||
|
|
checkPrefix(full, false);
|
|||
|
|
let binding = scopeObj[full] || (scopeObj[full] = {
|
|||
|
|
preventDefault: false,
|
|||
|
|
stopPropagation: false,
|
|||
|
|
run: ((_b = (_a = scopeObj._any) === null || _a === void 0 ? void 0 : _a.run) === null || _b === void 0 ? void 0 : _b.slice()) || []
|
|||
|
|
});
|
|||
|
|
if (command)
|
|||
|
|
binding.run.push(command);
|
|||
|
|
if (preventDefault)
|
|||
|
|
binding.preventDefault = true;
|
|||
|
|
if (stopPropagation)
|
|||
|
|
binding.stopPropagation = true;
|
|||
|
|
};
|
|||
|
|
for (let b of bindings) {
|
|||
|
|
let scopes = b.scope ? b.scope.split(" ") : ["editor"];
|
|||
|
|
if (b.any)
|
|||
|
|
for (let scope of scopes) {
|
|||
|
|
let scopeObj = bound[scope] || (bound[scope] = /* @__PURE__ */ Object.create(null));
|
|||
|
|
if (!scopeObj._any)
|
|||
|
|
scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
|
|||
|
|
let { any } = b;
|
|||
|
|
for (let key in scopeObj)
|
|||
|
|
scopeObj[key].run.push((view) => any(view, currentKeyEvent));
|
|||
|
|
}
|
|||
|
|
let name = b[platform] || b.key;
|
|||
|
|
if (!name)
|
|||
|
|
continue;
|
|||
|
|
for (let scope of scopes) {
|
|||
|
|
add2(scope, name, b.run, b.preventDefault, b.stopPropagation);
|
|||
|
|
if (b.shift)
|
|||
|
|
add2(scope, "Shift-" + name, b.shift, b.preventDefault, b.stopPropagation);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return bound;
|
|||
|
|
}
|
|||
|
|
var currentKeyEvent = null;
|
|||
|
|
function runHandlers(map, event, view, scope) {
|
|||
|
|
currentKeyEvent = event;
|
|||
|
|
let name = keyName(event);
|
|||
|
|
let charCode = codePointAt(name, 0), isChar = codePointSize(charCode) == name.length && name != " ";
|
|||
|
|
let prefix = "", handled = false, prevented = false, stopPropagation = false;
|
|||
|
|
if (storedPrefix && storedPrefix.view == view && storedPrefix.scope == scope) {
|
|||
|
|
prefix = storedPrefix.prefix + " ";
|
|||
|
|
if (modifierCodes.indexOf(event.keyCode) < 0) {
|
|||
|
|
prevented = true;
|
|||
|
|
storedPrefix = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let ran = /* @__PURE__ */ new Set();
|
|||
|
|
let runFor = (binding) => {
|
|||
|
|
if (binding) {
|
|||
|
|
for (let cmd of binding.run)
|
|||
|
|
if (!ran.has(cmd)) {
|
|||
|
|
ran.add(cmd);
|
|||
|
|
if (cmd(view)) {
|
|||
|
|
if (binding.stopPropagation)
|
|||
|
|
stopPropagation = true;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (binding.preventDefault) {
|
|||
|
|
if (binding.stopPropagation)
|
|||
|
|
stopPropagation = true;
|
|||
|
|
prevented = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
let scopeObj = map[scope], baseName, shiftName;
|
|||
|
|
if (scopeObj) {
|
|||
|
|
if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)])) {
|
|||
|
|
handled = true;
|
|||
|
|
} else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) && // Ctrl-Alt may be used for AltGr on Windows
|
|||
|
|
!(browser.windows && event.ctrlKey && event.altKey) && // Alt-combinations on macOS tend to be typed characters
|
|||
|
|
!(browser.mac && event.altKey && !(event.ctrlKey || event.metaKey)) && (baseName = base[event.keyCode]) && baseName != name) {
|
|||
|
|
if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
|
|||
|
|
handled = true;
|
|||
|
|
} else if (event.shiftKey && (shiftName = shift[event.keyCode]) != name && shiftName != baseName && runFor(scopeObj[prefix + modifiers(shiftName, event, false)])) {
|
|||
|
|
handled = true;
|
|||
|
|
}
|
|||
|
|
} else if (isChar && event.shiftKey && runFor(scopeObj[prefix + modifiers(name, event, true)])) {
|
|||
|
|
handled = true;
|
|||
|
|
}
|
|||
|
|
if (!handled && runFor(scopeObj._any))
|
|||
|
|
handled = true;
|
|||
|
|
}
|
|||
|
|
if (prevented)
|
|||
|
|
handled = true;
|
|||
|
|
if (handled && stopPropagation)
|
|||
|
|
event.stopPropagation();
|
|||
|
|
currentKeyEvent = null;
|
|||
|
|
return handled;
|
|||
|
|
}
|
|||
|
|
var RectangleMarker = class _RectangleMarker {
|
|||
|
|
/**
|
|||
|
|
Create a marker with the given class and dimensions. If `width`
|
|||
|
|
is null, the DOM element will get no width style.
|
|||
|
|
*/
|
|||
|
|
constructor(className, left, top2, width, height) {
|
|||
|
|
this.className = className;
|
|||
|
|
this.left = left;
|
|||
|
|
this.top = top2;
|
|||
|
|
this.width = width;
|
|||
|
|
this.height = height;
|
|||
|
|
}
|
|||
|
|
draw() {
|
|||
|
|
let elt = document.createElement("div");
|
|||
|
|
elt.className = this.className;
|
|||
|
|
this.adjust(elt);
|
|||
|
|
return elt;
|
|||
|
|
}
|
|||
|
|
update(elt, prev) {
|
|||
|
|
if (prev.className != this.className)
|
|||
|
|
return false;
|
|||
|
|
this.adjust(elt);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
adjust(elt) {
|
|||
|
|
elt.style.left = this.left + "px";
|
|||
|
|
elt.style.top = this.top + "px";
|
|||
|
|
if (this.width != null)
|
|||
|
|
elt.style.width = this.width + "px";
|
|||
|
|
elt.style.height = this.height + "px";
|
|||
|
|
}
|
|||
|
|
eq(p) {
|
|||
|
|
return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height && this.className == p.className;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Create a set of rectangles for the given selection range,
|
|||
|
|
assigning them theclass`className`. Will create a single
|
|||
|
|
rectangle for empty ranges, and a set of selection-style
|
|||
|
|
rectangles covering the range's content (in a bidi-aware
|
|||
|
|
way) for non-empty ones.
|
|||
|
|
*/
|
|||
|
|
static forRange(view, className, range) {
|
|||
|
|
if (range.empty) {
|
|||
|
|
let pos = view.coordsAtPos(range.head, range.assoc || 1);
|
|||
|
|
if (!pos)
|
|||
|
|
return [];
|
|||
|
|
let base2 = getBase(view);
|
|||
|
|
return [new _RectangleMarker(className, pos.left - base2.left, pos.top - base2.top, null, pos.bottom - pos.top)];
|
|||
|
|
} else {
|
|||
|
|
return rectanglesForRange(view, className, range);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function getBase(view) {
|
|||
|
|
let rect = view.scrollDOM.getBoundingClientRect();
|
|||
|
|
let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth * view.scaleX;
|
|||
|
|
return { left: left - view.scrollDOM.scrollLeft * view.scaleX, top: rect.top - view.scrollDOM.scrollTop * view.scaleY };
|
|||
|
|
}
|
|||
|
|
function wrappedLine(view, pos, side, inside) {
|
|||
|
|
let coords = view.coordsAtPos(pos, side * 2);
|
|||
|
|
if (!coords)
|
|||
|
|
return inside;
|
|||
|
|
let editorRect = view.dom.getBoundingClientRect();
|
|||
|
|
let y = (coords.top + coords.bottom) / 2;
|
|||
|
|
let left = view.posAtCoords({ x: editorRect.left + 1, y });
|
|||
|
|
let right = view.posAtCoords({ x: editorRect.right - 1, y });
|
|||
|
|
if (left == null || right == null)
|
|||
|
|
return inside;
|
|||
|
|
return { from: Math.max(inside.from, Math.min(left, right)), to: Math.min(inside.to, Math.max(left, right)) };
|
|||
|
|
}
|
|||
|
|
function rectanglesForRange(view, className, range) {
|
|||
|
|
if (range.to <= view.viewport.from || range.from >= view.viewport.to)
|
|||
|
|
return [];
|
|||
|
|
let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
|
|||
|
|
let ltr = view.textDirection == Direction.LTR;
|
|||
|
|
let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base2 = getBase(view);
|
|||
|
|
let lineElt = content.querySelector(".cm-line"), lineStyle = lineElt && window.getComputedStyle(lineElt);
|
|||
|
|
let leftSide = contentRect.left + (lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
|
|||
|
|
let rightSide = contentRect.right - (lineStyle ? parseInt(lineStyle.paddingRight) : 0);
|
|||
|
|
let startBlock = blockAt(view, from, 1), endBlock = blockAt(view, to, -1);
|
|||
|
|
let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
|
|||
|
|
let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
|
|||
|
|
if (visualStart && (view.lineWrapping || startBlock.widgetLineBreaks))
|
|||
|
|
visualStart = wrappedLine(view, from, 1, visualStart);
|
|||
|
|
if (visualEnd && (view.lineWrapping || endBlock.widgetLineBreaks))
|
|||
|
|
visualEnd = wrappedLine(view, to, -1, visualEnd);
|
|||
|
|
if (visualStart && visualEnd && visualStart.from == visualEnd.from && visualStart.to == visualEnd.to) {
|
|||
|
|
return pieces(drawForLine(range.from, range.to, visualStart));
|
|||
|
|
} else {
|
|||
|
|
let top2 = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
|
|||
|
|
let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
|
|||
|
|
let between = [];
|
|||
|
|
if ((visualStart || startBlock).to < (visualEnd || endBlock).from - (visualStart && visualEnd ? 1 : 0) || startBlock.widgetLineBreaks > 1 && top2.bottom + view.defaultLineHeight / 2 < bottom.top)
|
|||
|
|
between.push(piece(leftSide, top2.bottom, rightSide, bottom.top));
|
|||
|
|
else if (top2.bottom < bottom.top && view.elementAtHeight((top2.bottom + bottom.top) / 2).type == BlockType.Text)
|
|||
|
|
top2.bottom = bottom.top = (top2.bottom + bottom.top) / 2;
|
|||
|
|
return pieces(top2).concat(between).concat(pieces(bottom));
|
|||
|
|
}
|
|||
|
|
function piece(left, top2, right, bottom) {
|
|||
|
|
return new RectangleMarker(className, left - base2.left, top2 - base2.top, Math.max(0, right - left), bottom - top2);
|
|||
|
|
}
|
|||
|
|
function pieces({ top: top2, bottom, horizontal }) {
|
|||
|
|
let pieces2 = [];
|
|||
|
|
for (let i = 0; i < horizontal.length; i += 2)
|
|||
|
|
pieces2.push(piece(horizontal[i], top2, horizontal[i + 1], bottom));
|
|||
|
|
return pieces2;
|
|||
|
|
}
|
|||
|
|
function drawForLine(from2, to2, line) {
|
|||
|
|
let top2 = 1e9, bottom = -1e9, horizontal = [];
|
|||
|
|
function addSpan(from3, fromOpen, to3, toOpen, dir) {
|
|||
|
|
let fromCoords = view.coordsAtPos(from3, from3 == line.to ? -2 : 2);
|
|||
|
|
let toCoords = view.coordsAtPos(to3, to3 == line.from ? 2 : -2);
|
|||
|
|
if (!fromCoords || !toCoords)
|
|||
|
|
return;
|
|||
|
|
top2 = Math.min(fromCoords.top, toCoords.top, top2);
|
|||
|
|
bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
|
|||
|
|
if (dir == Direction.LTR)
|
|||
|
|
horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
|
|||
|
|
else
|
|||
|
|
horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
|
|||
|
|
}
|
|||
|
|
let start = from2 !== null && from2 !== void 0 ? from2 : line.from, end = to2 !== null && to2 !== void 0 ? to2 : line.to;
|
|||
|
|
for (let r of view.visibleRanges)
|
|||
|
|
if (r.to > start && r.from < end) {
|
|||
|
|
for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end); ; ) {
|
|||
|
|
let docLine = view.state.doc.lineAt(pos);
|
|||
|
|
for (let span of view.bidiSpans(docLine)) {
|
|||
|
|
let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
|
|||
|
|
if (spanFrom >= endPos)
|
|||
|
|
break;
|
|||
|
|
if (spanTo > pos)
|
|||
|
|
addSpan(Math.max(spanFrom, pos), from2 == null && spanFrom <= start, Math.min(spanTo, endPos), to2 == null && spanTo >= end, span.dir);
|
|||
|
|
}
|
|||
|
|
pos = docLine.to + 1;
|
|||
|
|
if (pos >= endPos)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (horizontal.length == 0)
|
|||
|
|
addSpan(start, from2 == null, end, to2 == null, view.textDirection);
|
|||
|
|
return { top: top2, bottom, horizontal };
|
|||
|
|
}
|
|||
|
|
function drawForWidget(block, top2) {
|
|||
|
|
let y = contentRect.top + (top2 ? block.top : block.bottom);
|
|||
|
|
return { top: y, bottom: y, horizontal: [] };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function sameMarker(a, b) {
|
|||
|
|
return a.constructor == b.constructor && a.eq(b);
|
|||
|
|
}
|
|||
|
|
var LayerView = class {
|
|||
|
|
constructor(view, layer2) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.layer = layer2;
|
|||
|
|
this.drawn = [];
|
|||
|
|
this.scaleX = 1;
|
|||
|
|
this.scaleY = 1;
|
|||
|
|
this.measureReq = { read: this.measure.bind(this), write: this.draw.bind(this) };
|
|||
|
|
this.dom = view.scrollDOM.appendChild(document.createElement("div"));
|
|||
|
|
this.dom.classList.add("cm-layer");
|
|||
|
|
if (layer2.above)
|
|||
|
|
this.dom.classList.add("cm-layer-above");
|
|||
|
|
if (layer2.class)
|
|||
|
|
this.dom.classList.add(layer2.class);
|
|||
|
|
this.scale();
|
|||
|
|
this.dom.setAttribute("aria-hidden", "true");
|
|||
|
|
this.setOrder(view.state);
|
|||
|
|
view.requestMeasure(this.measureReq);
|
|||
|
|
if (layer2.mount)
|
|||
|
|
layer2.mount(this.dom, view);
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (update.startState.facet(layerOrder) != update.state.facet(layerOrder))
|
|||
|
|
this.setOrder(update.state);
|
|||
|
|
if (this.layer.update(update, this.dom) || update.geometryChanged) {
|
|||
|
|
this.scale();
|
|||
|
|
update.view.requestMeasure(this.measureReq);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
docViewUpdate(view) {
|
|||
|
|
if (this.layer.updateOnDocViewUpdate !== false)
|
|||
|
|
view.requestMeasure(this.measureReq);
|
|||
|
|
}
|
|||
|
|
setOrder(state) {
|
|||
|
|
let pos = 0, order = state.facet(layerOrder);
|
|||
|
|
while (pos < order.length && order[pos] != this.layer)
|
|||
|
|
pos++;
|
|||
|
|
this.dom.style.zIndex = String((this.layer.above ? 150 : -1) - pos);
|
|||
|
|
}
|
|||
|
|
measure() {
|
|||
|
|
return this.layer.markers(this.view);
|
|||
|
|
}
|
|||
|
|
scale() {
|
|||
|
|
let { scaleX, scaleY } = this.view;
|
|||
|
|
if (scaleX != this.scaleX || scaleY != this.scaleY) {
|
|||
|
|
this.scaleX = scaleX;
|
|||
|
|
this.scaleY = scaleY;
|
|||
|
|
this.dom.style.transform = `scale(${1 / scaleX}, ${1 / scaleY})`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
draw(markers) {
|
|||
|
|
if (markers.length != this.drawn.length || markers.some((p, i) => !sameMarker(p, this.drawn[i]))) {
|
|||
|
|
let old = this.dom.firstChild, oldI = 0;
|
|||
|
|
for (let marker of markers) {
|
|||
|
|
if (marker.update && old && marker.constructor && this.drawn[oldI].constructor && marker.update(old, this.drawn[oldI])) {
|
|||
|
|
old = old.nextSibling;
|
|||
|
|
oldI++;
|
|||
|
|
} else {
|
|||
|
|
this.dom.insertBefore(marker.draw(), old);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
while (old) {
|
|||
|
|
let next = old.nextSibling;
|
|||
|
|
old.remove();
|
|||
|
|
old = next;
|
|||
|
|
}
|
|||
|
|
this.drawn = markers;
|
|||
|
|
if (browser.webkit)
|
|||
|
|
this.dom.style.display = this.dom.firstChild ? "" : "none";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
if (this.layer.destroy)
|
|||
|
|
this.layer.destroy(this.dom, this.view);
|
|||
|
|
this.dom.remove();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var layerOrder = Facet.define();
|
|||
|
|
function layer(config) {
|
|||
|
|
return [
|
|||
|
|
ViewPlugin.define((v) => new LayerView(v, config)),
|
|||
|
|
layerOrder.of(config)
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
var selectionConfig = Facet.define({
|
|||
|
|
combine(configs) {
|
|||
|
|
return combineConfig(configs, {
|
|||
|
|
cursorBlinkRate: 1200,
|
|||
|
|
drawRangeCursor: true,
|
|||
|
|
iosSelectionHandles: true
|
|||
|
|
}, {
|
|||
|
|
cursorBlinkRate: (a, b) => Math.min(a, b),
|
|||
|
|
drawRangeCursor: (a, b) => a || b
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function drawSelection(config = {}) {
|
|||
|
|
return [
|
|||
|
|
selectionConfig.of(config),
|
|||
|
|
cursorLayer,
|
|||
|
|
selectionLayer,
|
|||
|
|
hideNativeSelection,
|
|||
|
|
nativeSelectionHidden.of(true)
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
function getDrawSelectionConfig(state) {
|
|||
|
|
return state.facet(selectionConfig);
|
|||
|
|
}
|
|||
|
|
function configChanged(update) {
|
|||
|
|
return update.startState.facet(selectionConfig) != update.state.facet(selectionConfig);
|
|||
|
|
}
|
|||
|
|
var cursorLayer = layer({
|
|||
|
|
above: true,
|
|||
|
|
markers(view) {
|
|||
|
|
let { state } = view, conf = state.facet(selectionConfig);
|
|||
|
|
let cursors = [];
|
|||
|
|
for (let r of state.selection.ranges) {
|
|||
|
|
let prim = r == state.selection.main;
|
|||
|
|
if (r.empty || conf.drawRangeCursor && !(prim && browser.ios && conf.iosSelectionHandles)) {
|
|||
|
|
let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
|
|||
|
|
let cursor = r.empty ? r : EditorSelection.cursor(r.head, r.assoc);
|
|||
|
|
for (let piece of RectangleMarker.forRange(view, className, cursor))
|
|||
|
|
cursors.push(piece);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return cursors;
|
|||
|
|
},
|
|||
|
|
update(update, dom) {
|
|||
|
|
if (update.transactions.some((tr) => tr.selection))
|
|||
|
|
dom.style.animationName = dom.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink";
|
|||
|
|
let confChange = configChanged(update);
|
|||
|
|
if (confChange)
|
|||
|
|
setBlinkRate(update.state, dom);
|
|||
|
|
return update.docChanged || update.selectionSet || confChange;
|
|||
|
|
},
|
|||
|
|
mount(dom, view) {
|
|||
|
|
setBlinkRate(view.state, dom);
|
|||
|
|
},
|
|||
|
|
class: "cm-cursorLayer"
|
|||
|
|
});
|
|||
|
|
function setBlinkRate(state, dom) {
|
|||
|
|
dom.style.animationDuration = state.facet(selectionConfig).cursorBlinkRate + "ms";
|
|||
|
|
}
|
|||
|
|
var selectionLayer = layer({
|
|||
|
|
above: false,
|
|||
|
|
markers(view) {
|
|||
|
|
let markers = [], { main, ranges } = view.state.selection;
|
|||
|
|
for (let r of ranges)
|
|||
|
|
if (!r.empty) {
|
|||
|
|
for (let marker of RectangleMarker.forRange(view, "cm-selectionBackground", r))
|
|||
|
|
markers.push(marker);
|
|||
|
|
}
|
|||
|
|
if (browser.ios && !main.empty && view.state.facet(selectionConfig).iosSelectionHandles) {
|
|||
|
|
for (let piece of RectangleMarker.forRange(view, "cm-selectionHandle cm-selectionHandle-start", EditorSelection.cursor(main.from, 1)))
|
|||
|
|
markers.push(piece);
|
|||
|
|
for (let piece of RectangleMarker.forRange(view, "cm-selectionHandle cm-selectionHandle-end", EditorSelection.cursor(main.to, 1)))
|
|||
|
|
markers.push(piece);
|
|||
|
|
}
|
|||
|
|
return markers;
|
|||
|
|
},
|
|||
|
|
update(update, dom) {
|
|||
|
|
return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
|
|||
|
|
},
|
|||
|
|
class: "cm-selectionLayer"
|
|||
|
|
});
|
|||
|
|
var hideNativeSelection = Prec.highest(EditorView.theme({
|
|||
|
|
".cm-line": {
|
|||
|
|
"& ::selection, &::selection": { backgroundColor: "transparent !important" },
|
|||
|
|
caretColor: "transparent !important"
|
|||
|
|
},
|
|||
|
|
".cm-content": {
|
|||
|
|
caretColor: "transparent !important",
|
|||
|
|
"& :focus": {
|
|||
|
|
caretColor: "initial !important",
|
|||
|
|
"&::selection, & ::selection": {
|
|||
|
|
backgroundColor: "Highlight !important"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}));
|
|||
|
|
var setDropCursorPos = StateEffect.define({
|
|||
|
|
map(pos, mapping) {
|
|||
|
|
return pos == null ? null : mapping.mapPos(pos);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var dropCursorPos = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return null;
|
|||
|
|
},
|
|||
|
|
update(pos, tr) {
|
|||
|
|
if (pos != null)
|
|||
|
|
pos = tr.changes.mapPos(pos);
|
|||
|
|
return tr.effects.reduce((pos2, e) => e.is(setDropCursorPos) ? e.value : pos2, pos);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var drawDropCursor = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.cursor = null;
|
|||
|
|
this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
var _a;
|
|||
|
|
let cursorPos = update.state.field(dropCursorPos);
|
|||
|
|
if (cursorPos == null) {
|
|||
|
|
if (this.cursor != null) {
|
|||
|
|
(_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
|
|||
|
|
this.cursor = null;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (!this.cursor) {
|
|||
|
|
this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
|
|||
|
|
this.cursor.className = "cm-dropCursor";
|
|||
|
|
}
|
|||
|
|
if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
|
|||
|
|
this.view.requestMeasure(this.measureReq);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
readPos() {
|
|||
|
|
let { view } = this;
|
|||
|
|
let pos = view.state.field(dropCursorPos);
|
|||
|
|
let rect = pos != null && view.coordsAtPos(pos);
|
|||
|
|
if (!rect)
|
|||
|
|
return null;
|
|||
|
|
let outer = view.scrollDOM.getBoundingClientRect();
|
|||
|
|
return {
|
|||
|
|
left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
|
|||
|
|
top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
|
|||
|
|
height: rect.bottom - rect.top
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
drawCursor(pos) {
|
|||
|
|
if (this.cursor) {
|
|||
|
|
let { scaleX, scaleY } = this.view;
|
|||
|
|
if (pos) {
|
|||
|
|
this.cursor.style.left = pos.left / scaleX + "px";
|
|||
|
|
this.cursor.style.top = pos.top / scaleY + "px";
|
|||
|
|
this.cursor.style.height = pos.height / scaleY + "px";
|
|||
|
|
} else {
|
|||
|
|
this.cursor.style.left = "-100000px";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
if (this.cursor)
|
|||
|
|
this.cursor.remove();
|
|||
|
|
}
|
|||
|
|
setDropPos(pos) {
|
|||
|
|
if (this.view.state.field(dropCursorPos) != pos)
|
|||
|
|
this.view.dispatch({ effects: setDropCursorPos.of(pos) });
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
eventObservers: {
|
|||
|
|
dragover(event) {
|
|||
|
|
this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
|
|||
|
|
},
|
|||
|
|
dragleave(event) {
|
|||
|
|
if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
|
|||
|
|
this.setDropPos(null);
|
|||
|
|
},
|
|||
|
|
dragend() {
|
|||
|
|
this.setDropPos(null);
|
|||
|
|
},
|
|||
|
|
drop() {
|
|||
|
|
this.setDropPos(null);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function dropCursor() {
|
|||
|
|
return [dropCursorPos, drawDropCursor];
|
|||
|
|
}
|
|||
|
|
function iterMatches(doc2, re, from, to, f) {
|
|||
|
|
re.lastIndex = 0;
|
|||
|
|
for (let cursor = doc2.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|||
|
|
if (!cursor.lineBreak)
|
|||
|
|
while (m = re.exec(cursor.value))
|
|||
|
|
f(pos + m.index, m);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function matchRanges(view, maxLength) {
|
|||
|
|
let visible = view.visibleRanges;
|
|||
|
|
if (visible.length == 1 && visible[0].from == view.viewport.from && visible[0].to == view.viewport.to)
|
|||
|
|
return visible;
|
|||
|
|
let result = [];
|
|||
|
|
for (let { from, to } of visible) {
|
|||
|
|
from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
|
|||
|
|
to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
|
|||
|
|
if (result.length && result[result.length - 1].to >= from)
|
|||
|
|
result[result.length - 1].to = to;
|
|||
|
|
else
|
|||
|
|
result.push({ from, to });
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
var MatchDecorator = class {
|
|||
|
|
/**
|
|||
|
|
Create a decorator.
|
|||
|
|
*/
|
|||
|
|
constructor(config) {
|
|||
|
|
const { regexp, decoration, decorate, boundary, maxLength = 1e3 } = config;
|
|||
|
|
if (!regexp.global)
|
|||
|
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|||
|
|
this.regexp = regexp;
|
|||
|
|
if (decorate) {
|
|||
|
|
this.addMatch = (match, view, from, add2) => decorate(add2, from, from + match[0].length, match, view);
|
|||
|
|
} else if (typeof decoration == "function") {
|
|||
|
|
this.addMatch = (match, view, from, add2) => {
|
|||
|
|
let deco = decoration(match, view, from);
|
|||
|
|
if (deco)
|
|||
|
|
add2(from, from + match[0].length, deco);
|
|||
|
|
};
|
|||
|
|
} else if (decoration) {
|
|||
|
|
this.addMatch = (match, _view, from, add2) => add2(from, from + match[0].length, decoration);
|
|||
|
|
} else {
|
|||
|
|
throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
|
|||
|
|
}
|
|||
|
|
this.boundary = boundary;
|
|||
|
|
this.maxLength = maxLength;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Compute the full set of decorations for matches in the given
|
|||
|
|
view's viewport. You'll want to call this when initializing your
|
|||
|
|
plugin.
|
|||
|
|
*/
|
|||
|
|
createDeco(view) {
|
|||
|
|
let build = new RangeSetBuilder(), add2 = build.add.bind(build);
|
|||
|
|
for (let { from, to } of matchRanges(view, this.maxLength))
|
|||
|
|
iterMatches(view.state.doc, this.regexp, from, to, (from2, m) => this.addMatch(m, view, from2, add2));
|
|||
|
|
return build.finish();
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Update a set of decorations for a view update. `deco` _must_ be
|
|||
|
|
the set of decorations produced by _this_ `MatchDecorator` for
|
|||
|
|
the view state before the update.
|
|||
|
|
*/
|
|||
|
|
updateDeco(update, deco) {
|
|||
|
|
let changeFrom = 1e9, changeTo = -1;
|
|||
|
|
if (update.docChanged)
|
|||
|
|
update.changes.iterChanges((_f, _t, from, to) => {
|
|||
|
|
if (to >= update.view.viewport.from && from <= update.view.viewport.to) {
|
|||
|
|
changeFrom = Math.min(from, changeFrom);
|
|||
|
|
changeTo = Math.max(to, changeTo);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
if (update.viewportMoved || changeTo - changeFrom > 1e3)
|
|||
|
|
return this.createDeco(update.view);
|
|||
|
|
if (changeTo > -1)
|
|||
|
|
return this.updateRange(update.view, deco.map(update.changes), changeFrom, changeTo);
|
|||
|
|
return deco;
|
|||
|
|
}
|
|||
|
|
updateRange(view, deco, updateFrom, updateTo) {
|
|||
|
|
for (let r of view.visibleRanges) {
|
|||
|
|
let from = Math.max(r.from, updateFrom), to = Math.min(r.to, updateTo);
|
|||
|
|
if (to >= from) {
|
|||
|
|
let fromLine = view.state.doc.lineAt(from), toLine = fromLine.to < to ? view.state.doc.lineAt(to) : fromLine;
|
|||
|
|
let start = Math.max(r.from, fromLine.from), end = Math.min(r.to, toLine.to);
|
|||
|
|
if (this.boundary) {
|
|||
|
|
for (; from > fromLine.from; from--)
|
|||
|
|
if (this.boundary.test(fromLine.text[from - 1 - fromLine.from])) {
|
|||
|
|
start = from;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
for (; to < toLine.to; to++)
|
|||
|
|
if (this.boundary.test(toLine.text[to - toLine.from])) {
|
|||
|
|
end = to;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let ranges = [], m;
|
|||
|
|
let add2 = (from2, to2, deco2) => ranges.push(deco2.range(from2, to2));
|
|||
|
|
if (fromLine == toLine) {
|
|||
|
|
this.regexp.lastIndex = start - fromLine.from;
|
|||
|
|
while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
|
|||
|
|
this.addMatch(m, view, m.index + fromLine.from, add2);
|
|||
|
|
} else {
|
|||
|
|
iterMatches(view.state.doc, this.regexp, start, end, (from2, m2) => this.addMatch(m2, view, from2, add2));
|
|||
|
|
}
|
|||
|
|
deco = deco.update({ filterFrom: start, filterTo: end, filter: (from2, to2) => from2 < start || to2 > end, add: ranges });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return deco;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
|
|||
|
|
var Specials = new RegExp("[\0-\b\n--\u2028\u2029\uFEFF-]", UnicodeRegexpSupport);
|
|||
|
|
var Names = {
|
|||
|
|
0: "null",
|
|||
|
|
7: "bell",
|
|||
|
|
8: "backspace",
|
|||
|
|
10: "newline",
|
|||
|
|
11: "vertical tab",
|
|||
|
|
13: "carriage return",
|
|||
|
|
27: "escape",
|
|||
|
|
8203: "zero width space",
|
|||
|
|
8204: "zero width non-joiner",
|
|||
|
|
8205: "zero width joiner",
|
|||
|
|
8206: "left-to-right mark",
|
|||
|
|
8207: "right-to-left mark",
|
|||
|
|
8232: "line separator",
|
|||
|
|
8237: "left-to-right override",
|
|||
|
|
8238: "right-to-left override",
|
|||
|
|
8294: "left-to-right isolate",
|
|||
|
|
8295: "right-to-left isolate",
|
|||
|
|
8297: "pop directional isolate",
|
|||
|
|
8233: "paragraph separator",
|
|||
|
|
65279: "zero width no-break space",
|
|||
|
|
65532: "object replacement"
|
|||
|
|
};
|
|||
|
|
var _supportsTabSize = null;
|
|||
|
|
function supportsTabSize() {
|
|||
|
|
var _a;
|
|||
|
|
if (_supportsTabSize == null && typeof document != "undefined" && document.body) {
|
|||
|
|
let styles = document.body.style;
|
|||
|
|
_supportsTabSize = ((_a = styles.tabSize) !== null && _a !== void 0 ? _a : styles.MozTabSize) != null;
|
|||
|
|
}
|
|||
|
|
return _supportsTabSize || false;
|
|||
|
|
}
|
|||
|
|
var specialCharConfig = Facet.define({
|
|||
|
|
combine(configs) {
|
|||
|
|
let config = combineConfig(configs, {
|
|||
|
|
render: null,
|
|||
|
|
specialChars: Specials,
|
|||
|
|
addSpecialChars: null
|
|||
|
|
});
|
|||
|
|
if (config.replaceTabs = !supportsTabSize())
|
|||
|
|
config.specialChars = new RegExp(" |" + config.specialChars.source, UnicodeRegexpSupport);
|
|||
|
|
if (config.addSpecialChars)
|
|||
|
|
config.specialChars = new RegExp(config.specialChars.source + "|" + config.addSpecialChars.source, UnicodeRegexpSupport);
|
|||
|
|
return config;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function highlightSpecialChars(config = {}) {
|
|||
|
|
return [specialCharConfig.of(config), specialCharPlugin()];
|
|||
|
|
}
|
|||
|
|
var _plugin = null;
|
|||
|
|
function specialCharPlugin() {
|
|||
|
|
return _plugin || (_plugin = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.decorations = Decoration.none;
|
|||
|
|
this.decorationCache = /* @__PURE__ */ Object.create(null);
|
|||
|
|
this.decorator = this.makeDecorator(view.state.facet(specialCharConfig));
|
|||
|
|
this.decorations = this.decorator.createDeco(view);
|
|||
|
|
}
|
|||
|
|
makeDecorator(conf) {
|
|||
|
|
return new MatchDecorator({
|
|||
|
|
regexp: conf.specialChars,
|
|||
|
|
decoration: (m, view, pos) => {
|
|||
|
|
let { doc: doc2 } = view.state;
|
|||
|
|
let code = codePointAt(m[0], 0);
|
|||
|
|
if (code == 9) {
|
|||
|
|
let line = doc2.lineAt(pos);
|
|||
|
|
let size = view.state.tabSize, col = countColumn(line.text, size, pos - line.from);
|
|||
|
|
return Decoration.replace({
|
|||
|
|
widget: new TabWidget((size - col % size) * this.view.defaultCharacterWidth / this.view.scaleX)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return this.decorationCache[code] || (this.decorationCache[code] = Decoration.replace({ widget: new SpecialCharWidget(conf, code) }));
|
|||
|
|
},
|
|||
|
|
boundary: conf.replaceTabs ? void 0 : /[^]/
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let conf = update.state.facet(specialCharConfig);
|
|||
|
|
if (update.startState.facet(specialCharConfig) != conf) {
|
|||
|
|
this.decorator = this.makeDecorator(conf);
|
|||
|
|
this.decorations = this.decorator.createDeco(update.view);
|
|||
|
|
} else {
|
|||
|
|
this.decorations = this.decorator.updateDeco(update, this.decorations);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
decorations: (v) => v.decorations
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
var DefaultPlaceholder = "•";
|
|||
|
|
function placeholder$1(code) {
|
|||
|
|
if (code >= 32)
|
|||
|
|
return DefaultPlaceholder;
|
|||
|
|
if (code == 10)
|
|||
|
|
return "";
|
|||
|
|
return String.fromCharCode(9216 + code);
|
|||
|
|
}
|
|||
|
|
var SpecialCharWidget = class extends WidgetType {
|
|||
|
|
constructor(options, code) {
|
|||
|
|
super();
|
|||
|
|
this.options = options;
|
|||
|
|
this.code = code;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.code == this.code;
|
|||
|
|
}
|
|||
|
|
toDOM(view) {
|
|||
|
|
let ph = placeholder$1(this.code);
|
|||
|
|
let desc = view.state.phrase("Control character") + " " + (Names[this.code] || "0x" + this.code.toString(16));
|
|||
|
|
let custom = this.options.render && this.options.render(this.code, desc, ph);
|
|||
|
|
if (custom)
|
|||
|
|
return custom;
|
|||
|
|
let span = document.createElement("span");
|
|||
|
|
span.textContent = ph;
|
|||
|
|
span.title = desc;
|
|||
|
|
span.setAttribute("aria-label", desc);
|
|||
|
|
span.className = "cm-specialChar";
|
|||
|
|
return span;
|
|||
|
|
}
|
|||
|
|
ignoreEvent() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var TabWidget = class extends WidgetType {
|
|||
|
|
constructor(width) {
|
|||
|
|
super();
|
|||
|
|
this.width = width;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return other.width == this.width;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
let span = document.createElement("span");
|
|||
|
|
span.textContent = " ";
|
|||
|
|
span.className = "cm-tab";
|
|||
|
|
span.style.width = this.width + "px";
|
|||
|
|
return span;
|
|||
|
|
}
|
|||
|
|
ignoreEvent() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var plugin = ViewPlugin.fromClass(class {
|
|||
|
|
constructor() {
|
|||
|
|
this.height = 1e3;
|
|||
|
|
this.attrs = { style: "padding-bottom: 1000px" };
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let { view } = update;
|
|||
|
|
let height = view.viewState.editorHeight - view.defaultLineHeight - view.documentPadding.top - 0.5;
|
|||
|
|
if (height >= 0 && height != this.height) {
|
|||
|
|
this.height = height;
|
|||
|
|
this.attrs = { style: `padding-bottom: ${height}px` };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function scrollPastEnd() {
|
|||
|
|
return [plugin, contentAttributes.of((view) => {
|
|||
|
|
var _a;
|
|||
|
|
return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null;
|
|||
|
|
})];
|
|||
|
|
}
|
|||
|
|
function highlightActiveLine() {
|
|||
|
|
return activeLineHighlighter;
|
|||
|
|
}
|
|||
|
|
var lineDeco = Decoration.line({ class: "cm-activeLine" });
|
|||
|
|
var activeLineHighlighter = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.decorations = this.getDeco(view);
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (update.docChanged || update.selectionSet)
|
|||
|
|
this.decorations = this.getDeco(update.view);
|
|||
|
|
}
|
|||
|
|
getDeco(view) {
|
|||
|
|
let lastLineStart = -1, deco = [];
|
|||
|
|
for (let r of view.state.selection.ranges) {
|
|||
|
|
let line = view.lineBlockAt(r.head);
|
|||
|
|
if (line.from > lastLineStart) {
|
|||
|
|
deco.push(lineDeco.range(line.from));
|
|||
|
|
lastLineStart = line.from;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return Decoration.set(deco);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
decorations: (v) => v.decorations
|
|||
|
|
});
|
|||
|
|
var Placeholder = class extends WidgetType {
|
|||
|
|
constructor(content) {
|
|||
|
|
super();
|
|||
|
|
this.content = content;
|
|||
|
|
}
|
|||
|
|
toDOM(view) {
|
|||
|
|
let wrap = document.createElement("span");
|
|||
|
|
wrap.className = "cm-placeholder";
|
|||
|
|
wrap.style.pointerEvents = "none";
|
|||
|
|
wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) : typeof this.content == "function" ? this.content(view) : this.content.cloneNode(true));
|
|||
|
|
wrap.setAttribute("aria-hidden", "true");
|
|||
|
|
return wrap;
|
|||
|
|
}
|
|||
|
|
coordsAt(dom) {
|
|||
|
|
let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
|
|||
|
|
if (!rects.length)
|
|||
|
|
return null;
|
|||
|
|
let style = window.getComputedStyle(dom.parentNode);
|
|||
|
|
let rect = flattenRect(rects[0], style.direction != "rtl");
|
|||
|
|
let lineHeight = parseInt(style.lineHeight);
|
|||
|
|
if (rect.bottom - rect.top > lineHeight * 1.5)
|
|||
|
|
return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
|
|||
|
|
return rect;
|
|||
|
|
}
|
|||
|
|
ignoreEvent() {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function placeholder(content) {
|
|||
|
|
let plugin2 = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.placeholder = content ? Decoration.set([Decoration.widget({ widget: new Placeholder(content), side: 1 }).range(0)]) : Decoration.none;
|
|||
|
|
}
|
|||
|
|
get decorations() {
|
|||
|
|
return this.view.state.doc.length ? Decoration.none : this.placeholder;
|
|||
|
|
}
|
|||
|
|
}, { decorations: (v) => v.decorations });
|
|||
|
|
return typeof content == "string" ? [
|
|||
|
|
plugin2,
|
|||
|
|
EditorView.contentAttributes.of({ "aria-placeholder": content })
|
|||
|
|
] : plugin2;
|
|||
|
|
}
|
|||
|
|
var MaxOff = 2e3;
|
|||
|
|
function rectangleFor(state, a, b) {
|
|||
|
|
let startLine = Math.min(a.line, b.line), endLine = Math.max(a.line, b.line);
|
|||
|
|
let ranges = [];
|
|||
|
|
if (a.off > MaxOff || b.off > MaxOff || a.col < 0 || b.col < 0) {
|
|||
|
|
let startOff = Math.min(a.off, b.off), endOff = Math.max(a.off, b.off);
|
|||
|
|
for (let i = startLine; i <= endLine; i++) {
|
|||
|
|
let line = state.doc.line(i);
|
|||
|
|
if (line.length <= endOff)
|
|||
|
|
ranges.push(EditorSelection.range(line.from + startOff, line.to + endOff));
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col);
|
|||
|
|
for (let i = startLine; i <= endLine; i++) {
|
|||
|
|
let line = state.doc.line(i);
|
|||
|
|
let start = findColumn(line.text, startCol, state.tabSize, true);
|
|||
|
|
if (start < 0) {
|
|||
|
|
ranges.push(EditorSelection.cursor(line.to));
|
|||
|
|
} else {
|
|||
|
|
let end = findColumn(line.text, endCol, state.tabSize);
|
|||
|
|
ranges.push(EditorSelection.range(line.from + start, line.from + end));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ranges;
|
|||
|
|
}
|
|||
|
|
function absoluteColumn(view, x) {
|
|||
|
|
let ref = view.coordsAtPos(view.viewport.from);
|
|||
|
|
return ref ? Math.round(Math.abs((ref.left - x) / view.defaultCharacterWidth)) : -1;
|
|||
|
|
}
|
|||
|
|
function getPos(view, event) {
|
|||
|
|
let offset = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
|
|||
|
|
let line = view.state.doc.lineAt(offset), off = offset - line.from;
|
|||
|
|
let col = off > MaxOff ? -1 : off == line.length ? absoluteColumn(view, event.clientX) : countColumn(line.text, view.state.tabSize, offset - line.from);
|
|||
|
|
return { line: line.number, col, off };
|
|||
|
|
}
|
|||
|
|
function rectangleSelectionStyle(view, event) {
|
|||
|
|
let start = getPos(view, event), startSel = view.state.selection;
|
|||
|
|
if (!start)
|
|||
|
|
return null;
|
|||
|
|
return {
|
|||
|
|
update(update) {
|
|||
|
|
if (update.docChanged) {
|
|||
|
|
let newStart = update.changes.mapPos(update.startState.doc.line(start.line).from);
|
|||
|
|
let newLine = update.state.doc.lineAt(newStart);
|
|||
|
|
start = { line: newLine.number, col: start.col, off: Math.min(start.off, newLine.length) };
|
|||
|
|
startSel = startSel.map(update.changes);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
get(event2, _extend, multiple) {
|
|||
|
|
let cur = getPos(view, event2);
|
|||
|
|
if (!cur)
|
|||
|
|
return startSel;
|
|||
|
|
let ranges = rectangleFor(view.state, start, cur);
|
|||
|
|
if (!ranges.length)
|
|||
|
|
return startSel;
|
|||
|
|
if (multiple)
|
|||
|
|
return EditorSelection.create(ranges.concat(startSel.ranges));
|
|||
|
|
else
|
|||
|
|
return EditorSelection.create(ranges);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function rectangularSelection(options) {
|
|||
|
|
let filter = (options === null || options === void 0 ? void 0 : options.eventFilter) || ((e) => e.altKey && e.button == 0);
|
|||
|
|
return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
|
|||
|
|
}
|
|||
|
|
var keys = {
|
|||
|
|
Alt: [18, (e) => !!e.altKey],
|
|||
|
|
Control: [17, (e) => !!e.ctrlKey],
|
|||
|
|
Shift: [16, (e) => !!e.shiftKey],
|
|||
|
|
Meta: [91, (e) => !!e.metaKey]
|
|||
|
|
};
|
|||
|
|
var showCrosshair = { style: "cursor: crosshair" };
|
|||
|
|
function crosshairCursor(options = {}) {
|
|||
|
|
let [code, getter] = keys[options.key || "Alt"];
|
|||
|
|
let plugin2 = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.isDown = false;
|
|||
|
|
}
|
|||
|
|
set(isDown) {
|
|||
|
|
if (this.isDown != isDown) {
|
|||
|
|
this.isDown = isDown;
|
|||
|
|
this.view.update([]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
eventObservers: {
|
|||
|
|
keydown(e) {
|
|||
|
|
this.set(e.keyCode == code || getter(e));
|
|||
|
|
},
|
|||
|
|
keyup(e) {
|
|||
|
|
if (e.keyCode == code || !getter(e))
|
|||
|
|
this.set(false);
|
|||
|
|
},
|
|||
|
|
mousemove(e) {
|
|||
|
|
this.set(getter(e));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return [
|
|||
|
|
plugin2,
|
|||
|
|
EditorView.contentAttributes.of((view) => {
|
|||
|
|
var _a;
|
|||
|
|
return ((_a = view.plugin(plugin2)) === null || _a === void 0 ? void 0 : _a.isDown) ? showCrosshair : null;
|
|||
|
|
})
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
var Outside = "-10000px";
|
|||
|
|
var TooltipViewManager = class {
|
|||
|
|
constructor(view, facet, createTooltipView, removeTooltipView) {
|
|||
|
|
this.facet = facet;
|
|||
|
|
this.createTooltipView = createTooltipView;
|
|||
|
|
this.removeTooltipView = removeTooltipView;
|
|||
|
|
this.input = view.state.facet(facet);
|
|||
|
|
this.tooltips = this.input.filter((t) => t);
|
|||
|
|
let prev = null;
|
|||
|
|
this.tooltipViews = this.tooltips.map((t) => prev = createTooltipView(t, prev));
|
|||
|
|
}
|
|||
|
|
update(update, above) {
|
|||
|
|
var _a;
|
|||
|
|
let input = update.state.facet(this.facet);
|
|||
|
|
let tooltips2 = input.filter((x) => x);
|
|||
|
|
if (input === this.input) {
|
|||
|
|
for (let t of this.tooltipViews)
|
|||
|
|
if (t.update)
|
|||
|
|
t.update(update);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
let tooltipViews = [], newAbove = above ? [] : null;
|
|||
|
|
for (let i = 0; i < tooltips2.length; i++) {
|
|||
|
|
let tip = tooltips2[i], known = -1;
|
|||
|
|
if (!tip)
|
|||
|
|
continue;
|
|||
|
|
for (let i2 = 0; i2 < this.tooltips.length; i2++) {
|
|||
|
|
let other = this.tooltips[i2];
|
|||
|
|
if (other && other.create == tip.create)
|
|||
|
|
known = i2;
|
|||
|
|
}
|
|||
|
|
if (known < 0) {
|
|||
|
|
tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
|
|||
|
|
if (newAbove)
|
|||
|
|
newAbove[i] = !!tip.above;
|
|||
|
|
} else {
|
|||
|
|
let tooltipView = tooltipViews[i] = this.tooltipViews[known];
|
|||
|
|
if (newAbove)
|
|||
|
|
newAbove[i] = above[known];
|
|||
|
|
if (tooltipView.update)
|
|||
|
|
tooltipView.update(update);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let t of this.tooltipViews)
|
|||
|
|
if (tooltipViews.indexOf(t) < 0) {
|
|||
|
|
this.removeTooltipView(t);
|
|||
|
|
(_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
|
|||
|
|
}
|
|||
|
|
if (above) {
|
|||
|
|
newAbove.forEach((val, i) => above[i] = val);
|
|||
|
|
above.length = newAbove.length;
|
|||
|
|
}
|
|||
|
|
this.input = input;
|
|||
|
|
this.tooltips = tooltips2;
|
|||
|
|
this.tooltipViews = tooltipViews;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function tooltips(config = {}) {
|
|||
|
|
return tooltipConfig.of(config);
|
|||
|
|
}
|
|||
|
|
function windowSpace(view) {
|
|||
|
|
let docElt = view.dom.ownerDocument.documentElement;
|
|||
|
|
return { top: 0, left: 0, bottom: docElt.clientHeight, right: docElt.clientWidth };
|
|||
|
|
}
|
|||
|
|
var tooltipConfig = Facet.define({
|
|||
|
|
combine: (values) => {
|
|||
|
|
var _a, _b, _c;
|
|||
|
|
return {
|
|||
|
|
position: browser.ios ? "absolute" : ((_a = values.find((conf) => conf.position)) === null || _a === void 0 ? void 0 : _a.position) || "fixed",
|
|||
|
|
parent: ((_b = values.find((conf) => conf.parent)) === null || _b === void 0 ? void 0 : _b.parent) || null,
|
|||
|
|
tooltipSpace: ((_c = values.find((conf) => conf.tooltipSpace)) === null || _c === void 0 ? void 0 : _c.tooltipSpace) || windowSpace
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var knownHeight = /* @__PURE__ */ new WeakMap();
|
|||
|
|
var tooltipPlugin = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.above = [];
|
|||
|
|
this.inView = true;
|
|||
|
|
this.madeAbsolute = false;
|
|||
|
|
this.lastTransaction = 0;
|
|||
|
|
this.measureTimeout = -1;
|
|||
|
|
let config = view.state.facet(tooltipConfig);
|
|||
|
|
this.position = config.position;
|
|||
|
|
this.parent = config.parent;
|
|||
|
|
this.classes = view.themeClasses;
|
|||
|
|
this.createContainer();
|
|||
|
|
this.measureReq = { read: this.readMeasure.bind(this), write: this.writeMeasure.bind(this), key: this };
|
|||
|
|
this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
|
|||
|
|
this.manager = new TooltipViewManager(view, showTooltip, (t, p) => this.createTooltip(t, p), (t) => {
|
|||
|
|
if (this.resizeObserver)
|
|||
|
|
this.resizeObserver.unobserve(t.dom);
|
|||
|
|
t.dom.remove();
|
|||
|
|
});
|
|||
|
|
this.above = this.manager.tooltips.map((t) => !!t.above);
|
|||
|
|
this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver((entries) => {
|
|||
|
|
if (Date.now() > this.lastTransaction - 50 && entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
|
|||
|
|
this.measureSoon();
|
|||
|
|
}, { threshold: [1] }) : null;
|
|||
|
|
this.observeIntersection();
|
|||
|
|
view.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
|
|||
|
|
this.maybeMeasure();
|
|||
|
|
}
|
|||
|
|
createContainer() {
|
|||
|
|
if (this.parent) {
|
|||
|
|
this.container = document.createElement("div");
|
|||
|
|
this.container.style.position = "relative";
|
|||
|
|
this.container.className = this.view.themeClasses;
|
|||
|
|
this.parent.appendChild(this.container);
|
|||
|
|
} else {
|
|||
|
|
this.container = this.view.dom;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
observeIntersection() {
|
|||
|
|
if (this.intersectionObserver) {
|
|||
|
|
this.intersectionObserver.disconnect();
|
|||
|
|
for (let tooltip of this.manager.tooltipViews)
|
|||
|
|
this.intersectionObserver.observe(tooltip.dom);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
measureSoon() {
|
|||
|
|
if (this.measureTimeout < 0)
|
|||
|
|
this.measureTimeout = setTimeout(() => {
|
|||
|
|
this.measureTimeout = -1;
|
|||
|
|
this.maybeMeasure();
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (update.transactions.length)
|
|||
|
|
this.lastTransaction = Date.now();
|
|||
|
|
let updated = this.manager.update(update, this.above);
|
|||
|
|
if (updated)
|
|||
|
|
this.observeIntersection();
|
|||
|
|
let shouldMeasure = updated || update.geometryChanged;
|
|||
|
|
let newConfig = update.state.facet(tooltipConfig);
|
|||
|
|
if (newConfig.position != this.position && !this.madeAbsolute) {
|
|||
|
|
this.position = newConfig.position;
|
|||
|
|
for (let t of this.manager.tooltipViews)
|
|||
|
|
t.dom.style.position = this.position;
|
|||
|
|
shouldMeasure = true;
|
|||
|
|
}
|
|||
|
|
if (newConfig.parent != this.parent) {
|
|||
|
|
if (this.parent)
|
|||
|
|
this.container.remove();
|
|||
|
|
this.parent = newConfig.parent;
|
|||
|
|
this.createContainer();
|
|||
|
|
for (let t of this.manager.tooltipViews)
|
|||
|
|
this.container.appendChild(t.dom);
|
|||
|
|
shouldMeasure = true;
|
|||
|
|
} else if (this.parent && this.view.themeClasses != this.classes) {
|
|||
|
|
this.classes = this.container.className = this.view.themeClasses;
|
|||
|
|
}
|
|||
|
|
if (shouldMeasure)
|
|||
|
|
this.maybeMeasure();
|
|||
|
|
}
|
|||
|
|
createTooltip(tooltip, prev) {
|
|||
|
|
let tooltipView = tooltip.create(this.view);
|
|||
|
|
let before = prev ? prev.dom : null;
|
|||
|
|
tooltipView.dom.classList.add("cm-tooltip");
|
|||
|
|
if (tooltip.arrow && !tooltipView.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")) {
|
|||
|
|
let arrow = document.createElement("div");
|
|||
|
|
arrow.className = "cm-tooltip-arrow";
|
|||
|
|
tooltipView.dom.appendChild(arrow);
|
|||
|
|
}
|
|||
|
|
tooltipView.dom.style.position = this.position;
|
|||
|
|
tooltipView.dom.style.top = Outside;
|
|||
|
|
tooltipView.dom.style.left = "0px";
|
|||
|
|
this.container.insertBefore(tooltipView.dom, before);
|
|||
|
|
if (tooltipView.mount)
|
|||
|
|
tooltipView.mount(this.view);
|
|||
|
|
if (this.resizeObserver)
|
|||
|
|
this.resizeObserver.observe(tooltipView.dom);
|
|||
|
|
return tooltipView;
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
var _a, _b, _c;
|
|||
|
|
this.view.win.removeEventListener("resize", this.measureSoon);
|
|||
|
|
for (let tooltipView of this.manager.tooltipViews) {
|
|||
|
|
tooltipView.dom.remove();
|
|||
|
|
(_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
|
|||
|
|
}
|
|||
|
|
if (this.parent)
|
|||
|
|
this.container.remove();
|
|||
|
|
(_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|||
|
|
(_c = this.intersectionObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
|
|||
|
|
clearTimeout(this.measureTimeout);
|
|||
|
|
}
|
|||
|
|
readMeasure() {
|
|||
|
|
let scaleX = 1, scaleY = 1, makeAbsolute = false;
|
|||
|
|
if (this.position == "fixed" && this.manager.tooltipViews.length) {
|
|||
|
|
let { dom } = this.manager.tooltipViews[0];
|
|||
|
|
if (browser.safari) {
|
|||
|
|
let rect = dom.getBoundingClientRect();
|
|||
|
|
makeAbsolute = Math.abs(rect.top + 1e4) > 1 || Math.abs(rect.left) > 1;
|
|||
|
|
} else {
|
|||
|
|
makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (makeAbsolute || this.position == "absolute") {
|
|||
|
|
if (this.parent) {
|
|||
|
|
let rect = this.parent.getBoundingClientRect();
|
|||
|
|
if (rect.width && rect.height) {
|
|||
|
|
scaleX = rect.width / this.parent.offsetWidth;
|
|||
|
|
scaleY = rect.height / this.parent.offsetHeight;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
({ scaleX, scaleY } = this.view.viewState);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let visible = this.view.scrollDOM.getBoundingClientRect(), margins = getScrollMargins(this.view);
|
|||
|
|
return {
|
|||
|
|
visible: {
|
|||
|
|
left: visible.left + margins.left,
|
|||
|
|
top: visible.top + margins.top,
|
|||
|
|
right: visible.right - margins.right,
|
|||
|
|
bottom: visible.bottom - margins.bottom
|
|||
|
|
},
|
|||
|
|
parent: this.parent ? this.container.getBoundingClientRect() : this.view.dom.getBoundingClientRect(),
|
|||
|
|
pos: this.manager.tooltips.map((t, i) => {
|
|||
|
|
let tv = this.manager.tooltipViews[i];
|
|||
|
|
return tv.getCoords ? tv.getCoords(t.pos) : this.view.coordsAtPos(t.pos);
|
|||
|
|
}),
|
|||
|
|
size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
|
|||
|
|
space: this.view.state.facet(tooltipConfig).tooltipSpace(this.view),
|
|||
|
|
scaleX,
|
|||
|
|
scaleY,
|
|||
|
|
makeAbsolute
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
writeMeasure(measured) {
|
|||
|
|
var _a;
|
|||
|
|
if (measured.makeAbsolute) {
|
|||
|
|
this.madeAbsolute = true;
|
|||
|
|
this.position = "absolute";
|
|||
|
|
for (let t of this.manager.tooltipViews)
|
|||
|
|
t.dom.style.position = "absolute";
|
|||
|
|
}
|
|||
|
|
let { visible, space, scaleX, scaleY } = measured;
|
|||
|
|
let others = [];
|
|||
|
|
for (let i = 0; i < this.manager.tooltips.length; i++) {
|
|||
|
|
let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
|
|||
|
|
let pos = measured.pos[i], size = measured.size[i];
|
|||
|
|
if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) || pos.top >= Math.min(visible.bottom, space.bottom) || pos.right < Math.max(visible.left, space.left) - 0.1 || pos.left > Math.min(visible.right, space.right) + 0.1)) {
|
|||
|
|
dom.style.top = Outside;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
let arrow = tooltip.arrow ? tView.dom.querySelector(".cm-tooltip-arrow") : null;
|
|||
|
|
let arrowHeight = arrow ? 7 : 0;
|
|||
|
|
let width = size.right - size.left, height = (_a = knownHeight.get(tView)) !== null && _a !== void 0 ? _a : size.bottom - size.top;
|
|||
|
|
let offset = tView.offset || noOffset, ltr = this.view.textDirection == Direction.LTR;
|
|||
|
|
let left = size.width > space.right - space.left ? ltr ? space.left : space.right - size.width : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 : 0) + offset.x, space.right - width)) : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 : 0) - offset.x), space.right - width);
|
|||
|
|
let above = this.above[i];
|
|||
|
|
if (!tooltip.strictSide && (above ? pos.top - height - arrowHeight - offset.y < space.top : pos.bottom + height + arrowHeight + offset.y > space.bottom) && above == space.bottom - pos.bottom > pos.top - space.top)
|
|||
|
|
above = this.above[i] = !above;
|
|||
|
|
let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
|
|||
|
|
if (spaceVert < height && tView.resize !== false) {
|
|||
|
|
if (spaceVert < this.view.defaultLineHeight) {
|
|||
|
|
dom.style.top = Outside;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
knownHeight.set(tView, height);
|
|||
|
|
dom.style.height = (height = spaceVert) / scaleY + "px";
|
|||
|
|
} else if (dom.style.height) {
|
|||
|
|
dom.style.height = "";
|
|||
|
|
}
|
|||
|
|
let top2 = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
|
|||
|
|
let right = left + width;
|
|||
|
|
if (tView.overlap !== true) {
|
|||
|
|
for (let r of others)
|
|||
|
|
if (r.left < right && r.right > left && r.top < top2 + height && r.bottom > top2)
|
|||
|
|
top2 = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
|
|||
|
|
}
|
|||
|
|
if (this.position == "absolute") {
|
|||
|
|
dom.style.top = (top2 - measured.parent.top) / scaleY + "px";
|
|||
|
|
setLeftStyle(dom, (left - measured.parent.left) / scaleX);
|
|||
|
|
} else {
|
|||
|
|
dom.style.top = top2 / scaleY + "px";
|
|||
|
|
setLeftStyle(dom, left / scaleX);
|
|||
|
|
}
|
|||
|
|
if (arrow) {
|
|||
|
|
let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 - 7);
|
|||
|
|
arrow.style.left = arrowLeft / scaleX + "px";
|
|||
|
|
}
|
|||
|
|
if (tView.overlap !== true)
|
|||
|
|
others.push({ left, top: top2, right, bottom: top2 + height });
|
|||
|
|
dom.classList.toggle("cm-tooltip-above", above);
|
|||
|
|
dom.classList.toggle("cm-tooltip-below", !above);
|
|||
|
|
if (tView.positioned)
|
|||
|
|
tView.positioned(measured.space);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
maybeMeasure() {
|
|||
|
|
if (this.manager.tooltips.length) {
|
|||
|
|
if (this.view.inView)
|
|||
|
|
this.view.requestMeasure(this.measureReq);
|
|||
|
|
if (this.inView != this.view.inView) {
|
|||
|
|
this.inView = this.view.inView;
|
|||
|
|
if (!this.inView)
|
|||
|
|
for (let tv of this.manager.tooltipViews)
|
|||
|
|
tv.dom.style.top = Outside;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
eventObservers: {
|
|||
|
|
scroll() {
|
|||
|
|
this.maybeMeasure();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function setLeftStyle(elt, value) {
|
|||
|
|
let current = parseInt(elt.style.left, 10);
|
|||
|
|
if (isNaN(current) || Math.abs(value - current) > 1)
|
|||
|
|
elt.style.left = value + "px";
|
|||
|
|
}
|
|||
|
|
var baseTheme = EditorView.baseTheme({
|
|||
|
|
".cm-tooltip": {
|
|||
|
|
zIndex: 500,
|
|||
|
|
boxSizing: "border-box"
|
|||
|
|
},
|
|||
|
|
"&light .cm-tooltip": {
|
|||
|
|
border: "1px solid #bbb",
|
|||
|
|
backgroundColor: "#f5f5f5"
|
|||
|
|
},
|
|||
|
|
"&light .cm-tooltip-section:not(:first-child)": {
|
|||
|
|
borderTop: "1px solid #bbb"
|
|||
|
|
},
|
|||
|
|
"&dark .cm-tooltip": {
|
|||
|
|
backgroundColor: "#333338",
|
|||
|
|
color: "white"
|
|||
|
|
},
|
|||
|
|
".cm-tooltip-arrow": {
|
|||
|
|
height: `${7}px`,
|
|||
|
|
width: `${7 * 2}px`,
|
|||
|
|
position: "absolute",
|
|||
|
|
zIndex: -1,
|
|||
|
|
overflow: "hidden",
|
|||
|
|
"&:before, &:after": {
|
|||
|
|
content: "''",
|
|||
|
|
position: "absolute",
|
|||
|
|
width: 0,
|
|||
|
|
height: 0,
|
|||
|
|
borderLeft: `${7}px solid transparent`,
|
|||
|
|
borderRight: `${7}px solid transparent`
|
|||
|
|
},
|
|||
|
|
".cm-tooltip-above &": {
|
|||
|
|
bottom: `-${7}px`,
|
|||
|
|
"&:before": {
|
|||
|
|
borderTop: `${7}px solid #bbb`
|
|||
|
|
},
|
|||
|
|
"&:after": {
|
|||
|
|
borderTop: `${7}px solid #f5f5f5`,
|
|||
|
|
bottom: "1px"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
".cm-tooltip-below &": {
|
|||
|
|
top: `-${7}px`,
|
|||
|
|
"&:before": {
|
|||
|
|
borderBottom: `${7}px solid #bbb`
|
|||
|
|
},
|
|||
|
|
"&:after": {
|
|||
|
|
borderBottom: `${7}px solid #f5f5f5`,
|
|||
|
|
top: "1px"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"&dark .cm-tooltip .cm-tooltip-arrow": {
|
|||
|
|
"&:before": {
|
|||
|
|
borderTopColor: "#333338",
|
|||
|
|
borderBottomColor: "#333338"
|
|||
|
|
},
|
|||
|
|
"&:after": {
|
|||
|
|
borderTopColor: "transparent",
|
|||
|
|
borderBottomColor: "transparent"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var noOffset = { x: 0, y: 0 };
|
|||
|
|
var showTooltip = Facet.define({
|
|||
|
|
enables: [tooltipPlugin, baseTheme]
|
|||
|
|
});
|
|||
|
|
var showHoverTooltip = Facet.define({
|
|||
|
|
combine: (inputs) => inputs.reduce((a, i) => a.concat(i), [])
|
|||
|
|
});
|
|||
|
|
var HoverTooltipHost = class _HoverTooltipHost {
|
|||
|
|
// Needs to be static so that host tooltip instances always match
|
|||
|
|
static create(view) {
|
|||
|
|
return new _HoverTooltipHost(view);
|
|||
|
|
}
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.mounted = false;
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.classList.add("cm-tooltip-hover");
|
|||
|
|
this.manager = new TooltipViewManager(view, showHoverTooltip, (t, p) => this.createHostedView(t, p), (t) => t.dom.remove());
|
|||
|
|
}
|
|||
|
|
createHostedView(tooltip, prev) {
|
|||
|
|
let hostedView = tooltip.create(this.view);
|
|||
|
|
hostedView.dom.classList.add("cm-tooltip-section");
|
|||
|
|
this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
|
|||
|
|
if (this.mounted && hostedView.mount)
|
|||
|
|
hostedView.mount(this.view);
|
|||
|
|
return hostedView;
|
|||
|
|
}
|
|||
|
|
mount(view) {
|
|||
|
|
for (let hostedView of this.manager.tooltipViews) {
|
|||
|
|
if (hostedView.mount)
|
|||
|
|
hostedView.mount(view);
|
|||
|
|
}
|
|||
|
|
this.mounted = true;
|
|||
|
|
}
|
|||
|
|
positioned(space) {
|
|||
|
|
for (let hostedView of this.manager.tooltipViews) {
|
|||
|
|
if (hostedView.positioned)
|
|||
|
|
hostedView.positioned(space);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
this.manager.update(update);
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
var _a;
|
|||
|
|
for (let t of this.manager.tooltipViews)
|
|||
|
|
(_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
|
|||
|
|
}
|
|||
|
|
passProp(name) {
|
|||
|
|
let value = void 0;
|
|||
|
|
for (let view of this.manager.tooltipViews) {
|
|||
|
|
let given = view[name];
|
|||
|
|
if (given !== void 0) {
|
|||
|
|
if (value === void 0)
|
|||
|
|
value = given;
|
|||
|
|
else if (value !== given)
|
|||
|
|
return void 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
get offset() {
|
|||
|
|
return this.passProp("offset");
|
|||
|
|
}
|
|||
|
|
get getCoords() {
|
|||
|
|
return this.passProp("getCoords");
|
|||
|
|
}
|
|||
|
|
get overlap() {
|
|||
|
|
return this.passProp("overlap");
|
|||
|
|
}
|
|||
|
|
get resize() {
|
|||
|
|
return this.passProp("resize");
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var showHoverTooltipHost = showTooltip.compute([showHoverTooltip], (state) => {
|
|||
|
|
let tooltips2 = state.facet(showHoverTooltip);
|
|||
|
|
if (tooltips2.length === 0)
|
|||
|
|
return null;
|
|||
|
|
return {
|
|||
|
|
pos: Math.min(...tooltips2.map((t) => t.pos)),
|
|||
|
|
end: Math.max(...tooltips2.map((t) => {
|
|||
|
|
var _a;
|
|||
|
|
return (_a = t.end) !== null && _a !== void 0 ? _a : t.pos;
|
|||
|
|
})),
|
|||
|
|
create: HoverTooltipHost.create,
|
|||
|
|
above: tooltips2[0].above,
|
|||
|
|
arrow: tooltips2.some((t) => t.arrow)
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
var hoverPlugin = Facet.define();
|
|||
|
|
var HoverPlugin = class {
|
|||
|
|
constructor(view, source, field, locked, setHover, hoverTime) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.source = source;
|
|||
|
|
this.field = field;
|
|||
|
|
this.locked = locked;
|
|||
|
|
this.setHover = setHover;
|
|||
|
|
this.hoverTime = hoverTime;
|
|||
|
|
this.hoverTimeout = -1;
|
|||
|
|
this.restartTimeout = -1;
|
|||
|
|
this.pending = null;
|
|||
|
|
this.lastMove = { x: 0, y: 0, target: view.dom, time: 0 };
|
|||
|
|
this.checkHover = this.checkHover.bind(this);
|
|||
|
|
view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
|
|||
|
|
view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (this.pending) {
|
|||
|
|
this.pending = null;
|
|||
|
|
clearTimeout(this.restartTimeout);
|
|||
|
|
this.restartTimeout = setTimeout(() => this.startHover(), 20);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
get active() {
|
|||
|
|
return this.view.state.field(this.field);
|
|||
|
|
}
|
|||
|
|
checkHover() {
|
|||
|
|
this.hoverTimeout = -1;
|
|||
|
|
if (this.active.length)
|
|||
|
|
return;
|
|||
|
|
let hovered = Date.now() - this.lastMove.time;
|
|||
|
|
if (hovered < this.hoverTime)
|
|||
|
|
this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
|
|||
|
|
else
|
|||
|
|
this.startHover();
|
|||
|
|
}
|
|||
|
|
startHover() {
|
|||
|
|
clearTimeout(this.restartTimeout);
|
|||
|
|
let { view, lastMove } = this;
|
|||
|
|
let tile = view.docView.tile.nearest(lastMove.target);
|
|||
|
|
if (!tile)
|
|||
|
|
return;
|
|||
|
|
let pos, side = 1;
|
|||
|
|
if (tile.isWidget()) {
|
|||
|
|
pos = tile.posAtStart;
|
|||
|
|
} else {
|
|||
|
|
pos = view.posAtCoords(lastMove);
|
|||
|
|
if (pos == null)
|
|||
|
|
return;
|
|||
|
|
let posCoords = view.coordsAtPos(pos);
|
|||
|
|
if (!posCoords || lastMove.y < posCoords.top || lastMove.y > posCoords.bottom || lastMove.x < posCoords.left - view.defaultCharacterWidth || lastMove.x > posCoords.right + view.defaultCharacterWidth)
|
|||
|
|
return;
|
|||
|
|
let bidi = view.bidiSpans(view.state.doc.lineAt(pos)).find((s) => s.from <= pos && s.to >= pos);
|
|||
|
|
let rtl = bidi && bidi.dir == Direction.RTL ? -1 : 1;
|
|||
|
|
side = lastMove.x < posCoords.left ? -rtl : rtl;
|
|||
|
|
}
|
|||
|
|
this.activateHover(view, pos, side);
|
|||
|
|
}
|
|||
|
|
activateHover(view, pos, side, locked) {
|
|||
|
|
let open = this.source(view, pos, side);
|
|||
|
|
let done = (value) => {
|
|||
|
|
if (value && !(Array.isArray(value) && !value.length)) {
|
|||
|
|
let tooltips2 = Array.isArray(value) ? value : [value];
|
|||
|
|
if (locked)
|
|||
|
|
this.locked.set(tooltips2, locked);
|
|||
|
|
view.dispatch({ effects: this.setHover.of(tooltips2) });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
if (open && "then" in open) {
|
|||
|
|
let pending = this.pending = { pos };
|
|||
|
|
open.then((result) => {
|
|||
|
|
if (this.pending == pending) {
|
|||
|
|
this.pending = null;
|
|||
|
|
done(result);
|
|||
|
|
}
|
|||
|
|
}, (e) => logException(view.state, e, "hover tooltip"));
|
|||
|
|
} else {
|
|||
|
|
done(open);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
get tooltip() {
|
|||
|
|
let plugin2 = this.view.plugin(tooltipPlugin);
|
|||
|
|
let index = plugin2 ? plugin2.manager.tooltips.findIndex((t) => t.create == HoverTooltipHost.create) : -1;
|
|||
|
|
return index > -1 ? plugin2.manager.tooltipViews[index] : null;
|
|||
|
|
}
|
|||
|
|
mousemove(event) {
|
|||
|
|
var _a, _b;
|
|||
|
|
this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
|
|||
|
|
if (this.hoverTimeout < 0)
|
|||
|
|
this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
|
|||
|
|
let { active, tooltip } = this;
|
|||
|
|
if (active.length && !this.locked.has(active) && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
|
|||
|
|
let { pos } = active[0] || this.pending, end = (_b = (_a = active[0]) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : pos;
|
|||
|
|
if (pos == end ? this.view.posAtCoords(this.lastMove) != pos : !isOverRange(this.view, pos, end, event.clientX, event.clientY)) {
|
|||
|
|
this.view.dispatch({ effects: this.setHover.of([]) });
|
|||
|
|
this.pending = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
mouseleave(event) {
|
|||
|
|
clearTimeout(this.hoverTimeout);
|
|||
|
|
this.hoverTimeout = -1;
|
|||
|
|
let { active } = this;
|
|||
|
|
if (active.length && !this.locked.has(active)) {
|
|||
|
|
let { tooltip } = this;
|
|||
|
|
let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
|
|||
|
|
if (!inTooltip)
|
|||
|
|
this.view.dispatch({ effects: this.setHover.of([]) });
|
|||
|
|
else
|
|||
|
|
this.watchTooltipLeave(tooltip.dom);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
watchTooltipLeave(tooltip) {
|
|||
|
|
let watch = (event) => {
|
|||
|
|
tooltip.removeEventListener("mouseleave", watch);
|
|||
|
|
let { active } = this;
|
|||
|
|
if (active.length && !this.locked.has(active) && !this.view.dom.contains(event.relatedTarget))
|
|||
|
|
this.view.dispatch({ effects: this.setHover.of([]) });
|
|||
|
|
};
|
|||
|
|
tooltip.addEventListener("mouseleave", watch);
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
clearTimeout(this.hoverTimeout);
|
|||
|
|
clearTimeout(this.restartTimeout);
|
|||
|
|
this.view.dom.removeEventListener("mouseleave", this.mouseleave);
|
|||
|
|
this.view.dom.removeEventListener("mousemove", this.mousemove);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var tooltipMargin = 4;
|
|||
|
|
function isInTooltip(tooltip, event) {
|
|||
|
|
let { left, right, top: top2, bottom } = tooltip.getBoundingClientRect(), arrow;
|
|||
|
|
if (arrow = tooltip.querySelector(".cm-tooltip-arrow")) {
|
|||
|
|
let arrowRect = arrow.getBoundingClientRect();
|
|||
|
|
top2 = Math.min(arrowRect.top, top2);
|
|||
|
|
bottom = Math.max(arrowRect.bottom, bottom);
|
|||
|
|
}
|
|||
|
|
return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin && event.clientY >= top2 - tooltipMargin && event.clientY <= bottom + tooltipMargin;
|
|||
|
|
}
|
|||
|
|
function isOverRange(view, from, to, x, y, margin) {
|
|||
|
|
let rect = view.scrollDOM.getBoundingClientRect();
|
|||
|
|
let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
|
|||
|
|
if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
|
|||
|
|
return false;
|
|||
|
|
let pos = view.posAtCoords({ x, y }, false);
|
|||
|
|
return pos >= from && pos <= to;
|
|||
|
|
}
|
|||
|
|
function hoverTooltip(source, options = {}) {
|
|||
|
|
let setHover = StateEffect.define();
|
|||
|
|
let locked = /* @__PURE__ */ new WeakMap();
|
|||
|
|
let hoverState = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return [];
|
|||
|
|
},
|
|||
|
|
update(value, tr) {
|
|||
|
|
let lock = locked.get(value);
|
|||
|
|
if (value.length) {
|
|||
|
|
if (options.hideOnChange && (tr.docChanged || tr.selection))
|
|||
|
|
value = [];
|
|||
|
|
else if (lock && lock(tr))
|
|||
|
|
value = [];
|
|||
|
|
else if (options.hideOn)
|
|||
|
|
value = value.filter((v) => !options.hideOn(tr, v));
|
|||
|
|
}
|
|||
|
|
if (tr.docChanged && value.length) {
|
|||
|
|
let mapped = [];
|
|||
|
|
for (let tooltip of value) {
|
|||
|
|
let newPos = tr.changes.mapPos(tooltip.pos, -1, MapMode.TrackDel);
|
|||
|
|
if (newPos != null) {
|
|||
|
|
let copy = Object.assign(/* @__PURE__ */ Object.create(null), tooltip);
|
|||
|
|
copy.pos = newPos;
|
|||
|
|
if (copy.end != null)
|
|||
|
|
copy.end = tr.changes.mapPos(copy.end);
|
|||
|
|
mapped.push(copy);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
value = mapped;
|
|||
|
|
}
|
|||
|
|
for (let effect of tr.effects) {
|
|||
|
|
if (effect.is(setHover)) {
|
|||
|
|
value = effect.value;
|
|||
|
|
lock = void 0;
|
|||
|
|
}
|
|||
|
|
if (effect.is(closeHoverTooltipEffect) && !effect.value || effect.value == hoverState)
|
|||
|
|
value = [];
|
|||
|
|
}
|
|||
|
|
if (value.length && lock)
|
|||
|
|
locked.set(value, lock);
|
|||
|
|
return value;
|
|||
|
|
},
|
|||
|
|
provide: (f) => showHoverTooltip.from(f)
|
|||
|
|
});
|
|||
|
|
const plugin2 = ViewPlugin.define((view) => new HoverPlugin(
|
|||
|
|
view,
|
|||
|
|
source,
|
|||
|
|
hoverState,
|
|||
|
|
locked,
|
|||
|
|
setHover,
|
|||
|
|
options.hoverTime || 300
|
|||
|
|
/* Hover.Time */
|
|||
|
|
));
|
|||
|
|
return {
|
|||
|
|
active: hoverState,
|
|||
|
|
extension: [
|
|||
|
|
hoverState,
|
|||
|
|
plugin2,
|
|||
|
|
hoverPlugin.of(plugin2),
|
|||
|
|
showHoverTooltipHost
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
function activateHover(view, pos, side, options = {}) {
|
|||
|
|
var _a;
|
|||
|
|
let plugins = view.state.facet(hoverPlugin).map((p) => view.plugin(p)).filter((p) => !!p);
|
|||
|
|
if (options.tooltip && options.tooltip.active) {
|
|||
|
|
let found = plugins.find((p) => p.field == options.tooltip.active);
|
|||
|
|
if (found)
|
|||
|
|
plugins = [found];
|
|||
|
|
}
|
|||
|
|
for (let plugin2 of plugins)
|
|||
|
|
plugin2.activateHover(view, pos, side, (_a = options.until) !== null && _a !== void 0 ? _a : () => false);
|
|||
|
|
}
|
|||
|
|
function getTooltip(view, tooltip) {
|
|||
|
|
let plugin2 = view.plugin(tooltipPlugin);
|
|||
|
|
if (!plugin2)
|
|||
|
|
return null;
|
|||
|
|
let found = plugin2.manager.tooltips.indexOf(tooltip);
|
|||
|
|
return found < 0 ? null : plugin2.manager.tooltipViews[found];
|
|||
|
|
}
|
|||
|
|
function hasHoverTooltips(state) {
|
|||
|
|
return state.facet(showHoverTooltip).some((x) => x);
|
|||
|
|
}
|
|||
|
|
var closeHoverTooltipEffect = StateEffect.define();
|
|||
|
|
var closeHoverTooltips = closeHoverTooltipEffect.of(null);
|
|||
|
|
function closeHoverTooltip(tooltip) {
|
|||
|
|
return closeHoverTooltipEffect.of(tooltip.active);
|
|||
|
|
}
|
|||
|
|
function repositionTooltips(view) {
|
|||
|
|
let plugin2 = view.plugin(tooltipPlugin);
|
|||
|
|
if (plugin2)
|
|||
|
|
plugin2.maybeMeasure();
|
|||
|
|
}
|
|||
|
|
var panelConfig = Facet.define({
|
|||
|
|
combine(configs) {
|
|||
|
|
let topContainer, bottomContainer;
|
|||
|
|
for (let c of configs) {
|
|||
|
|
topContainer = topContainer || c.topContainer;
|
|||
|
|
bottomContainer = bottomContainer || c.bottomContainer;
|
|||
|
|
}
|
|||
|
|
return { topContainer, bottomContainer };
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
function panels(config) {
|
|||
|
|
return config ? [panelConfig.of(config)] : [];
|
|||
|
|
}
|
|||
|
|
function getPanel(view, panel) {
|
|||
|
|
let plugin2 = view.plugin(panelPlugin);
|
|||
|
|
let index = plugin2 ? plugin2.specs.indexOf(panel) : -1;
|
|||
|
|
return index > -1 ? plugin2.panels[index] : null;
|
|||
|
|
}
|
|||
|
|
var panelPlugin = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.input = view.state.facet(showPanel);
|
|||
|
|
this.specs = this.input.filter((s) => s);
|
|||
|
|
this.panels = this.specs.map((spec) => spec(view));
|
|||
|
|
let conf = view.state.facet(panelConfig);
|
|||
|
|
this.top = new PanelGroup(view, true, conf.topContainer);
|
|||
|
|
this.bottom = new PanelGroup(view, false, conf.bottomContainer);
|
|||
|
|
this.top.sync(this.panels.filter((p) => p.top));
|
|||
|
|
this.bottom.sync(this.panels.filter((p) => !p.top));
|
|||
|
|
for (let p of this.panels) {
|
|||
|
|
p.dom.classList.add("cm-panel");
|
|||
|
|
if (p.mount)
|
|||
|
|
p.mount();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let conf = update.state.facet(panelConfig);
|
|||
|
|
if (this.top.container != conf.topContainer) {
|
|||
|
|
this.top.sync([]);
|
|||
|
|
this.top = new PanelGroup(update.view, true, conf.topContainer);
|
|||
|
|
}
|
|||
|
|
if (this.bottom.container != conf.bottomContainer) {
|
|||
|
|
this.bottom.sync([]);
|
|||
|
|
this.bottom = new PanelGroup(update.view, false, conf.bottomContainer);
|
|||
|
|
}
|
|||
|
|
this.top.syncClasses();
|
|||
|
|
this.bottom.syncClasses();
|
|||
|
|
let input = update.state.facet(showPanel);
|
|||
|
|
if (input != this.input) {
|
|||
|
|
let specs = input.filter((x) => x);
|
|||
|
|
let panels2 = [], top2 = [], bottom = [], mount = [];
|
|||
|
|
for (let spec of specs) {
|
|||
|
|
let known = this.specs.indexOf(spec), panel;
|
|||
|
|
if (known < 0) {
|
|||
|
|
panel = spec(update.view);
|
|||
|
|
mount.push(panel);
|
|||
|
|
} else {
|
|||
|
|
panel = this.panels[known];
|
|||
|
|
if (panel.update)
|
|||
|
|
panel.update(update);
|
|||
|
|
}
|
|||
|
|
panels2.push(panel);
|
|||
|
|
(panel.top ? top2 : bottom).push(panel);
|
|||
|
|
}
|
|||
|
|
this.specs = specs;
|
|||
|
|
this.panels = panels2;
|
|||
|
|
this.top.sync(top2);
|
|||
|
|
this.bottom.sync(bottom);
|
|||
|
|
for (let p of mount) {
|
|||
|
|
p.dom.classList.add("cm-panel");
|
|||
|
|
if (p.mount)
|
|||
|
|
p.mount();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
for (let p of this.panels)
|
|||
|
|
if (p.update)
|
|||
|
|
p.update(update);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
this.top.sync([]);
|
|||
|
|
this.bottom.sync([]);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
provide: (plugin2) => EditorView.scrollMargins.of((view) => {
|
|||
|
|
let value = view.plugin(plugin2);
|
|||
|
|
return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
var PanelGroup = class {
|
|||
|
|
constructor(view, top2, container) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.top = top2;
|
|||
|
|
this.container = container;
|
|||
|
|
this.dom = void 0;
|
|||
|
|
this.classes = "";
|
|||
|
|
this.panels = [];
|
|||
|
|
this.syncClasses();
|
|||
|
|
}
|
|||
|
|
sync(panels2) {
|
|||
|
|
for (let p of this.panels)
|
|||
|
|
if (p.destroy && panels2.indexOf(p) < 0)
|
|||
|
|
p.destroy();
|
|||
|
|
this.panels = panels2;
|
|||
|
|
this.syncDOM();
|
|||
|
|
}
|
|||
|
|
syncDOM() {
|
|||
|
|
if (this.panels.length == 0) {
|
|||
|
|
if (this.dom) {
|
|||
|
|
this.dom.remove();
|
|||
|
|
this.dom = void 0;
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!this.dom) {
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
|
|||
|
|
this.dom.style[this.top ? "top" : "bottom"] = "0";
|
|||
|
|
let parent = this.container || this.view.dom;
|
|||
|
|
parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
|
|||
|
|
}
|
|||
|
|
let curDOM = this.dom.firstChild;
|
|||
|
|
for (let panel of this.panels) {
|
|||
|
|
if (panel.dom.parentNode == this.dom) {
|
|||
|
|
while (curDOM != panel.dom)
|
|||
|
|
curDOM = rm(curDOM);
|
|||
|
|
curDOM = curDOM.nextSibling;
|
|||
|
|
} else {
|
|||
|
|
this.dom.insertBefore(panel.dom, curDOM);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
while (curDOM)
|
|||
|
|
curDOM = rm(curDOM);
|
|||
|
|
}
|
|||
|
|
scrollMargin() {
|
|||
|
|
return !this.dom || this.container ? 0 : Math.max(0, this.top ? this.dom.getBoundingClientRect().bottom - Math.max(0, this.view.scrollDOM.getBoundingClientRect().top) : Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
|
|||
|
|
}
|
|||
|
|
syncClasses() {
|
|||
|
|
if (!this.container || this.classes == this.view.themeClasses)
|
|||
|
|
return;
|
|||
|
|
for (let cls of this.classes.split(" "))
|
|||
|
|
if (cls)
|
|||
|
|
this.container.classList.remove(cls);
|
|||
|
|
for (let cls of (this.classes = this.view.themeClasses).split(" "))
|
|||
|
|
if (cls)
|
|||
|
|
this.container.classList.add(cls);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function rm(node) {
|
|||
|
|
let next = node.nextSibling;
|
|||
|
|
node.remove();
|
|||
|
|
return next;
|
|||
|
|
}
|
|||
|
|
var showPanel = Facet.define({
|
|||
|
|
enables: panelPlugin
|
|||
|
|
});
|
|||
|
|
function showDialog(view, config) {
|
|||
|
|
let resolve;
|
|||
|
|
let promise = new Promise((r) => resolve = r);
|
|||
|
|
let panelCtor = (view2) => createDialog(view2, config, resolve);
|
|||
|
|
if (view.state.field(dialogField, false)) {
|
|||
|
|
view.dispatch({ effects: openDialogEffect.of(panelCtor) });
|
|||
|
|
} else {
|
|||
|
|
view.dispatch({ effects: StateEffect.appendConfig.of(dialogField.init(() => [panelCtor])) });
|
|||
|
|
}
|
|||
|
|
let close = closeDialogEffect.of(panelCtor);
|
|||
|
|
return { close, result: promise.then((form) => {
|
|||
|
|
let queue = view.win.queueMicrotask || ((f) => view.win.setTimeout(f, 10));
|
|||
|
|
queue(() => {
|
|||
|
|
if (view.state.field(dialogField).indexOf(panelCtor) > -1)
|
|||
|
|
view.dispatch({ effects: close });
|
|||
|
|
});
|
|||
|
|
return form;
|
|||
|
|
}) };
|
|||
|
|
}
|
|||
|
|
function getDialog(view, className) {
|
|||
|
|
let dialogs = view.state.field(dialogField, false) || [];
|
|||
|
|
for (let open of dialogs) {
|
|||
|
|
let panel = getPanel(view, open);
|
|||
|
|
if (panel && panel.dom.classList.contains(className))
|
|||
|
|
return panel;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
var dialogField = StateField.define({
|
|||
|
|
create() {
|
|||
|
|
return [];
|
|||
|
|
},
|
|||
|
|
update(dialogs, tr) {
|
|||
|
|
for (let e of tr.effects) {
|
|||
|
|
if (e.is(openDialogEffect))
|
|||
|
|
dialogs = [e.value].concat(dialogs);
|
|||
|
|
else if (e.is(closeDialogEffect))
|
|||
|
|
dialogs = dialogs.filter((d) => d != e.value);
|
|||
|
|
}
|
|||
|
|
return dialogs;
|
|||
|
|
},
|
|||
|
|
provide: (f) => showPanel.computeN([f], (state) => state.field(f))
|
|||
|
|
});
|
|||
|
|
var openDialogEffect = StateEffect.define();
|
|||
|
|
var closeDialogEffect = StateEffect.define();
|
|||
|
|
function createDialog(view, config, result) {
|
|||
|
|
let content = config.content ? config.content(view, () => done(null)) : null;
|
|||
|
|
if (!content) {
|
|||
|
|
content = crelt("form");
|
|||
|
|
if (config.input) {
|
|||
|
|
let input = crelt("input", config.input);
|
|||
|
|
if (/^(text|password|number|email|tel|url)$/.test(input.type))
|
|||
|
|
input.classList.add("cm-textfield");
|
|||
|
|
if (!input.name)
|
|||
|
|
input.name = "input";
|
|||
|
|
content.appendChild(crelt("label", (config.label || "") + ": ", input));
|
|||
|
|
} else {
|
|||
|
|
content.appendChild(document.createTextNode(config.label || ""));
|
|||
|
|
}
|
|||
|
|
content.appendChild(document.createTextNode(" "));
|
|||
|
|
content.appendChild(crelt("button", { class: "cm-button", type: "submit" }, config.submitLabel || "OK"));
|
|||
|
|
}
|
|||
|
|
let forms = content.nodeName == "FORM" ? [content] : content.querySelectorAll("form");
|
|||
|
|
for (let i = 0; i < forms.length; i++) {
|
|||
|
|
let form = forms[i];
|
|||
|
|
form.addEventListener("keydown", (event) => {
|
|||
|
|
if (event.keyCode == 27) {
|
|||
|
|
event.preventDefault();
|
|||
|
|
done(null);
|
|||
|
|
} else if (event.keyCode == 13) {
|
|||
|
|
event.preventDefault();
|
|||
|
|
done(form);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
form.addEventListener("submit", (event) => {
|
|||
|
|
event.preventDefault();
|
|||
|
|
done(form);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
let panel = crelt("div", content, crelt("button", {
|
|||
|
|
onclick: () => done(null),
|
|||
|
|
"aria-label": view.state.phrase("close"),
|
|||
|
|
class: "cm-dialog-close",
|
|||
|
|
type: "button"
|
|||
|
|
}, ["×"]));
|
|||
|
|
if (config.class)
|
|||
|
|
panel.className = config.class;
|
|||
|
|
panel.classList.add("cm-dialog");
|
|||
|
|
function done(form) {
|
|||
|
|
if (panel.contains(panel.ownerDocument.activeElement))
|
|||
|
|
view.focus();
|
|||
|
|
result(form);
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
dom: panel,
|
|||
|
|
top: config.top,
|
|||
|
|
mount: () => {
|
|||
|
|
if (config.focus) {
|
|||
|
|
let focus;
|
|||
|
|
if (typeof config.focus == "string")
|
|||
|
|
focus = content.querySelector(config.focus);
|
|||
|
|
else
|
|||
|
|
focus = content.querySelector("input") || content.querySelector("button");
|
|||
|
|
if (focus && "select" in focus)
|
|||
|
|
focus.select();
|
|||
|
|
else if (focus && "focus" in focus)
|
|||
|
|
focus.focus();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
var GutterMarker = class extends RangeValue {
|
|||
|
|
/**
|
|||
|
|
@internal
|
|||
|
|
*/
|
|||
|
|
compare(other) {
|
|||
|
|
return this == other || this.constructor == other.constructor && this.eq(other);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Compare this marker to another marker of the same type.
|
|||
|
|
*/
|
|||
|
|
eq(other) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
Called if the marker has a `toDOM` method and its representation
|
|||
|
|
was removed from a gutter.
|
|||
|
|
*/
|
|||
|
|
destroy(dom) {
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
GutterMarker.prototype.elementClass = "";
|
|||
|
|
GutterMarker.prototype.toDOM = void 0;
|
|||
|
|
GutterMarker.prototype.mapMode = MapMode.TrackBefore;
|
|||
|
|
GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
|
|||
|
|
GutterMarker.prototype.point = true;
|
|||
|
|
var gutterLineClass = Facet.define();
|
|||
|
|
var gutterWidgetClass = Facet.define();
|
|||
|
|
var defaults = {
|
|||
|
|
class: "",
|
|||
|
|
renderEmptyElements: false,
|
|||
|
|
elementStyle: "",
|
|||
|
|
markers: () => RangeSet.empty,
|
|||
|
|
lineMarker: () => null,
|
|||
|
|
widgetMarker: () => null,
|
|||
|
|
lineMarkerChange: null,
|
|||
|
|
initialSpacer: null,
|
|||
|
|
updateSpacer: null,
|
|||
|
|
domEventHandlers: {},
|
|||
|
|
side: "before"
|
|||
|
|
};
|
|||
|
|
var activeGutters = Facet.define();
|
|||
|
|
function gutter(config) {
|
|||
|
|
return [gutters(), activeGutters.of({ ...defaults, ...config })];
|
|||
|
|
}
|
|||
|
|
var unfixGutters = Facet.define({
|
|||
|
|
combine: (values) => values.some((x) => x)
|
|||
|
|
});
|
|||
|
|
function gutters(config) {
|
|||
|
|
let result = [
|
|||
|
|
gutterView
|
|||
|
|
];
|
|||
|
|
if (config && config.fixed === false)
|
|||
|
|
result.push(unfixGutters.of(true));
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
var gutterView = ViewPlugin.fromClass(class {
|
|||
|
|
constructor(view) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.domAfter = null;
|
|||
|
|
this.prevViewport = view.viewport;
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.className = "cm-gutters cm-gutters-before";
|
|||
|
|
this.dom.setAttribute("aria-hidden", "true");
|
|||
|
|
this.dom.style.minHeight = this.view.contentHeight / this.view.scaleY + "px";
|
|||
|
|
this.gutters = view.state.facet(activeGutters).map((conf) => new SingleGutterView(view, conf));
|
|||
|
|
this.fixed = !view.state.facet(unfixGutters);
|
|||
|
|
for (let gutter2 of this.gutters) {
|
|||
|
|
if (gutter2.config.side == "after")
|
|||
|
|
this.getDOMAfter().appendChild(gutter2.dom);
|
|||
|
|
else
|
|||
|
|
this.dom.appendChild(gutter2.dom);
|
|||
|
|
}
|
|||
|
|
if (this.fixed) {
|
|||
|
|
this.dom.style.position = "sticky";
|
|||
|
|
}
|
|||
|
|
this.syncGutters(false);
|
|||
|
|
view.scrollDOM.insertBefore(this.dom, view.contentDOM);
|
|||
|
|
}
|
|||
|
|
getDOMAfter() {
|
|||
|
|
if (!this.domAfter) {
|
|||
|
|
this.domAfter = document.createElement("div");
|
|||
|
|
this.domAfter.className = "cm-gutters cm-gutters-after";
|
|||
|
|
this.domAfter.setAttribute("aria-hidden", "true");
|
|||
|
|
this.domAfter.style.minHeight = this.view.contentHeight / this.view.scaleY + "px";
|
|||
|
|
this.domAfter.style.position = this.fixed ? "sticky" : "";
|
|||
|
|
this.view.scrollDOM.appendChild(this.domAfter);
|
|||
|
|
}
|
|||
|
|
return this.domAfter;
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
if (this.updateGutters(update)) {
|
|||
|
|
let vpA = this.prevViewport, vpB = update.view.viewport;
|
|||
|
|
let vpOverlap = Math.min(vpA.to, vpB.to) - Math.max(vpA.from, vpB.from);
|
|||
|
|
this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
|
|||
|
|
}
|
|||
|
|
if (update.geometryChanged) {
|
|||
|
|
let min = this.view.contentHeight / this.view.scaleY + "px";
|
|||
|
|
this.dom.style.minHeight = min;
|
|||
|
|
if (this.domAfter)
|
|||
|
|
this.domAfter.style.minHeight = min;
|
|||
|
|
}
|
|||
|
|
if (this.view.state.facet(unfixGutters) != !this.fixed) {
|
|||
|
|
this.fixed = !this.fixed;
|
|||
|
|
this.dom.style.position = this.fixed ? "sticky" : "";
|
|||
|
|
if (this.domAfter)
|
|||
|
|
this.domAfter.style.position = this.fixed ? "sticky" : "";
|
|||
|
|
}
|
|||
|
|
this.prevViewport = update.view.viewport;
|
|||
|
|
}
|
|||
|
|
syncGutters(detach) {
|
|||
|
|
let after = this.dom.nextSibling;
|
|||
|
|
if (detach) {
|
|||
|
|
this.dom.remove();
|
|||
|
|
if (this.domAfter)
|
|||
|
|
this.domAfter.remove();
|
|||
|
|
}
|
|||
|
|
let lineClasses = RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
|
|||
|
|
let classSet = [];
|
|||
|
|
let contexts = this.gutters.map((gutter2) => new UpdateContext(gutter2, this.view.viewport, -this.view.documentPadding.top));
|
|||
|
|
for (let line of this.view.viewportLineBlocks) {
|
|||
|
|
if (classSet.length)
|
|||
|
|
classSet = [];
|
|||
|
|
if (Array.isArray(line.type)) {
|
|||
|
|
let first = true;
|
|||
|
|
for (let b of line.type) {
|
|||
|
|
if (b.type == BlockType.Text && first) {
|
|||
|
|
advanceCursor(lineClasses, classSet, b.from);
|
|||
|
|
for (let cx of contexts)
|
|||
|
|
cx.line(this.view, b, classSet);
|
|||
|
|
first = false;
|
|||
|
|
} else if (b.widget) {
|
|||
|
|
for (let cx of contexts)
|
|||
|
|
cx.widget(this.view, b);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (line.type == BlockType.Text) {
|
|||
|
|
advanceCursor(lineClasses, classSet, line.from);
|
|||
|
|
for (let cx of contexts)
|
|||
|
|
cx.line(this.view, line, classSet);
|
|||
|
|
} else if (line.widget) {
|
|||
|
|
for (let cx of contexts)
|
|||
|
|
cx.widget(this.view, line);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let cx of contexts)
|
|||
|
|
cx.finish();
|
|||
|
|
if (detach) {
|
|||
|
|
this.view.scrollDOM.insertBefore(this.dom, after);
|
|||
|
|
if (this.domAfter)
|
|||
|
|
this.view.scrollDOM.appendChild(this.domAfter);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
updateGutters(update) {
|
|||
|
|
let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
|
|||
|
|
let change = update.docChanged || update.heightChanged || update.viewportChanged || !RangeSet.eq(update.startState.facet(gutterLineClass), update.state.facet(gutterLineClass), update.view.viewport.from, update.view.viewport.to);
|
|||
|
|
if (prev == cur) {
|
|||
|
|
for (let gutter2 of this.gutters)
|
|||
|
|
if (gutter2.update(update))
|
|||
|
|
change = true;
|
|||
|
|
} else {
|
|||
|
|
change = true;
|
|||
|
|
let gutters2 = [];
|
|||
|
|
for (let conf of cur) {
|
|||
|
|
let known = prev.indexOf(conf);
|
|||
|
|
if (known < 0) {
|
|||
|
|
gutters2.push(new SingleGutterView(this.view, conf));
|
|||
|
|
} else {
|
|||
|
|
this.gutters[known].update(update);
|
|||
|
|
gutters2.push(this.gutters[known]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (let g of this.gutters) {
|
|||
|
|
g.dom.remove();
|
|||
|
|
if (gutters2.indexOf(g) < 0)
|
|||
|
|
g.destroy();
|
|||
|
|
}
|
|||
|
|
for (let g of gutters2) {
|
|||
|
|
if (g.config.side == "after")
|
|||
|
|
this.getDOMAfter().appendChild(g.dom);
|
|||
|
|
else
|
|||
|
|
this.dom.appendChild(g.dom);
|
|||
|
|
}
|
|||
|
|
this.gutters = gutters2;
|
|||
|
|
}
|
|||
|
|
return change;
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
for (let view of this.gutters)
|
|||
|
|
view.destroy();
|
|||
|
|
this.dom.remove();
|
|||
|
|
if (this.domAfter)
|
|||
|
|
this.domAfter.remove();
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
provide: (plugin2) => EditorView.scrollMargins.of((view) => {
|
|||
|
|
let value = view.plugin(plugin2);
|
|||
|
|
if (!value || value.gutters.length == 0 || !value.fixed)
|
|||
|
|
return null;
|
|||
|
|
let before = value.dom.offsetWidth * view.scaleX, after = value.domAfter ? value.domAfter.offsetWidth * view.scaleX : 0;
|
|||
|
|
return view.textDirection == Direction.LTR ? { left: before, right: after } : { right: before, left: after };
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
function asArray(val) {
|
|||
|
|
return Array.isArray(val) ? val : [val];
|
|||
|
|
}
|
|||
|
|
function advanceCursor(cursor, collect, pos) {
|
|||
|
|
while (cursor.value && cursor.from <= pos) {
|
|||
|
|
if (cursor.from == pos)
|
|||
|
|
collect.push(cursor.value);
|
|||
|
|
cursor.next();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var UpdateContext = class {
|
|||
|
|
constructor(gutter2, viewport, height) {
|
|||
|
|
this.gutter = gutter2;
|
|||
|
|
this.height = height;
|
|||
|
|
this.i = 0;
|
|||
|
|
this.cursor = RangeSet.iter(gutter2.markers, viewport.from);
|
|||
|
|
}
|
|||
|
|
addElement(view, block, markers) {
|
|||
|
|
let { gutter: gutter2 } = this, above = (block.top - this.height) / view.scaleY, height = block.height / view.scaleY;
|
|||
|
|
if (this.i == gutter2.elements.length) {
|
|||
|
|
let newElt = new GutterElement(view, height, above, markers);
|
|||
|
|
gutter2.elements.push(newElt);
|
|||
|
|
gutter2.dom.appendChild(newElt.dom);
|
|||
|
|
} else {
|
|||
|
|
gutter2.elements[this.i].update(view, height, above, markers);
|
|||
|
|
}
|
|||
|
|
this.height = block.bottom;
|
|||
|
|
this.i++;
|
|||
|
|
}
|
|||
|
|
line(view, line, extraMarkers) {
|
|||
|
|
let localMarkers = [];
|
|||
|
|
advanceCursor(this.cursor, localMarkers, line.from);
|
|||
|
|
if (extraMarkers.length)
|
|||
|
|
localMarkers = localMarkers.concat(extraMarkers);
|
|||
|
|
let forLine = this.gutter.config.lineMarker(view, line, localMarkers);
|
|||
|
|
if (forLine)
|
|||
|
|
localMarkers.unshift(forLine);
|
|||
|
|
let gutter2 = this.gutter;
|
|||
|
|
if (localMarkers.length == 0 && !gutter2.config.renderEmptyElements)
|
|||
|
|
return;
|
|||
|
|
this.addElement(view, line, localMarkers);
|
|||
|
|
}
|
|||
|
|
widget(view, block) {
|
|||
|
|
let marker = this.gutter.config.widgetMarker(view, block.widget, block), markers = marker ? [marker] : null;
|
|||
|
|
for (let cls of view.state.facet(gutterWidgetClass)) {
|
|||
|
|
let marker2 = cls(view, block.widget, block);
|
|||
|
|
if (marker2)
|
|||
|
|
(markers || (markers = [])).push(marker2);
|
|||
|
|
}
|
|||
|
|
if (markers)
|
|||
|
|
this.addElement(view, block, markers);
|
|||
|
|
}
|
|||
|
|
finish() {
|
|||
|
|
let gutter2 = this.gutter;
|
|||
|
|
while (gutter2.elements.length > this.i) {
|
|||
|
|
let last = gutter2.elements.pop();
|
|||
|
|
gutter2.dom.removeChild(last.dom);
|
|||
|
|
last.destroy();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var SingleGutterView = class {
|
|||
|
|
constructor(view, config) {
|
|||
|
|
this.view = view;
|
|||
|
|
this.config = config;
|
|||
|
|
this.elements = [];
|
|||
|
|
this.spacer = null;
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.className = "cm-gutter" + (this.config.class ? " " + this.config.class : "");
|
|||
|
|
for (let prop in config.domEventHandlers) {
|
|||
|
|
this.dom.addEventListener(prop, (event) => {
|
|||
|
|
let target = event.target, y;
|
|||
|
|
if (target != this.dom && this.dom.contains(target)) {
|
|||
|
|
while (target.parentNode != this.dom)
|
|||
|
|
target = target.parentNode;
|
|||
|
|
let rect = target.getBoundingClientRect();
|
|||
|
|
y = (rect.top + rect.bottom) / 2;
|
|||
|
|
} else {
|
|||
|
|
y = event.clientY;
|
|||
|
|
}
|
|||
|
|
let line = view.lineBlockAtHeight(y - view.documentTop);
|
|||
|
|
if (config.domEventHandlers[prop](view, line, event))
|
|||
|
|
event.preventDefault();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
this.markers = asArray(config.markers(view));
|
|||
|
|
if (config.initialSpacer) {
|
|||
|
|
this.spacer = new GutterElement(view, 0, 0, [config.initialSpacer(view)]);
|
|||
|
|
this.dom.appendChild(this.spacer.dom);
|
|||
|
|
this.spacer.dom.style.cssText += "visibility: hidden; pointer-events: none";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
update(update) {
|
|||
|
|
let prevMarkers = this.markers;
|
|||
|
|
this.markers = asArray(this.config.markers(update.view));
|
|||
|
|
if (this.spacer && this.config.updateSpacer) {
|
|||
|
|
let updated = this.config.updateSpacer(this.spacer.markers[0], update);
|
|||
|
|
if (updated != this.spacer.markers[0])
|
|||
|
|
this.spacer.update(update.view, 0, 0, [updated]);
|
|||
|
|
}
|
|||
|
|
let vp = update.view.viewport;
|
|||
|
|
return !RangeSet.eq(this.markers, prevMarkers, vp.from, vp.to) || (this.config.lineMarkerChange ? this.config.lineMarkerChange(update) : false);
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
for (let elt of this.elements)
|
|||
|
|
elt.destroy();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var GutterElement = class {
|
|||
|
|
constructor(view, height, above, markers) {
|
|||
|
|
this.height = -1;
|
|||
|
|
this.above = 0;
|
|||
|
|
this.markers = [];
|
|||
|
|
this.dom = document.createElement("div");
|
|||
|
|
this.dom.className = "cm-gutterElement";
|
|||
|
|
this.update(view, height, above, markers);
|
|||
|
|
}
|
|||
|
|
update(view, height, above, markers) {
|
|||
|
|
if (this.height != height) {
|
|||
|
|
this.height = height;
|
|||
|
|
this.dom.style.height = height + "px";
|
|||
|
|
}
|
|||
|
|
if (this.above != above)
|
|||
|
|
this.dom.style.marginTop = (this.above = above) ? above + "px" : "";
|
|||
|
|
if (!sameMarkers(this.markers, markers))
|
|||
|
|
this.setMarkers(view, markers);
|
|||
|
|
}
|
|||
|
|
setMarkers(view, markers) {
|
|||
|
|
let cls = "cm-gutterElement", domPos = this.dom.firstChild;
|
|||
|
|
for (let iNew = 0, iOld = 0; ; ) {
|
|||
|
|
let skipTo = iOld, marker = iNew < markers.length ? markers[iNew++] : null, matched = false;
|
|||
|
|
if (marker) {
|
|||
|
|
let c = marker.elementClass;
|
|||
|
|
if (c)
|
|||
|
|
cls += " " + c;
|
|||
|
|
for (let i = iOld; i < this.markers.length; i++)
|
|||
|
|
if (this.markers[i].compare(marker)) {
|
|||
|
|
skipTo = i;
|
|||
|
|
matched = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
skipTo = this.markers.length;
|
|||
|
|
}
|
|||
|
|
while (iOld < skipTo) {
|
|||
|
|
let next = this.markers[iOld++];
|
|||
|
|
if (next.toDOM) {
|
|||
|
|
next.destroy(domPos);
|
|||
|
|
let after = domPos.nextSibling;
|
|||
|
|
domPos.remove();
|
|||
|
|
domPos = after;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!marker)
|
|||
|
|
break;
|
|||
|
|
if (marker.toDOM) {
|
|||
|
|
if (matched)
|
|||
|
|
domPos = domPos.nextSibling;
|
|||
|
|
else
|
|||
|
|
this.dom.insertBefore(marker.toDOM(view), domPos);
|
|||
|
|
}
|
|||
|
|
if (matched)
|
|||
|
|
iOld++;
|
|||
|
|
}
|
|||
|
|
this.dom.className = cls;
|
|||
|
|
this.markers = markers;
|
|||
|
|
}
|
|||
|
|
destroy() {
|
|||
|
|
this.setMarkers(null, []);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function sameMarkers(a, b) {
|
|||
|
|
if (a.length != b.length)
|
|||
|
|
return false;
|
|||
|
|
for (let i = 0; i < a.length; i++)
|
|||
|
|
if (!a[i].compare(b[i]))
|
|||
|
|
return false;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
var lineNumberMarkers = Facet.define();
|
|||
|
|
var lineNumberWidgetMarker = Facet.define();
|
|||
|
|
var lineNumberConfig = Facet.define({
|
|||
|
|
combine(values) {
|
|||
|
|
return combineConfig(values, { formatNumber: String, domEventHandlers: {} }, {
|
|||
|
|
domEventHandlers(a, b) {
|
|||
|
|
let result = Object.assign({}, a);
|
|||
|
|
for (let event in b) {
|
|||
|
|
let exists = result[event], add2 = b[event];
|
|||
|
|
result[event] = exists ? (view, line, event2) => exists(view, line, event2) || add2(view, line, event2) : add2;
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var NumberMarker = class extends GutterMarker {
|
|||
|
|
constructor(number) {
|
|||
|
|
super();
|
|||
|
|
this.number = number;
|
|||
|
|
}
|
|||
|
|
eq(other) {
|
|||
|
|
return this.number == other.number;
|
|||
|
|
}
|
|||
|
|
toDOM() {
|
|||
|
|
return document.createTextNode(this.number);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function formatNumber(view, number) {
|
|||
|
|
return view.state.facet(lineNumberConfig).formatNumber(number, view.state);
|
|||
|
|
}
|
|||
|
|
var lineNumberGutter = activeGutters.compute([lineNumberConfig], (state) => ({
|
|||
|
|
class: "cm-lineNumbers",
|
|||
|
|
renderEmptyElements: false,
|
|||
|
|
markers(view) {
|
|||
|
|
return view.state.facet(lineNumberMarkers);
|
|||
|
|
},
|
|||
|
|
lineMarker(view, line, others) {
|
|||
|
|
if (others.some((m) => m.toDOM))
|
|||
|
|
return null;
|
|||
|
|
return new NumberMarker(formatNumber(view, view.state.doc.lineAt(line.from).number));
|
|||
|
|
},
|
|||
|
|
widgetMarker: (view, widget, block) => {
|
|||
|
|
for (let m of view.state.facet(lineNumberWidgetMarker)) {
|
|||
|
|
let result = m(view, widget, block);
|
|||
|
|
if (result)
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
},
|
|||
|
|
lineMarkerChange: (update) => update.startState.facet(lineNumberConfig) != update.state.facet(lineNumberConfig),
|
|||
|
|
initialSpacer(view) {
|
|||
|
|
return new NumberMarker(formatNumber(view, maxLineNumber(view.state.doc.lines)));
|
|||
|
|
},
|
|||
|
|
updateSpacer(spacer, update) {
|
|||
|
|
let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
|
|||
|
|
return max == spacer.number ? spacer : new NumberMarker(max);
|
|||
|
|
},
|
|||
|
|
domEventHandlers: state.facet(lineNumberConfig).domEventHandlers,
|
|||
|
|
side: "before"
|
|||
|
|
}));
|
|||
|
|
function lineNumbers(config = {}) {
|
|||
|
|
return [
|
|||
|
|
lineNumberConfig.of(config),
|
|||
|
|
gutters(),
|
|||
|
|
lineNumberGutter
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
function maxLineNumber(lines) {
|
|||
|
|
let last = 9;
|
|||
|
|
while (last < lines)
|
|||
|
|
last = last * 10 + 9;
|
|||
|
|
return last;
|
|||
|
|
}
|
|||
|
|
var activeLineGutterMarker = new class extends GutterMarker {
|
|||
|
|
constructor() {
|
|||
|
|
super(...arguments);
|
|||
|
|
this.elementClass = "cm-activeLineGutter";
|
|||
|
|
}
|
|||
|
|
}();
|
|||
|
|
var activeLineGutterHighlighter = gutterLineClass.compute(["selection"], (state) => {
|
|||
|
|
let marks = [], last = -1;
|
|||
|
|
for (let range of state.selection.ranges) {
|
|||
|
|
let linePos = state.doc.lineAt(range.head).from;
|
|||
|
|
if (linePos > last) {
|
|||
|
|
last = linePos;
|
|||
|
|
marks.push(activeLineGutterMarker.range(linePos));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return RangeSet.of(marks);
|
|||
|
|
});
|
|||
|
|
function highlightActiveLineGutter() {
|
|||
|
|
return activeLineGutterHighlighter;
|
|||
|
|
}
|
|||
|
|
function matcher(decorator) {
|
|||
|
|
return ViewPlugin.define((view) => ({
|
|||
|
|
decorations: decorator.createDeco(view),
|
|||
|
|
update(u) {
|
|||
|
|
this.decorations = decorator.updateDeco(u, this.decorations);
|
|||
|
|
}
|
|||
|
|
}), {
|
|||
|
|
decorations: (v) => v.decorations
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
var tabDeco = Decoration.mark({ class: "cm-highlightTab" });
|
|||
|
|
var spaceDeco = Decoration.mark({ class: "cm-highlightSpace" });
|
|||
|
|
var whitespaceHighlighter = matcher(new MatchDecorator({
|
|||
|
|
regexp: /\t| /g,
|
|||
|
|
decoration: (match) => match[0] == " " ? tabDeco : spaceDeco,
|
|||
|
|
boundary: /\S/
|
|||
|
|
}));
|
|||
|
|
function highlightWhitespace() {
|
|||
|
|
return whitespaceHighlighter;
|
|||
|
|
}
|
|||
|
|
var trailingHighlighter = matcher(new MatchDecorator({
|
|||
|
|
regexp: /\s+$/g,
|
|||
|
|
decoration: Decoration.mark({ class: "cm-trailingSpace" })
|
|||
|
|
}));
|
|||
|
|
function highlightTrailingWhitespace() {
|
|||
|
|
return trailingHighlighter;
|
|||
|
|
}
|
|||
|
|
var __test = {
|
|||
|
|
HeightMap,
|
|||
|
|
HeightOracle,
|
|||
|
|
MeasuredHeights,
|
|||
|
|
QueryType,
|
|||
|
|
ChangedRange,
|
|||
|
|
computeOrder,
|
|||
|
|
moveVisually,
|
|||
|
|
clearHeightChangeFlag,
|
|||
|
|
getHeightChangeFlag: () => heightChangeFlag
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export {
|
|||
|
|
StyleModule,
|
|||
|
|
crelt,
|
|||
|
|
WidgetType,
|
|||
|
|
BlockType,
|
|||
|
|
Decoration,
|
|||
|
|
BlockWrapper,
|
|||
|
|
Direction,
|
|||
|
|
BidiSpan,
|
|||
|
|
logException,
|
|||
|
|
ViewPlugin,
|
|||
|
|
ViewUpdate,
|
|||
|
|
BlockInfo,
|
|||
|
|
EditorView,
|
|||
|
|
keymap,
|
|||
|
|
runScopeHandlers,
|
|||
|
|
RectangleMarker,
|
|||
|
|
layer,
|
|||
|
|
drawSelection,
|
|||
|
|
getDrawSelectionConfig,
|
|||
|
|
dropCursor,
|
|||
|
|
MatchDecorator,
|
|||
|
|
highlightSpecialChars,
|
|||
|
|
scrollPastEnd,
|
|||
|
|
highlightActiveLine,
|
|||
|
|
placeholder,
|
|||
|
|
rectangularSelection,
|
|||
|
|
crosshairCursor,
|
|||
|
|
tooltips,
|
|||
|
|
showTooltip,
|
|||
|
|
hoverTooltip,
|
|||
|
|
activateHover,
|
|||
|
|
getTooltip,
|
|||
|
|
hasHoverTooltips,
|
|||
|
|
closeHoverTooltips,
|
|||
|
|
closeHoverTooltip,
|
|||
|
|
repositionTooltips,
|
|||
|
|
panels,
|
|||
|
|
getPanel,
|
|||
|
|
showPanel,
|
|||
|
|
showDialog,
|
|||
|
|
getDialog,
|
|||
|
|
GutterMarker,
|
|||
|
|
gutterLineClass,
|
|||
|
|
gutterWidgetClass,
|
|||
|
|
gutter,
|
|||
|
|
gutters,
|
|||
|
|
lineNumberMarkers,
|
|||
|
|
lineNumberWidgetMarker,
|
|||
|
|
lineNumbers,
|
|||
|
|
highlightActiveLineGutter,
|
|||
|
|
highlightWhitespace,
|
|||
|
|
highlightTrailingWhitespace,
|
|||
|
|
__test
|
|||
|
|
};
|
|||
|
|
//# sourceMappingURL=chunk-M6T3QFJD.js.map
|