1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 19:42:38 +02:00
This commit is contained in:
DanieL 2023-02-13 14:41:08 -03:00
parent 64c36d9f4e
commit 0d0338876d
1197 changed files with 121461 additions and 179724 deletions

View file

@ -1,3 +1,89 @@
## 6.4.0 (2022-12-14)
### Bug fixes
Fix an issue where the extension would sometimes try to draw a disabled dialog at an outdated position, leading to plugin crashes.
### New features
A `tooltipClass` option to autocompletion can now be used to add additional CSS classes to the completion tooltip.
## 6.3.4 (2022-11-24)
### Bug fixes
Fix an issue where completion lists could end up being higher than the tooltip they were in.
## 6.3.3 (2022-11-18)
### Bug fixes
Set an explicit `box-sizing` style on completion icons so CSS resets don't mess them up.
Allow closing braces in templates to be escaped with a backslash.
## 6.3.2 (2022-11-15)
### Bug fixes
Fix a regression that could cause the completion dialog to stick around when it should be hidden.
## 6.3.1 (2022-11-14)
### Bug fixes
Fix a regression where transactions for picking a completion (without custom `apply` method) no longer had the `pickedCompletion` annotation.
Reduce flickering for completion sources without `validFor` info by temporarily showing a disabled tooltip while the completion updates.
Make sure completion info tooltips are kept within the space provided by the `tooltipSpace` option.
## 6.3.0 (2022-09-22)
### New features
Close bracket configuration now supports a `stringPrefixes` property that can be used to allow autoclosing of prefixed strings.
## 6.2.0 (2022-09-13)
### New features
Autocompletion now takes an `interactionDelay` option that can be used to control the delay between the time where completion opens and the time where commands like `acceptCompletion` affect it.
## 6.1.1 (2022-09-08)
### Bug fixes
Fix a bug that prevented transactions produced by `deleteBracketPair` from being marked as deletion user events.
Improve positioning of completion info tooltips so they are less likely to stick out of the screen on small displays.
## 6.1.0 (2022-07-19)
### New features
You can now provide a `compareCompletions` option to autocompletion to influence the way completions with the same match score are sorted.
The `selectOnOpen` option to autocompletion can be used to require explicitly selecting a completion option before `acceptCompletion` does anything.
## 6.0.4 (2022-07-07)
### Bug fixes
Remove a leftover `console.log` in bracket closing code.
## 6.0.3 (2022-07-04)
### Bug fixes
Fix a bug that caused `closeBrackets` to not close quotes when at the end of a syntactic construct that starts with a similar quote.
## 6.0.2 (2022-06-15)
### Bug fixes
Declare package dependencies as peer dependencies as an attempt to avoid duplicated package issues.
## 6.0.1 (2022-06-09)
### Bug fixes

View file

@ -179,7 +179,7 @@ function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = option.source;
if (typeof apply == "string")
view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
}
@ -233,7 +233,7 @@ class FuzzyMatcher {
if (chars.length == 1) {
let first = state.codePointAt(word, 0);
return first == chars[0] ? [0, 0, state.codePointSize(first)]
: first == folded[0] ? [-200 /* CaseFold */, 0, state.codePointSize(first)] : null;
: first == folded[0] ? [-200 /* Penalty.CaseFold */, 0, state.codePointSize(first)] : null;
}
let direct = word.indexOf(this.pattern);
if (direct == 0)
@ -261,7 +261,7 @@ class FuzzyMatcher {
let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
let hasLower = /[a-z]/.test(word), wordAdjacent = true;
// Go over the option's text, scanning for the various kinds of matches
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* Tp.NonWord */; i < e && byWordTo < len;) {
let next = state.codePointAt(word, i);
if (direct < 0) {
if (preciseTo < len && next == chars[preciseTo])
@ -279,9 +279,9 @@ class FuzzyMatcher {
}
}
let ch, type = next < 0xff
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
: ((ch = state.fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Tp.Lower */ : next >= 65 && next <= 90 ? 1 /* Tp.Upper */ : 0 /* Tp.NonWord */)
: ((ch = state.fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Tp.Upper */ : ch != ch.toUpperCase() ? 2 /* Tp.Lower */ : 0 /* Tp.NonWord */);
if (!i || type == 1 /* Tp.Upper */ && hasLower || prevType == 0 /* Tp.NonWord */ && type != 0 /* Tp.NonWord */) {
if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
byWord[byWordTo++] = i;
else if (byWord.length)
@ -291,17 +291,17 @@ class FuzzyMatcher {
i += state.codePointSize(next);
}
if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
if (adjacentTo == len && adjacentStart == 0)
return [-200 /* CaseFold */ - word.length, 0, adjacentEnd];
return [-200 /* Penalty.CaseFold */ - word.length, 0, adjacentEnd];
if (direct > -1)
return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
return [-700 /* Penalty.NotStart */ - word.length, direct, direct + this.pattern.length];
if (adjacentTo == len)
return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
return [-200 /* Penalty.CaseFold */ + -700 /* Penalty.NotStart */ - word.length, adjacentStart, adjacentEnd];
if (byWordTo == len)
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
(wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
(wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
return chars.length == 2 ? null : this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
}
result(score, positions, word) {
let result = [score - word.length], i = 1;
@ -322,18 +322,23 @@ const completionConfig = state.Facet.define({
combine(configs) {
return state.combineConfig(configs, {
activateOnTyping: true,
selectOnOpen: true,
override: null,
closeOnBlur: true,
maxRenderedOptions: 100,
defaultKeymap: true,
tooltipClass: () => "",
optionClass: () => "",
aboveCursor: false,
icons: true,
addToOptions: []
addToOptions: [],
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
icons: (a, b) => a && b,
tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
optionClass: (a, b) => c => joinClass(a(c), b(c)),
addToOptions: (a, b) => a.concat(b)
});
@ -392,6 +397,8 @@ function optionContent(config) {
function rangeAroundSelected(total, selected, max) {
if (total <= max)
return { from: 0, to: total };
if (selected < 0)
selected = 0;
if (selected <= (total >> 1)) {
let off = Math.floor(selected / max);
return { from: off * max, to: (off + 1) * max };
@ -409,14 +416,18 @@ class CompletionTooltip {
write: (pos) => this.positionInfo(pos),
key: this
};
this.space = null;
this.currentClass = "";
let cState = view.state.field(stateField);
let { options, selected } = cState.open;
let config = view.state.facet(completionConfig);
this.optionContent = optionContent(config);
this.optionClass = config.optionClass;
this.tooltipClass = config.tooltipClass;
this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
this.dom = document.createElement("div");
this.dom.className = "cm-tooltip-autocomplete";
this.updateTooltipClass(view.state);
this.dom.addEventListener("mousedown", (e) => {
for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
@ -434,16 +445,36 @@ class CompletionTooltip {
}
mount() { this.updateSel(); }
update(update) {
if (update.state.field(this.stateField) != update.startState.field(this.stateField))
var _a, _b, _c;
let cState = update.state.field(this.stateField);
let prevState = update.startState.field(this.stateField);
this.updateTooltipClass(update.state);
if (cState != prevState) {
this.updateSel();
if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
}
}
positioned() {
updateTooltipClass(state) {
let cls = this.tooltipClass(state);
if (cls != this.currentClass) {
for (let c of this.currentClass.split(" "))
if (c)
this.dom.classList.remove(c);
for (let c of cls.split(" "))
if (c)
this.dom.classList.add(c);
this.currentClass = cls;
}
}
positioned(space) {
this.space = space;
if (this.info)
this.view.requestMeasure(this.placeInfo);
}
updateSel() {
let cState = this.view.state.field(this.stateField), open = cState.open;
if (open.selected < this.range.from || open.selected >= this.range.to) {
if (open.selected > -1 && open.selected < this.range.from || open.selected >= this.range.to) {
this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
this.list.remove();
this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
@ -507,23 +538,49 @@ class CompletionTooltip {
let listRect = this.dom.getBoundingClientRect();
let infoRect = this.info.getBoundingClientRect();
let selRect = sel.getBoundingClientRect();
if (selRect.top > Math.min(innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
let space = this.space;
if (!space) {
let win = this.dom.ownerDocument.defaultView || window;
space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
}
if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
selRect.bottom < Math.max(space.top, listRect.top) + 10)
return null;
let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
let left = this.view.textDirection == view.Direction.RTL;
let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
let rtl = this.view.textDirection == view.Direction.RTL, left = rtl, narrow = false, maxWidth;
let top = "", bottom = "";
let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
left = false;
else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
left = true;
return { top, left };
if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
}
else {
narrow = true;
maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
let spaceBelow = space.bottom - listRect.bottom;
if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
top = (selRect.bottom - listRect.top) + "px";
else // Above it
bottom = (listRect.bottom - selRect.top) + "px";
}
return {
top, bottom, maxWidth,
class: narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right",
};
}
positionInfo(pos) {
if (this.info) {
this.info.style.top = (pos ? pos.top : -1e6) + "px";
if (pos) {
this.info.classList.toggle("cm-completionInfo-left", pos.left);
this.info.classList.toggle("cm-completionInfo-right", !pos.left);
this.info.style.top = pos.top;
this.info.style.bottom = pos.bottom;
this.info.style.maxWidth = pos.maxWidth;
this.info.className = "cm-tooltip cm-completionInfo cm-completionInfo-" + pos.class;
}
else {
this.info.style.top = "-1e6px";
}
}
}
@ -599,7 +656,8 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
for (let opt of options.sort(cmpOption)) {
let compare = state.facet(completionConfig).compareCompletions;
for (let opt of options.sort((a, b) => (b.match[0] - a.match[0]) || compare(a.completion, b.completion))) {
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
prev.apply != opt.completion.apply)
@ -611,23 +669,26 @@ function sortOptions(active, state) {
return result;
}
class CompletionDialog {
constructor(options, attrs, tooltip, timestamp, selected) {
constructor(options, attrs, tooltip, timestamp, selected, disabled) {
this.options = options;
this.attrs = attrs;
this.tooltip = tooltip;
this.timestamp = timestamp;
this.selected = selected;
this.disabled = disabled;
}
setSelected(selected, id) {
return selected == this.selected || selected >= this.options.length ? this
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf) {
let options = sortOptions(active, state);
if (!options.length)
return null;
let selected = 0;
if (prev && prev.selected) {
if (!options.length) {
return prev && active.some(a => a.state == 1 /* State.Pending */) ?
new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null;
}
let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
if (prev && prev.selected != selected && prev.selected != -1) {
let selectedValue = prev.options[prev.selected].completion;
for (let i = 0; i < options.length; i++)
if (options[i].completion == selectedValue) {
@ -639,10 +700,10 @@ class CompletionDialog {
pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
create: completionTooltip(completionState),
above: conf.aboveCursor,
}, prev ? prev.timestamp : Date.now(), selected);
}, prev ? prev.timestamp : Date.now(), selected, false);
}
map(changes) {
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected);
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
}
}
class CompletionState {
@ -660,16 +721,21 @@ class CompletionState {
state.languageDataAt("autocomplete", cur(state)).map(asSource);
let active = sources.map(source => {
let value = this.active.find(s => s.source == source) ||
new ActiveSource(source, this.active.some(a => a.state != 0 /* Inactive */) ? 1 /* Pending */ : 0 /* Inactive */);
new ActiveSource(source, this.active.some(a => a.state != 0 /* State.Inactive */) ? 1 /* State.Pending */ : 0 /* State.Inactive */);
return value.update(tr, conf);
});
if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
active = this.active;
let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
!sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
: this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
let open = this.open;
if (open && tr.docChanged)
open = open.map(tr.changes);
if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
!sameResults(active, this.active))
open = CompletionDialog.build(active, state, this.id, open, conf);
else if (open && open.disabled && !active.some(a => a.state == 1 /* State.Pending */))
open = null;
if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
for (let effect of tr.effects)
if (effect.is(setSelectedEffect))
open = open && open.setSelected(effect.value, this.id);
@ -697,20 +763,16 @@ const baseAttrs = {
"aria-autocomplete": "list"
};
function makeAttrs(id, selected) {
return {
let result = {
"aria-autocomplete": "list",
"aria-haspopup": "listbox",
"aria-activedescendant": id + "-" + selected,
"aria-controls": id
};
if (selected > -1)
result["aria-activedescendant"] = id + "-" + selected;
return result;
}
const none = [];
function cmpOption(a, b) {
let dScore = b.match[0] - a.match[0];
if (dScore)
return dScore;
return a.completion.label.localeCompare(b.completion.label);
}
function getUserEvent(tr) {
return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
}
@ -727,13 +789,13 @@ class ActiveSource {
value = value.handleUserEvent(tr, event, conf);
else if (tr.docChanged)
value = value.handleChange(tr);
else if (tr.selection && value.state != 0 /* Inactive */)
value = new ActiveSource(value.source, 0 /* Inactive */);
else if (tr.selection && value.state != 0 /* State.Inactive */)
value = new ActiveSource(value.source, 0 /* State.Inactive */);
for (let effect of tr.effects) {
if (effect.is(startCompletionEffect))
value = new ActiveSource(value.source, 1 /* Pending */, effect.value ? cur(tr.state) : -1);
value = new ActiveSource(value.source, 1 /* State.Pending */, effect.value ? cur(tr.state) : -1);
else if (effect.is(closeCompletionEffect))
value = new ActiveSource(value.source, 0 /* Inactive */);
value = new ActiveSource(value.source, 0 /* State.Inactive */);
else if (effect.is(setActiveEffect))
for (let active of effect.value)
if (active.source == value.source)
@ -742,10 +804,10 @@ class ActiveSource {
return value;
}
handleUserEvent(tr, type, conf) {
return type == "delete" || !conf.activateOnTyping ? this.map(tr.changes) : new ActiveSource(this.source, 1 /* Pending */);
return type == "delete" || !conf.activateOnTyping ? this.map(tr.changes) : new ActiveSource(this.source, 1 /* State.Pending */);
}
handleChange(tr) {
return tr.changes.touchesRange(cur(tr.startState)) ? new ActiveSource(this.source, 0 /* Inactive */) : this.map(tr.changes);
return tr.changes.touchesRange(cur(tr.startState)) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
}
map(changes) {
return changes.empty || this.explicitPos < 0 ? this : new ActiveSource(this.source, this.state, changes.mapPos(this.explicitPos));
@ -753,7 +815,7 @@ class ActiveSource {
}
class ActiveResult extends ActiveSource {
constructor(source, explicitPos, result, from, to) {
super(source, 2 /* Result */, explicitPos);
super(source, 2 /* State.Result */, explicitPos);
this.result = result;
this.from = from;
this.to = to;
@ -766,17 +828,17 @@ class ActiveResult extends ActiveSource {
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
pos > to ||
type == "delete" && cur(tr.startState) == this.from)
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* State.Pending */ : 0 /* State.Inactive */);
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
if (checkValid(this.result.validFor, tr.state, from, to))
return new ActiveResult(this.source, explicitPos, this.result, from, to);
if (this.result.update &&
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
return new ActiveSource(this.source, 1 /* State.Pending */, explicitPos);
}
handleChange(tr) {
return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* Inactive */) : this.map(tr.changes);
return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
}
map(mapping) {
return mapping.empty ? this :
@ -804,7 +866,6 @@ const completionState = state.StateField.define({
]
});
const CompletionInteractMargin = 75;
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@ -812,13 +873,15 @@ backward by the given amount.
function moveCompletionSelection(forward, by = "option") {
return (view$1) => {
let cState = view$1.state.field(completionState, false);
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
if (!cState || !cState.open || cState.open.disabled ||
Date.now() - cState.open.timestamp < view$1.state.facet(completionConfig).interactionDelay)
return false;
let step = 1, tooltip;
if (by == "page" && (tooltip = view.getTooltip(view$1, cState.open.tooltip)))
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
tooltip.dom.querySelector("li").offsetHeight) - 1);
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
let { length } = cState.open.options;
let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
if (selected < 0)
selected = by == "page" ? 0 : length - 1;
else if (selected >= length)
@ -832,9 +895,11 @@ Accept the current completion.
*/
const acceptCompletion = (view) => {
let cState = view.state.field(completionState, false);
if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
return false;
applyCompletion(view, cState.open.options[cState.open.selected]);
if (!cState.open.disabled)
applyCompletion(view, cState.open.options[cState.open.selected]);
return true;
};
/**
@ -852,7 +917,7 @@ Close the currently active completion.
*/
const closeCompletion = (view) => {
let cState = view.state.field(completionState, false);
if (!cState || !cState.active.some(a => a.state != 0 /* Inactive */))
if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
return false;
view.dispatch({ effects: closeCompletionEffect.of(null) });
return true;
@ -875,9 +940,9 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
this.debounceUpdate = -1;
this.running = [];
this.debounceAccept = -1;
this.composing = 0 /* None */;
this.composing = 0 /* CompositionState.None */;
for (let active of view.state.field(completionState).active)
if (active.state == 1 /* Pending */)
if (active.state == 1 /* State.Pending */)
this.startQuery(active);
}
update(update) {
@ -908,21 +973,21 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
}
if (this.debounceUpdate > -1)
clearTimeout(this.debounceUpdate);
this.debounceUpdate = cState.active.some(a => a.state == 1 /* Pending */ && !this.running.some(q => q.active.source == a.source))
this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
if (this.composing != 0 /* None */)
if (this.composing != 0 /* CompositionState.None */)
for (let tr of update.transactions) {
if (getUserEvent(tr) == "input")
this.composing = 2 /* Changed */;
else if (this.composing == 2 /* Changed */ && tr.selection)
this.composing = 3 /* ChangedAndMoved */;
this.composing = 2 /* CompositionState.Changed */;
else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
this.composing = 3 /* CompositionState.ChangedAndMoved */;
}
}
startUpdate() {
this.debounceUpdate = -1;
let { state } = this.view, cState = state.field(completionState);
for (let active of cState.active) {
if (active.state == 1 /* Pending */ && !this.running.some(r => r.active.source == active.source))
if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
this.startQuery(active);
}
}
@ -973,14 +1038,14 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
}
}
let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
if (current && current.state == 1 /* Pending */) {
if (current && current.state == 1 /* State.Pending */) {
if (query.done == null) {
// Explicitly failed. Should clear the pending status if it
// hasn't been re-set in the meantime.
let active = new ActiveSource(query.active.source, 0 /* Inactive */);
let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
for (let tr of query.updates)
active = active.update(tr, conf);
if (active.state != 1 /* Pending */)
if (active.state != 1 /* State.Pending */)
updated.push(active);
}
else {
@ -1000,15 +1065,15 @@ const completionPlugin = view.ViewPlugin.fromClass(class {
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
},
compositionstart() {
this.composing = 1 /* Started */;
this.composing = 1 /* CompositionState.Started */;
},
compositionend() {
if (this.composing == 3 /* ChangedAndMoved */) {
if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
// Safari fires compositionend events synchronously, possibly
// from inside an update, so dispatch asynchronously to avoid reentrancy
setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
}
this.composing = 0 /* None */;
this.composing = 0 /* CompositionState.None */;
}
}
});
@ -1023,6 +1088,7 @@ const baseTheme = view.EditorView.baseTheme({
maxWidth: "min(700px, 95vw)",
minWidth: "250px",
maxHeight: "10em",
height: "100%",
listStyle: "none",
margin: 0,
padding: 0,
@ -1039,10 +1105,16 @@ const baseTheme = view.EditorView.baseTheme({
background: "#17c",
color: "white",
},
"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
background: "#777",
},
"&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
background: "#347",
color: "white",
},
"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
background: "#444",
},
".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
content: '"···"',
opacity: 0.5,
@ -1053,16 +1125,20 @@ const baseTheme = view.EditorView.baseTheme({
position: "absolute",
padding: "3px 9px",
width: "max-content",
maxWidth: "300px",
maxWidth: `${400 /* Info.Width */}px`,
boxSizing: "border-box"
},
".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${30 /* Info.Margin */}px` },
".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${30 /* Info.Margin */}px` },
"&light .cm-snippetField": { backgroundColor: "#00000022" },
"&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
".cm-snippetFieldPosition": {
verticalAlign: "text-top",
width: 0,
height: "1.15em",
display: "inline-block",
margin: "0 -0.7px -.7em",
borderLeft: "1.4px dotted #888"
},
@ -1079,7 +1155,8 @@ const baseTheme = view.EditorView.baseTheme({
display: "inline-block",
textAlign: "center",
paddingRight: ".6em",
opacity: "0.6"
opacity: "0.6",
boxSizing: "content-box"
},
".cm-completionIcon-function, .cm-completionIcon-method": {
"&:after": { content: "'ƒ'" }
@ -1181,8 +1258,8 @@ class Snippet {
positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length));
line = line.slice(0, m.index) + name + line.slice(m.index + m[0].length);
}
for (let esc; esc = /([$#])\\{/.exec(line);) {
line = line.slice(0, esc.index) + esc[1] + "{" + line.slice(esc.index + esc[0].length);
for (let esc; esc = /\\([{}])/.exec(line);) {
line = line.slice(0, esc.index) + esc[1] + line.slice(esc.index + esc[0].length);
for (let pos of positions)
if (pos.line == lines.length && pos.from > esc.index) {
pos.from--;
@ -1273,10 +1350,9 @@ The order of fields defaults to textual order, but you can add
numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
a custom order.
To include a literal `${` or `#{` in your template, put a
backslash after the dollar or hash and before the brace (`$\\{`).
This will be removed and the sequence will not be interpreted as a
placeholder.
To include a literal `{` or `}` in your template, put a backslash
in front of it. This will be removed and the brace will not be
interpreted as indicating a placeholder.
*/
function snippet(template) {
let snippet = Snippet.parse(template);
@ -1391,7 +1467,7 @@ function storeWords(doc, wordRE, result, seen, ignoreAt) {
if (!seen[m[0]] && pos + m.index != ignoreAt) {
result.push({ type: "text", label: m[0] });
seen[m[0]] = true;
if (result.length >= 2000 /* MaxList */)
if (result.length >= 2000 /* C.MaxList */)
return;
}
}
@ -1399,7 +1475,7 @@ function storeWords(doc, wordRE, result, seen, ignoreAt) {
}
}
function collectWords(doc, cache, wordRE, to, ignoreAt) {
let big = doc.length >= 1000 /* MinCacheLen */;
let big = doc.length >= 1000 /* C.MinCacheLen */;
let cached = big && cache.get(doc);
if (cached)
return cached;
@ -1407,7 +1483,7 @@ function collectWords(doc, cache, wordRE, to, ignoreAt) {
if (doc.children) {
let pos = 0;
for (let ch of doc.children) {
if (ch.length >= 1000 /* MinCacheLen */) {
if (ch.length >= 1000 /* C.MinCacheLen */) {
for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
if (!seen[c.label]) {
seen[c.label] = true;
@ -1424,7 +1500,7 @@ function collectWords(doc, cache, wordRE, to, ignoreAt) {
else {
storeWords(doc, wordRE, result, seen, ignoreAt);
}
if (big && result.length < 2000 /* MaxList */)
if (big && result.length < 2000 /* C.MaxList */)
cache.set(doc, result);
return result;
}
@ -1440,13 +1516,14 @@ const completeAnyWord = context => {
if (!token && !context.explicit)
return null;
let from = token ? token.from : context.pos;
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* C.Range */, from);
return { from, options, validFor: mapRE(re, s => "^" + s) };
};
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>"
before: ")]}:;>",
stringPrefixes: []
};
const closeBracketEffect = state.StateEffect.define({
map(value, mapping) {
@ -1529,14 +1606,13 @@ const deleteBracketPair = ({ state: state$1, dispatch }) => {
for (let token of tokens) {
if (token == before && nextChar(state$1.doc, range.head) == closing(state.codePointAt(token, 0)))
return { changes: { from: range.head - token.length, to: range.head + token.length },
range: state.EditorSelection.cursor(range.head - token.length),
userEvent: "delete.backward" };
range: state.EditorSelection.cursor(range.head - token.length) };
}
}
return { range: dont = range };
});
if (!dont)
dispatch(state$1.update(changes, { scrollIntoView: true }));
dispatch(state$1.update(changes, { scrollIntoView: true, userEvent: "delete.backward" }));
return !dont;
};
/**
@ -1563,7 +1639,7 @@ function insertBracket(state$1, bracket) {
for (let tok of tokens) {
let closed = closing(state.codePointAt(tok, 0));
if (bracket == tok)
return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1)
return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
: handleOpen(state$1, tok, closed, conf.before || defaults.before);
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
return handleClose(state$1, tok, closed);
@ -1618,13 +1694,14 @@ function handleClose(state$1, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
function handleSame(state$1, token, allowTriple) {
function handleSame(state$1, token, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
let dont = null, changes = state$1.changeByRange(range => {
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state$1.doc, pos);
let pos = range.head, next = nextChar(state$1.doc, pos), start;
if (next == token) {
if (nodeStart(state$1, pos)) {
return { changes: { insert: token + token, from: pos },
@ -1638,14 +1715,14 @@ function handleSame(state$1, token, allowTriple) {
}
}
else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
nodeStart(state$1, pos - 2 * token.length)) {
(start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
nodeStart(state$1, start)) {
return { changes: { insert: token + token + token + token, from: pos },
effects: closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
}
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
let prev = state$1.sliceDoc(pos - 1, pos);
if (prev != token && state$1.charCategorizer(pos)(prev) != state.CharCategory.Word && !probablyInString(state$1, pos, token))
if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
return { changes: { insert: token + token, from: pos },
effects: closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
@ -1661,11 +1738,21 @@ function nodeStart(state, pos) {
let tree = language.syntaxTree(state).resolveInner(pos + 1);
return tree.parent && tree.from == pos;
}
function probablyInString(state, pos, quoteToken) {
function probablyInString(state, pos, quoteToken, prefixes) {
let node = language.syntaxTree(state).resolveInner(pos, -1);
let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
for (let i = 0; i < 5; i++) {
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
let quotePos = start.indexOf(quoteToken);
if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
let first = node.firstChild;
while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
return false;
first = first.firstChild;
}
return true;
}
let parent = node.to == pos && node.parent;
if (!parent)
break;
@ -1673,6 +1760,17 @@ function probablyInString(state, pos, quoteToken) {
}
return false;
}
function canStartStringAt(state$1, pos, prefixes) {
let charCat = state$1.charCategorizer(pos);
if (charCat(state$1.sliceDoc(pos - 1, pos)) != state.CharCategory.Word)
return pos;
for (let prefix of prefixes) {
let start = pos - prefix.length;
if (state$1.sliceDoc(start, pos) == prefix && charCat(state$1.sliceDoc(start - 1, start)) != state.CharCategory.Word)
return start;
}
return -1;
}
/**
Returns an extension that enables autocompletion.
@ -1715,8 +1813,8 @@ returns `null`.
*/
function completionStatus(state) {
let cState = state.field(completionState, false);
return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
: cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
return cState && cState.active.some(a => a.state == 1 /* State.Pending */) ? "pending"
: cState && cState.active.some(a => a.state != 0 /* State.Inactive */) ? "active" : null;
}
const completionArrayCache = new WeakMap;
/**
@ -1725,7 +1823,7 @@ Returns the available completions as an array.
function currentCompletions(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
if (!open)
if (!open || open.disabled)
return [];
let completions = completionArrayCache.get(open.options);
if (!completions)
@ -1738,7 +1836,7 @@ Return the currently selected completion, if any.
function selectedCompletion(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
return open ? open.options[open.selected].completion : null;
return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
}
/**
Returns the currently selected position in the active completion
@ -1747,7 +1845,7 @@ list, or null if no completions are active.
function selectedCompletionIndex(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
return open ? open.selected : null;
return open && !open.disabled && open.selected >= 0 ? open.selected : null;
}
/**
Create an effect that can be attached to a transaction to change

View file

@ -10,6 +10,15 @@ interface CompletionConfig {
*/
activateOnTyping?: boolean;
/**
By default, when completion opens, the first option is selected
and can be confirmed with
[`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion). When this
is set to false, the completion widget starts with no completion
selected, and the user has to explicitly move to a completion
before you can confirm one.
*/
selectOnOpen?: boolean;
/**
Override the completion sources used. By default, they will be
taken from the `"autocomplete"` [language
data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) (which should hold
@ -41,6 +50,11 @@ interface CompletionConfig {
*/
aboveCursor?: boolean;
/**
When given, this may return an additional CSS class to add to
the completion dialog element.
*/
tooltipClass?: (state: EditorState) => string;
/**
This can be used to add additional CSS classes to completion
options.
*/
@ -57,12 +71,26 @@ interface CompletionConfig {
completion, and should produce a DOM node to show. `position`
determines where in the DOM the result appears, relative to
other added widgets and the standard content. The default icons
have position 20, the label position 50, and the detail position 70.
have position 20, the label position 50, and the detail position
80.
*/
addToOptions?: {
render: (completion: Completion, state: EditorState) => Node | null;
position: number;
}[];
/**
The comparison function to use when sorting completions with the same
match score. Defaults to using
[`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
*/
compareCompletions?: (a: Completion, b: Completion) => number;
/**
By default, commands relating to an open completion only take
effect 75 milliseconds after the completion opened, so that key
presses made before the user is aware of the tooltip don't go to
the tooltip. This option can be used to configure that delay.
*/
interactionDelay?: number;
}
/**
@ -303,10 +331,9 @@ The order of fields defaults to textual order, but you can add
numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
a custom order.
To include a literal `${` or `#{` in your template, put a
backslash after the dollar or hash and before the brace (`$\\{`).
This will be removed and the sequence will not be interpreted as a
placeholder.
To include a literal `{` or `}` in your template, put a backslash
in front of it. This will be removed and the brace will not be
interpreted as indicating a placeholder.
*/
declare function snippet(template: string): (editor: {
state: EditorState;
@ -382,6 +409,11 @@ interface CloseBracketConfig {
whitespace. Defaults to `")]}:;>"`.
*/
before?: string;
/**
When determining whether a given node may be a string, recognize
these prefixes before the opening quote.
*/
stringPrefixes?: string[];
}
/**
Extension to enable bracket-closing behavior. When a closeable

View file

@ -175,7 +175,7 @@ function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = option.source;
if (typeof apply == "string")
view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
}
@ -229,7 +229,7 @@ class FuzzyMatcher {
if (chars.length == 1) {
let first = codePointAt(word, 0);
return first == chars[0] ? [0, 0, codePointSize(first)]
: first == folded[0] ? [-200 /* CaseFold */, 0, codePointSize(first)] : null;
: first == folded[0] ? [-200 /* Penalty.CaseFold */, 0, codePointSize(first)] : null;
}
let direct = word.indexOf(this.pattern);
if (direct == 0)
@ -257,7 +257,7 @@ class FuzzyMatcher {
let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
let hasLower = /[a-z]/.test(word), wordAdjacent = true;
// Go over the option's text, scanning for the various kinds of matches
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* NonWord */; i < e && byWordTo < len;) {
for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* Tp.NonWord */; i < e && byWordTo < len;) {
let next = codePointAt(word, i);
if (direct < 0) {
if (preciseTo < len && next == chars[preciseTo])
@ -275,9 +275,9 @@ class FuzzyMatcher {
}
}
let ch, type = next < 0xff
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Lower */ : next >= 65 && next <= 90 ? 1 /* Upper */ : 0 /* NonWord */)
: ((ch = fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Upper */ : ch != ch.toUpperCase() ? 2 /* Lower */ : 0 /* NonWord */);
if (!i || type == 1 /* Upper */ && hasLower || prevType == 0 /* NonWord */ && type != 0 /* NonWord */) {
? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Tp.Lower */ : next >= 65 && next <= 90 ? 1 /* Tp.Upper */ : 0 /* Tp.NonWord */)
: ((ch = fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Tp.Upper */ : ch != ch.toUpperCase() ? 2 /* Tp.Lower */ : 0 /* Tp.NonWord */);
if (!i || type == 1 /* Tp.Upper */ && hasLower || prevType == 0 /* Tp.NonWord */ && type != 0 /* Tp.NonWord */) {
if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
byWord[byWordTo++] = i;
else if (byWord.length)
@ -287,17 +287,17 @@ class FuzzyMatcher {
i += codePointSize(next);
}
if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0), byWord, word);
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
if (adjacentTo == len && adjacentStart == 0)
return [-200 /* CaseFold */ - word.length, 0, adjacentEnd];
return [-200 /* Penalty.CaseFold */ - word.length, 0, adjacentEnd];
if (direct > -1)
return [-700 /* NotStart */ - word.length, direct, direct + this.pattern.length];
return [-700 /* Penalty.NotStart */ - word.length, direct, direct + this.pattern.length];
if (adjacentTo == len)
return [-200 /* CaseFold */ + -700 /* NotStart */ - word.length, adjacentStart, adjacentEnd];
return [-200 /* Penalty.CaseFold */ + -700 /* Penalty.NotStart */ - word.length, adjacentStart, adjacentEnd];
if (byWordTo == len)
return this.result(-100 /* ByWord */ + (byWordFolded ? -200 /* CaseFold */ : 0) + -700 /* NotStart */ +
(wordAdjacent ? 0 : -1100 /* Gap */), byWord, word);
return chars.length == 2 ? null : this.result((any[0] ? -700 /* NotStart */ : 0) + -200 /* CaseFold */ + -1100 /* Gap */, any, word);
return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
(wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
return chars.length == 2 ? null : this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
}
result(score, positions, word) {
let result = [score - word.length], i = 1;
@ -318,18 +318,23 @@ const completionConfig = /*@__PURE__*/Facet.define({
combine(configs) {
return combineConfig(configs, {
activateOnTyping: true,
selectOnOpen: true,
override: null,
closeOnBlur: true,
maxRenderedOptions: 100,
defaultKeymap: true,
tooltipClass: () => "",
optionClass: () => "",
aboveCursor: false,
icons: true,
addToOptions: []
addToOptions: [],
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
icons: (a, b) => a && b,
tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
optionClass: (a, b) => c => joinClass(a(c), b(c)),
addToOptions: (a, b) => a.concat(b)
});
@ -388,6 +393,8 @@ function optionContent(config) {
function rangeAroundSelected(total, selected, max) {
if (total <= max)
return { from: 0, to: total };
if (selected < 0)
selected = 0;
if (selected <= (total >> 1)) {
let off = Math.floor(selected / max);
return { from: off * max, to: (off + 1) * max };
@ -405,14 +412,18 @@ class CompletionTooltip {
write: (pos) => this.positionInfo(pos),
key: this
};
this.space = null;
this.currentClass = "";
let cState = view.state.field(stateField);
let { options, selected } = cState.open;
let config = view.state.facet(completionConfig);
this.optionContent = optionContent(config);
this.optionClass = config.optionClass;
this.tooltipClass = config.tooltipClass;
this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
this.dom = document.createElement("div");
this.dom.className = "cm-tooltip-autocomplete";
this.updateTooltipClass(view.state);
this.dom.addEventListener("mousedown", (e) => {
for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
@ -430,16 +441,36 @@ class CompletionTooltip {
}
mount() { this.updateSel(); }
update(update) {
if (update.state.field(this.stateField) != update.startState.field(this.stateField))
var _a, _b, _c;
let cState = update.state.field(this.stateField);
let prevState = update.startState.field(this.stateField);
this.updateTooltipClass(update.state);
if (cState != prevState) {
this.updateSel();
if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
}
}
positioned() {
updateTooltipClass(state) {
let cls = this.tooltipClass(state);
if (cls != this.currentClass) {
for (let c of this.currentClass.split(" "))
if (c)
this.dom.classList.remove(c);
for (let c of cls.split(" "))
if (c)
this.dom.classList.add(c);
this.currentClass = cls;
}
}
positioned(space) {
this.space = space;
if (this.info)
this.view.requestMeasure(this.placeInfo);
}
updateSel() {
let cState = this.view.state.field(this.stateField), open = cState.open;
if (open.selected < this.range.from || open.selected >= this.range.to) {
if (open.selected > -1 && open.selected < this.range.from || open.selected >= this.range.to) {
this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
this.list.remove();
this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range));
@ -503,23 +534,49 @@ class CompletionTooltip {
let listRect = this.dom.getBoundingClientRect();
let infoRect = this.info.getBoundingClientRect();
let selRect = sel.getBoundingClientRect();
if (selRect.top > Math.min(innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
let space = this.space;
if (!space) {
let win = this.dom.ownerDocument.defaultView || window;
space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
}
if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
selRect.bottom < Math.max(space.top, listRect.top) + 10)
return null;
let top = Math.max(0, Math.min(selRect.top, innerHeight - infoRect.height)) - listRect.top;
let left = this.view.textDirection == Direction.RTL;
let spaceLeft = listRect.left, spaceRight = innerWidth - listRect.right;
let rtl = this.view.textDirection == Direction.RTL, left = rtl, narrow = false, maxWidth;
let top = "", bottom = "";
let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
left = false;
else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
left = true;
return { top, left };
if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
}
else {
narrow = true;
maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
let spaceBelow = space.bottom - listRect.bottom;
if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
top = (selRect.bottom - listRect.top) + "px";
else // Above it
bottom = (listRect.bottom - selRect.top) + "px";
}
return {
top, bottom, maxWidth,
class: narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right",
};
}
positionInfo(pos) {
if (this.info) {
this.info.style.top = (pos ? pos.top : -1e6) + "px";
if (pos) {
this.info.classList.toggle("cm-completionInfo-left", pos.left);
this.info.classList.toggle("cm-completionInfo-right", !pos.left);
this.info.style.top = pos.top;
this.info.style.bottom = pos.bottom;
this.info.style.maxWidth = pos.maxWidth;
this.info.className = "cm-tooltip cm-completionInfo cm-completionInfo-" + pos.class;
}
else {
this.info.style.top = "-1e6px";
}
}
}
@ -595,7 +652,8 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
for (let opt of options.sort(cmpOption)) {
let compare = state.facet(completionConfig).compareCompletions;
for (let opt of options.sort((a, b) => (b.match[0] - a.match[0]) || compare(a.completion, b.completion))) {
if (!prev || prev.label != opt.completion.label || prev.detail != opt.completion.detail ||
(prev.type != null && opt.completion.type != null && prev.type != opt.completion.type) ||
prev.apply != opt.completion.apply)
@ -607,23 +665,26 @@ function sortOptions(active, state) {
return result;
}
class CompletionDialog {
constructor(options, attrs, tooltip, timestamp, selected) {
constructor(options, attrs, tooltip, timestamp, selected, disabled) {
this.options = options;
this.attrs = attrs;
this.tooltip = tooltip;
this.timestamp = timestamp;
this.selected = selected;
this.disabled = disabled;
}
setSelected(selected, id) {
return selected == this.selected || selected >= this.options.length ? this
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf) {
let options = sortOptions(active, state);
if (!options.length)
return null;
let selected = 0;
if (prev && prev.selected) {
if (!options.length) {
return prev && active.some(a => a.state == 1 /* State.Pending */) ?
new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null;
}
let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
if (prev && prev.selected != selected && prev.selected != -1) {
let selectedValue = prev.options[prev.selected].completion;
for (let i = 0; i < options.length; i++)
if (options[i].completion == selectedValue) {
@ -635,10 +696,10 @@ class CompletionDialog {
pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
create: completionTooltip(completionState),
above: conf.aboveCursor,
}, prev ? prev.timestamp : Date.now(), selected);
}, prev ? prev.timestamp : Date.now(), selected, false);
}
map(changes) {
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected);
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
}
}
class CompletionState {
@ -656,16 +717,21 @@ class CompletionState {
state.languageDataAt("autocomplete", cur(state)).map(asSource);
let active = sources.map(source => {
let value = this.active.find(s => s.source == source) ||
new ActiveSource(source, this.active.some(a => a.state != 0 /* Inactive */) ? 1 /* Pending */ : 0 /* Inactive */);
new ActiveSource(source, this.active.some(a => a.state != 0 /* State.Inactive */) ? 1 /* State.Pending */ : 0 /* State.Inactive */);
return value.update(tr, conf);
});
if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
active = this.active;
let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
!sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
: this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
if (!open && active.every(a => a.state != 1 /* Pending */) && active.some(a => a.hasResult()))
active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* Inactive */) : a);
let open = this.open;
if (open && tr.docChanged)
open = open.map(tr.changes);
if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
!sameResults(active, this.active))
open = CompletionDialog.build(active, state, this.id, open, conf);
else if (open && open.disabled && !active.some(a => a.state == 1 /* State.Pending */))
open = null;
if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
for (let effect of tr.effects)
if (effect.is(setSelectedEffect))
open = open && open.setSelected(effect.value, this.id);
@ -693,20 +759,16 @@ const baseAttrs = {
"aria-autocomplete": "list"
};
function makeAttrs(id, selected) {
return {
let result = {
"aria-autocomplete": "list",
"aria-haspopup": "listbox",
"aria-activedescendant": id + "-" + selected,
"aria-controls": id
};
if (selected > -1)
result["aria-activedescendant"] = id + "-" + selected;
return result;
}
const none = [];
function cmpOption(a, b) {
let dScore = b.match[0] - a.match[0];
if (dScore)
return dScore;
return a.completion.label.localeCompare(b.completion.label);
}
function getUserEvent(tr) {
return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null;
}
@ -723,13 +785,13 @@ class ActiveSource {
value = value.handleUserEvent(tr, event, conf);
else if (tr.docChanged)
value = value.handleChange(tr);
else if (tr.selection && value.state != 0 /* Inactive */)
value = new ActiveSource(value.source, 0 /* Inactive */);
else if (tr.selection && value.state != 0 /* State.Inactive */)
value = new ActiveSource(value.source, 0 /* State.Inactive */);
for (let effect of tr.effects) {
if (effect.is(startCompletionEffect))
value = new ActiveSource(value.source, 1 /* Pending */, effect.value ? cur(tr.state) : -1);
value = new ActiveSource(value.source, 1 /* State.Pending */, effect.value ? cur(tr.state) : -1);
else if (effect.is(closeCompletionEffect))
value = new ActiveSource(value.source, 0 /* Inactive */);
value = new ActiveSource(value.source, 0 /* State.Inactive */);
else if (effect.is(setActiveEffect))
for (let active of effect.value)
if (active.source == value.source)
@ -738,10 +800,10 @@ class ActiveSource {
return value;
}
handleUserEvent(tr, type, conf) {
return type == "delete" || !conf.activateOnTyping ? this.map(tr.changes) : new ActiveSource(this.source, 1 /* Pending */);
return type == "delete" || !conf.activateOnTyping ? this.map(tr.changes) : new ActiveSource(this.source, 1 /* State.Pending */);
}
handleChange(tr) {
return tr.changes.touchesRange(cur(tr.startState)) ? new ActiveSource(this.source, 0 /* Inactive */) : this.map(tr.changes);
return tr.changes.touchesRange(cur(tr.startState)) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
}
map(changes) {
return changes.empty || this.explicitPos < 0 ? this : new ActiveSource(this.source, this.state, changes.mapPos(this.explicitPos));
@ -749,7 +811,7 @@ class ActiveSource {
}
class ActiveResult extends ActiveSource {
constructor(source, explicitPos, result, from, to) {
super(source, 2 /* Result */, explicitPos);
super(source, 2 /* State.Result */, explicitPos);
this.result = result;
this.from = from;
this.to = to;
@ -762,17 +824,17 @@ class ActiveResult extends ActiveSource {
if ((this.explicitPos < 0 ? pos <= from : pos < this.from) ||
pos > to ||
type == "delete" && cur(tr.startState) == this.from)
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* Pending */ : 0 /* Inactive */);
return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? 1 /* State.Pending */ : 0 /* State.Inactive */);
let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated;
if (checkValid(this.result.validFor, tr.state, from, to))
return new ActiveResult(this.source, explicitPos, this.result, from, to);
if (this.result.update &&
(updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0))))
return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state));
return new ActiveSource(this.source, 1 /* Pending */, explicitPos);
return new ActiveSource(this.source, 1 /* State.Pending */, explicitPos);
}
handleChange(tr) {
return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* Inactive */) : this.map(tr.changes);
return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource(this.source, 0 /* State.Inactive */) : this.map(tr.changes);
}
map(mapping) {
return mapping.empty ? this :
@ -800,7 +862,6 @@ const completionState = /*@__PURE__*/StateField.define({
]
});
const CompletionInteractMargin = 75;
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@ -808,13 +869,15 @@ backward by the given amount.
function moveCompletionSelection(forward, by = "option") {
return (view) => {
let cState = view.state.field(completionState, false);
if (!cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
if (!cState || !cState.open || cState.open.disabled ||
Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
return false;
let step = 1, tooltip;
if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
tooltip.dom.querySelector("li").offsetHeight) - 1);
let selected = cState.open.selected + step * (forward ? 1 : -1), { length } = cState.open.options;
let { length } = cState.open.options;
let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
if (selected < 0)
selected = by == "page" ? 0 : length - 1;
else if (selected >= length)
@ -828,9 +891,11 @@ Accept the current completion.
*/
const acceptCompletion = (view) => {
let cState = view.state.field(completionState, false);
if (view.state.readOnly || !cState || !cState.open || Date.now() - cState.open.timestamp < CompletionInteractMargin)
if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
return false;
applyCompletion(view, cState.open.options[cState.open.selected]);
if (!cState.open.disabled)
applyCompletion(view, cState.open.options[cState.open.selected]);
return true;
};
/**
@ -848,7 +913,7 @@ Close the currently active completion.
*/
const closeCompletion = (view) => {
let cState = view.state.field(completionState, false);
if (!cState || !cState.active.some(a => a.state != 0 /* Inactive */))
if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
return false;
view.dispatch({ effects: closeCompletionEffect.of(null) });
return true;
@ -871,9 +936,9 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
this.debounceUpdate = -1;
this.running = [];
this.debounceAccept = -1;
this.composing = 0 /* None */;
this.composing = 0 /* CompositionState.None */;
for (let active of view.state.field(completionState).active)
if (active.state == 1 /* Pending */)
if (active.state == 1 /* State.Pending */)
this.startQuery(active);
}
update(update) {
@ -904,21 +969,21 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
}
if (this.debounceUpdate > -1)
clearTimeout(this.debounceUpdate);
this.debounceUpdate = cState.active.some(a => a.state == 1 /* Pending */ && !this.running.some(q => q.active.source == a.source))
this.debounceUpdate = cState.active.some(a => a.state == 1 /* State.Pending */ && !this.running.some(q => q.active.source == a.source))
? setTimeout(() => this.startUpdate(), DebounceTime) : -1;
if (this.composing != 0 /* None */)
if (this.composing != 0 /* CompositionState.None */)
for (let tr of update.transactions) {
if (getUserEvent(tr) == "input")
this.composing = 2 /* Changed */;
else if (this.composing == 2 /* Changed */ && tr.selection)
this.composing = 3 /* ChangedAndMoved */;
this.composing = 2 /* CompositionState.Changed */;
else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
this.composing = 3 /* CompositionState.ChangedAndMoved */;
}
}
startUpdate() {
this.debounceUpdate = -1;
let { state } = this.view, cState = state.field(completionState);
for (let active of cState.active) {
if (active.state == 1 /* Pending */ && !this.running.some(r => r.active.source == active.source))
if (active.state == 1 /* State.Pending */ && !this.running.some(r => r.active.source == active.source))
this.startQuery(active);
}
}
@ -969,14 +1034,14 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
}
}
let current = this.view.state.field(completionState).active.find(a => a.source == query.active.source);
if (current && current.state == 1 /* Pending */) {
if (current && current.state == 1 /* State.Pending */) {
if (query.done == null) {
// Explicitly failed. Should clear the pending status if it
// hasn't been re-set in the meantime.
let active = new ActiveSource(query.active.source, 0 /* Inactive */);
let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
for (let tr of query.updates)
active = active.update(tr, conf);
if (active.state != 1 /* Pending */)
if (active.state != 1 /* State.Pending */)
updated.push(active);
}
else {
@ -996,15 +1061,15 @@ const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
this.view.dispatch({ effects: closeCompletionEffect.of(null) });
},
compositionstart() {
this.composing = 1 /* Started */;
this.composing = 1 /* CompositionState.Started */;
},
compositionend() {
if (this.composing == 3 /* ChangedAndMoved */) {
if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
// Safari fires compositionend events synchronously, possibly
// from inside an update, so dispatch asynchronously to avoid reentrancy
setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
}
this.composing = 0 /* None */;
this.composing = 0 /* CompositionState.None */;
}
}
});
@ -1019,6 +1084,7 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
maxWidth: "min(700px, 95vw)",
minWidth: "250px",
maxHeight: "10em",
height: "100%",
listStyle: "none",
margin: 0,
padding: 0,
@ -1035,10 +1101,16 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
background: "#17c",
color: "white",
},
"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
background: "#777",
},
"&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
background: "#347",
color: "white",
},
"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
background: "#444",
},
".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
content: '"···"',
opacity: 0.5,
@ -1049,16 +1121,20 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
position: "absolute",
padding: "3px 9px",
width: "max-content",
maxWidth: "300px",
maxWidth: `${400 /* Info.Width */}px`,
boxSizing: "border-box"
},
".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${30 /* Info.Margin */}px` },
".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${30 /* Info.Margin */}px` },
"&light .cm-snippetField": { backgroundColor: "#00000022" },
"&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
".cm-snippetFieldPosition": {
verticalAlign: "text-top",
width: 0,
height: "1.15em",
display: "inline-block",
margin: "0 -0.7px -.7em",
borderLeft: "1.4px dotted #888"
},
@ -1075,7 +1151,8 @@ const baseTheme = /*@__PURE__*/EditorView.baseTheme({
display: "inline-block",
textAlign: "center",
paddingRight: ".6em",
opacity: "0.6"
opacity: "0.6",
boxSizing: "content-box"
},
".cm-completionIcon-function, .cm-completionIcon-method": {
"&:after": { content: "'ƒ'" }
@ -1177,8 +1254,8 @@ class Snippet {
positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length));
line = line.slice(0, m.index) + name + line.slice(m.index + m[0].length);
}
for (let esc; esc = /([$#])\\{/.exec(line);) {
line = line.slice(0, esc.index) + esc[1] + "{" + line.slice(esc.index + esc[0].length);
for (let esc; esc = /\\([{}])/.exec(line);) {
line = line.slice(0, esc.index) + esc[1] + line.slice(esc.index + esc[0].length);
for (let pos of positions)
if (pos.line == lines.length && pos.from > esc.index) {
pos.from--;
@ -1269,10 +1346,9 @@ The order of fields defaults to textual order, but you can add
numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
a custom order.
To include a literal `${` or `#{` in your template, put a
backslash after the dollar or hash and before the brace (`$\\{`).
This will be removed and the sequence will not be interpreted as a
placeholder.
To include a literal `{` or `}` in your template, put a backslash
in front of it. This will be removed and the brace will not be
interpreted as indicating a placeholder.
*/
function snippet(template) {
let snippet = Snippet.parse(template);
@ -1387,7 +1463,7 @@ function storeWords(doc, wordRE, result, seen, ignoreAt) {
if (!seen[m[0]] && pos + m.index != ignoreAt) {
result.push({ type: "text", label: m[0] });
seen[m[0]] = true;
if (result.length >= 2000 /* MaxList */)
if (result.length >= 2000 /* C.MaxList */)
return;
}
}
@ -1395,7 +1471,7 @@ function storeWords(doc, wordRE, result, seen, ignoreAt) {
}
}
function collectWords(doc, cache, wordRE, to, ignoreAt) {
let big = doc.length >= 1000 /* MinCacheLen */;
let big = doc.length >= 1000 /* C.MinCacheLen */;
let cached = big && cache.get(doc);
if (cached)
return cached;
@ -1403,7 +1479,7 @@ function collectWords(doc, cache, wordRE, to, ignoreAt) {
if (doc.children) {
let pos = 0;
for (let ch of doc.children) {
if (ch.length >= 1000 /* MinCacheLen */) {
if (ch.length >= 1000 /* C.MinCacheLen */) {
for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
if (!seen[c.label]) {
seen[c.label] = true;
@ -1420,7 +1496,7 @@ function collectWords(doc, cache, wordRE, to, ignoreAt) {
else {
storeWords(doc, wordRE, result, seen, ignoreAt);
}
if (big && result.length < 2000 /* MaxList */)
if (big && result.length < 2000 /* C.MaxList */)
cache.set(doc, result);
return result;
}
@ -1436,13 +1512,14 @@ const completeAnyWord = context => {
if (!token && !context.explicit)
return null;
let from = token ? token.from : context.pos;
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from);
let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* C.Range */, from);
return { from, options, validFor: mapRE(re, s => "^" + s) };
};
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>"
before: ")]}:;>",
stringPrefixes: []
};
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
map(value, mapping) {
@ -1525,14 +1602,13 @@ const deleteBracketPair = ({ state, dispatch }) => {
for (let token of tokens) {
if (token == before && nextChar(state.doc, range.head) == closing(codePointAt(token, 0)))
return { changes: { from: range.head - token.length, to: range.head + token.length },
range: EditorSelection.cursor(range.head - token.length),
userEvent: "delete.backward" };
range: EditorSelection.cursor(range.head - token.length) };
}
}
return { range: dont = range };
});
if (!dont)
dispatch(state.update(changes, { scrollIntoView: true }));
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete.backward" }));
return !dont;
};
/**
@ -1559,7 +1635,7 @@ function insertBracket(state, bracket) {
for (let tok of tokens) {
let closed = closing(codePointAt(tok, 0));
if (bracket == tok)
return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1)
return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
: handleOpen(state, tok, closed, conf.before || defaults.before);
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
return handleClose(state, tok, closed);
@ -1614,13 +1690,14 @@ function handleClose(state, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
function handleSame(state, token, allowTriple) {
function handleSame(state, token, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
let dont = null, changes = state.changeByRange(range => {
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state.doc, pos);
let pos = range.head, next = nextChar(state.doc, pos), start;
if (next == token) {
if (nodeStart(state, pos)) {
return { changes: { insert: token + token, from: pos },
@ -1634,14 +1711,14 @@ function handleSame(state, token, allowTriple) {
}
}
else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
nodeStart(state, pos - 2 * token.length)) {
(start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
nodeStart(state, start)) {
return { changes: { insert: token + token + token + token, from: pos },
effects: closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
}
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
let prev = state.sliceDoc(pos - 1, pos);
if (prev != token && state.charCategorizer(pos)(prev) != CharCategory.Word && !probablyInString(state, pos, token))
if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
return { changes: { insert: token + token, from: pos },
effects: closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
@ -1657,11 +1734,21 @@ function nodeStart(state, pos) {
let tree = syntaxTree(state).resolveInner(pos + 1);
return tree.parent && tree.from == pos;
}
function probablyInString(state, pos, quoteToken) {
function probablyInString(state, pos, quoteToken, prefixes) {
let node = syntaxTree(state).resolveInner(pos, -1);
let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
for (let i = 0; i < 5; i++) {
if (state.sliceDoc(node.from, node.from + quoteToken.length) == quoteToken)
let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
let quotePos = start.indexOf(quoteToken);
if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
let first = node.firstChild;
while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
return false;
first = first.firstChild;
}
return true;
}
let parent = node.to == pos && node.parent;
if (!parent)
break;
@ -1669,6 +1756,17 @@ function probablyInString(state, pos, quoteToken) {
}
return false;
}
function canStartStringAt(state, pos, prefixes) {
let charCat = state.charCategorizer(pos);
if (charCat(state.sliceDoc(pos - 1, pos)) != CharCategory.Word)
return pos;
for (let prefix of prefixes) {
let start = pos - prefix.length;
if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != CharCategory.Word)
return start;
}
return -1;
}
/**
Returns an extension that enables autocompletion.
@ -1711,8 +1809,8 @@ returns `null`.
*/
function completionStatus(state) {
let cState = state.field(completionState, false);
return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending"
: cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null;
return cState && cState.active.some(a => a.state == 1 /* State.Pending */) ? "pending"
: cState && cState.active.some(a => a.state != 0 /* State.Inactive */) ? "active" : null;
}
const completionArrayCache = /*@__PURE__*/new WeakMap;
/**
@ -1721,7 +1819,7 @@ Returns the available completions as an array.
function currentCompletions(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
if (!open)
if (!open || open.disabled)
return [];
let completions = completionArrayCache.get(open.options);
if (!completions)
@ -1734,7 +1832,7 @@ Return the currently selected completion, if any.
function selectedCompletion(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
return open ? open.options[open.selected].completion : null;
return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
}
/**
Returns the currently selected position in the active completion
@ -1743,7 +1841,7 @@ list, or null if no completions are active.
function selectedCompletionIndex(state) {
var _a;
let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
return open ? open.selected : null;
return open && !open.disabled && open.selected >= 0 ? open.selected : null;
}
/**
Create an effect that can be attached to a transaction to change

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/autocomplete",
"version": "6.0.1",
"version": "6.4.0",
"description": "Autocompletion for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",
@ -26,6 +26,12 @@
"sideEffects": false,
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.6.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,47 @@
## 6.2.0 (2023-01-18)
### New features
The new `joinToEvent` history configuration option allows you to provide custom logic that determines whether a new transaction is added to an existing history event.
## 6.1.3 (2022-12-26)
### Bug fixes
Preserve selection bidi level when extending the selection, to prevent shift-selection from getting stuck in some kinds of bidirectional text.
## 6.1.2 (2022-10-13)
### Bug fixes
Fix a bug that caused deletion commands on non-empty ranges to incorrectly return false and do nothing, causing the editor to fall back to native behavior.
## 6.1.1 (2022-09-28)
### Bug fixes
Make sure the selection endpoints are moved out of atomic ranges when applying a deletion command to a non-empty selection.
## 6.1.0 (2022-08-18)
### Bug fixes
Prevent native behavior on Ctrl/Cmd-ArrowLeft/ArrowRight bindings, so that browsers with odd bidi behavior won't do the wrong thing at start/end of line.
Cmd-ArrowLeft/Right on macOS now moves the cursor in the direction of the arrow even in right-to-left content.
### New features
The new `cursorLineBoundaryLeft`/`Right` and `selectLineBoundaryLeft`/`Right` commands allow directional motion to line boundaries.
## 6.0.1 (2022-06-30)
### Bug fixes
Announce to the screen reader when the selection is deleted.
Also bind Ctrl-Shift-z to redo on Linux.
## 6.0.0 (2022-06-08)
### Bug fixes

View file

@ -1,13 +1,13 @@
# @codemirror/commands [![NPM version](https://img.shields.io/npm/v/@codemirror/commands.svg)](https://www.npmjs.org/package/@codemirror/commands)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#commands) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/commands/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#commands) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/commands/blob/main/CHANGELOG.md) ]
This package implements a collection of editing commands for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/commands/tree/main/LICENSE).

View file

@ -32,35 +32,35 @@ The line comment syntax is taken from the
[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language
data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt).
*/
const toggleLineComment = command(changeLineComment, 0 /* Toggle */);
const toggleLineComment = command(changeLineComment, 0 /* CommentOption.Toggle */);
/**
Comment the current selection using line comments.
*/
const lineComment = command(changeLineComment, 1 /* Comment */);
const lineComment = command(changeLineComment, 1 /* CommentOption.Comment */);
/**
Uncomment the current selection using line comments.
*/
const lineUncomment = command(changeLineComment, 2 /* Uncomment */);
const lineUncomment = command(changeLineComment, 2 /* CommentOption.Uncomment */);
/**
Comment or uncomment the current selection using block comments.
The block comment syntax is taken from the
[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language
data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt).
*/
const toggleBlockComment = command(changeBlockComment, 0 /* Toggle */);
const toggleBlockComment = command(changeBlockComment, 0 /* CommentOption.Toggle */);
/**
Comment the current selection using block comments.
*/
const blockComment = command(changeBlockComment, 1 /* Comment */);
const blockComment = command(changeBlockComment, 1 /* CommentOption.Comment */);
/**
Uncomment the current selection using block comments.
*/
const blockUncomment = command(changeBlockComment, 2 /* Uncomment */);
const blockUncomment = command(changeBlockComment, 2 /* CommentOption.Uncomment */);
/**
Comment or uncomment the lines around the current selection using
block comments.
*/
const toggleBlockCommentByLine = command((o, s) => changeBlockComment(o, s, selectedLineRanges(s)), 0 /* Toggle */);
const toggleBlockCommentByLine = command((o, s) => changeBlockComment(o, s, selectedLineRanges(s)), 0 /* CommentOption.Toggle */);
function getConfig(state, pos = state.selection.main.head) {
let data = state.languageDataAt("commentTokens", pos);
return data.length ? data[0] : {};
@ -119,14 +119,14 @@ function changeBlockComment(option, state, ranges = state.selection.ranges) {
if (!tokens.every(c => c))
return null;
let comments = ranges.map((r, i) => findBlockComment(state, tokens[i], r.from, r.to));
if (option != 2 /* Uncomment */ && !comments.every(c => c)) {
if (option != 2 /* CommentOption.Uncomment */ && !comments.every(c => c)) {
return { changes: state.changes(ranges.map((range, i) => {
if (comments[i])
return [];
return [{ from: range.from, insert: tokens[i].open + " " }, { from: range.to, insert: " " + tokens[i].close }];
})) };
}
else if (option != 1 /* Comment */ && comments.some(c => c)) {
else if (option != 1 /* CommentOption.Comment */ && comments.some(c => c)) {
let changes = [];
for (let i = 0, comment; i < comments.length; i++)
if (comment = comments[i]) {
@ -166,7 +166,7 @@ function changeLineComment(option, state, ranges = state.selection.ranges) {
if (lines.length == startI + 1)
lines[startI].single = true;
}
if (option != 2 /* Uncomment */ && lines.some(l => l.comment < 0 && (!l.empty || l.single))) {
if (option != 2 /* CommentOption.Uncomment */ && lines.some(l => l.comment < 0 && (!l.empty || l.single))) {
let changes = [];
for (let { line, token, indent, empty, single } of lines)
if (single || !empty)
@ -174,7 +174,7 @@ function changeLineComment(option, state, ranges = state.selection.ranges) {
let changeSet = state.changes(changes);
return { changes: changeSet, selection: state.selection.map(changeSet, 1) };
}
else if (option != 1 /* Comment */ && lines.some(l => l.comment >= 0)) {
else if (option != 1 /* CommentOption.Comment */ && lines.some(l => l.comment >= 0)) {
let changes = [];
for (let { line, comment, token } of lines)
if (comment >= 0) {
@ -209,8 +209,13 @@ const historyConfig = state.Facet.define({
combine(configs) {
return state.combineConfig(configs, {
minDepth: 100,
newGroupDelay: 500
}, { minDepth: Math.max, newGroupDelay: Math.min });
newGroupDelay: 500,
joinToEvent: (_t, isAdjacent) => isAdjacent,
}, {
minDepth: Math.max,
newGroupDelay: Math.min,
joinToEvent: (a, b) => (tr, adj) => a(tr, adj) || b(tr, adj)
});
}
});
function changeEnd(changes) {
@ -228,12 +233,12 @@ const historyField_ = state.StateField.define({
if (fromHist) {
let selection = tr.docChanged ? state.EditorSelection.single(changeEnd(tr.changes)) : undefined;
let item = HistEvent.fromTransaction(tr, selection), from = fromHist.side;
let other = from == 0 /* Done */ ? state$1.undone : state$1.done;
let other = from == 0 /* BranchName.Done */ ? state$1.undone : state$1.done;
if (item)
other = updateBranch(other, other.length, config.minDepth, item);
else
other = addSelection(other, tr.startState.selection);
return new HistoryState(from == 0 /* Done */ ? fromHist.rest : other, from == 0 /* Done */ ? other : fromHist.rest);
return new HistoryState(from == 0 /* BranchName.Done */ ? fromHist.rest : other, from == 0 /* BranchName.Done */ ? other : fromHist.rest);
}
let isolate = tr.annotation(isolateHistory);
if (isolate == "full" || isolate == "before")
@ -243,7 +248,7 @@ const historyField_ = state.StateField.define({
let event = HistEvent.fromTransaction(tr);
let time = tr.annotation(state.Transaction.time), userEvent = tr.annotation(state.Transaction.userEvent);
if (event)
state$1 = state$1.addChanges(event, time, userEvent, config.newGroupDelay, config.minDepth);
state$1 = state$1.addChanges(event, time, userEvent, config, tr);
else if (tr.selection)
state$1 = state$1.addSelection(tr.startState.selection, time, userEvent, config.newGroupDelay);
if (isolate == "full" || isolate == "after")
@ -301,37 +306,37 @@ function cmd(side, selection) {
Undo a single group of history events. Returns false if no group
was available.
*/
const undo = cmd(0 /* Done */, false);
const undo = cmd(0 /* BranchName.Done */, false);
/**
Redo a group of history events. Returns false if no group was
available.
*/
const redo = cmd(1 /* Undone */, false);
const redo = cmd(1 /* BranchName.Undone */, false);
/**
Undo a change or selection change.
*/
const undoSelection = cmd(0 /* Done */, true);
const undoSelection = cmd(0 /* BranchName.Done */, true);
/**
Redo a change or selection change.
*/
const redoSelection = cmd(1 /* Undone */, true);
const redoSelection = cmd(1 /* BranchName.Undone */, true);
function depth(side) {
return function (state) {
let histState = state.field(historyField_, false);
if (!histState)
return 0;
let branch = side == 0 /* Done */ ? histState.done : histState.undone;
let branch = side == 0 /* BranchName.Done */ ? histState.done : histState.undone;
return branch.length - (branch.length && !branch[0].changes ? 1 : 0);
};
}
/**
The amount of undoable change events available in a given state.
*/
const undoDepth = depth(0 /* Done */);
const undoDepth = depth(0 /* BranchName.Done */);
/**
The amount of redoable change events available in a given state.
*/
const redoDepth = depth(1 /* Undone */);
const redoDepth = depth(1 /* BranchName.Undone */);
// History events store groups of changes or effects that need to be
// undone/redone together.
class HistEvent {
@ -343,7 +348,10 @@ class HistEvent {
// changes == startSelection == undefined
changes,
// The effects associated with this event
effects, mapped,
effects,
// Accumulated mapping (from addToHistory==false) that should be
// applied to events below this one.
mapped,
// The selection before this event
startSelection,
// Stores selection changes after this event, to be used for
@ -477,19 +485,19 @@ class HistoryState {
isolate() {
return this.prevTime ? new HistoryState(this.done, this.undone) : this;
}
addChanges(event, time, userEvent, newGroupDelay, maxLen) {
addChanges(event, time, userEvent, config, tr) {
let done = this.done, lastEvent = done[done.length - 1];
if (lastEvent && lastEvent.changes && !lastEvent.changes.empty && event.changes &&
(!userEvent || joinableUserEvent.test(userEvent)) &&
((!lastEvent.selectionsAfter.length &&
time - this.prevTime < newGroupDelay &&
isAdjacent(lastEvent.changes, event.changes)) ||
time - this.prevTime < config.newGroupDelay &&
config.joinToEvent(tr, isAdjacent(lastEvent.changes, event.changes))) ||
// For compose (but not compose.start) events, always join with previous event
userEvent == "input.type.compose")) {
done = updateBranch(done, done.length - 1, maxLen, new HistEvent(event.changes.compose(lastEvent.changes), conc(event.effects, lastEvent.effects), lastEvent.mapped, lastEvent.startSelection, none));
done = updateBranch(done, done.length - 1, config.minDepth, new HistEvent(event.changes.compose(lastEvent.changes), conc(event.effects, lastEvent.effects), lastEvent.mapped, lastEvent.startSelection, none));
}
else {
done = updateBranch(done, done.length, maxLen, event);
done = updateBranch(done, done.length, config.minDepth, event);
}
return new HistoryState(done, none, time, userEvent);
}
@ -506,7 +514,7 @@ class HistoryState {
return new HistoryState(addMappingToBranch(this.done, mapping), addMappingToBranch(this.undone, mapping), this.prevTime, this.prevUserEvent);
}
pop(side, state, selection) {
let branch = side == 0 /* Done */ ? this.done : this.undone;
let branch = side == 0 /* BranchName.Done */ ? this.done : this.undone;
if (branch.length == 0)
return null;
let event = branch[branch.length - 1];
@ -514,7 +522,7 @@ class HistoryState {
return state.update({
selection: event.selectionsAfter[event.selectionsAfter.length - 1],
annotations: fromHistory.of({ side, rest: popSelection(branch) }),
userEvent: side == 0 /* Done */ ? "select.undo" : "select.redo",
userEvent: side == 0 /* BranchName.Done */ ? "select.undo" : "select.redo",
scrollIntoView: true
});
}
@ -531,7 +539,7 @@ class HistoryState {
effects: event.effects,
annotations: fromHistory.of({ side, rest }),
filter: false,
userEvent: side == 0 /* Done */ ? "undo" : "redo",
userEvent: side == 0 /* BranchName.Done */ ? "undo" : "redo",
scrollIntoView: true
});
}
@ -542,13 +550,14 @@ HistoryState.empty = new HistoryState(none, none);
Default key bindings for the undo history.
- Mod-z: [`undo`](https://codemirror.net/6/docs/ref/#commands.undo).
- Mod-y (Mod-Shift-z on macOS): [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-y (Mod-Shift-z on macOS) + Ctrl-Shift-z on Linux: [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-u: [`undoSelection`](https://codemirror.net/6/docs/ref/#commands.undoSelection).
- Alt-u (Mod-Shift-u on macOS): [`redoSelection`](https://codemirror.net/6/docs/ref/#commands.redoSelection).
*/
const historyKeymap = [
{ key: "Mod-z", run: undo, preventDefault: true },
{ key: "Mod-y", mac: "Mod-Shift-z", run: redo, preventDefault: true },
{ linux: "Ctrl-Shift-z", run: redo, preventDefault: true },
{ key: "Mod-u", run: undoSelection, preventDefault: true },
{ key: "Alt-u", mac: "Mod-Shift-u", run: redoSelection, preventDefault: true }
];
@ -762,6 +771,14 @@ end of the indentation instead of the start of the line.
*/
const cursorLineBoundaryBackward = view => moveSel(view, range => moveByLineBoundary(view, range, false));
/**
Move the selection one line wrap point to the left.
*/
const cursorLineBoundaryLeft = view => moveSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
/**
Move the selection one line wrap point to the right.
*/
const cursorLineBoundaryRight = view => moveSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
/**
Move the selection to the start of the line.
*/
const cursorLineStart = view => moveSel(view, range => state.EditorSelection.cursor(view.lineBlockAt(range.head).from, 1));
@ -799,7 +816,7 @@ const selectMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state,
function extendSel(view, how) {
let selection = updateSel(view.state.selection, range => {
let head = how(range);
return state.EditorSelection.range(range.anchor, head.head, head.goalColumn);
return state.EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined);
});
if (selection.eq(view.state.selection))
return false;
@ -896,6 +913,14 @@ Move the selection head to the previous line boundary.
*/
const selectLineBoundaryBackward = view => extendSel(view, range => moveByLineBoundary(view, range, false));
/**
Move the selection head one line boundary to the left.
*/
const selectLineBoundaryLeft = view => extendSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
/**
Move the selection head one line boundary to the right.
*/
const selectLineBoundaryRight = view => extendSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
/**
Move the selection head to the start of the line.
*/
const selectLineStart = view => extendSel(view, range => state.EditorSelection.cursor(view.lineBlockAt(range.head).from));
@ -981,26 +1006,38 @@ const simplifySelection = ({ state: state$1, dispatch }) => {
dispatch(setSel(state$1, selection));
return true;
};
function deleteBy({ state: state$1, dispatch }, by) {
if (state$1.readOnly)
function deleteBy(target, by) {
if (target.state.readOnly)
return false;
let event = "delete.selection";
let event = "delete.selection", { state: state$1 } = target;
let changes = state$1.changeByRange(range => {
let { from, to } = range;
if (from == to) {
let towards = by(from);
if (towards < from)
if (towards < from) {
event = "delete.backward";
else if (towards > from)
towards = skipAtomic(target, towards, false);
}
else if (towards > from) {
event = "delete.forward";
towards = skipAtomic(target, towards, true);
}
from = Math.min(from, towards);
to = Math.max(to, towards);
}
else {
from = skipAtomic(target, from, false);
to = skipAtomic(target, to, true);
}
return from == to ? { range } : { changes: { from, to }, range: state.EditorSelection.cursor(from) };
});
if (changes.changes.empty)
return false;
dispatch(state$1.update(changes, { scrollIntoView: true, userEvent: event }));
target.dispatch(state$1.update(changes, {
scrollIntoView: true,
userEvent: event,
effects: event == "delete.selection" ? view.EditorView.announce.of(state$1.phrase("Selection deleted")) : undefined
}));
return true;
}
function skipAtomic(target, pos, forward) {
@ -1028,7 +1065,7 @@ const deleteByChar = (target, forward) => deleteBy(target, pos => {
if (targetPos == pos && line.number != (forward ? state$1.doc.lines : 1))
targetPos += forward ? 1 : -1;
}
return skipAtomic(target, targetPos, forward);
return targetPos;
});
/**
Delete the selection, or, for cursor selections, the character
@ -1057,7 +1094,7 @@ const deleteByGroup = (target, forward) => deleteBy(target, start => {
cat = nextCat;
pos = next;
}
return skipAtomic(target, pos, forward);
return pos;
});
/**
Delete the selection or backward until the end of the next
@ -1076,7 +1113,7 @@ line, delete the line break after it.
*/
const deleteToLineEnd = view => deleteBy(view, pos => {
let lineEnd = view.lineBlockAt(pos).to;
return skipAtomic(view, pos < lineEnd ? lineEnd : Math.min(view.state.doc.length, pos + 1), true);
return pos < lineEnd ? lineEnd : Math.min(view.state.doc.length, pos + 1);
});
/**
Delete the selection, or, if it is a cursor selection, delete to
@ -1085,7 +1122,7 @@ line, delete the line break before it.
*/
const deleteToLineStart = view => deleteBy(view, pos => {
let lineStart = view.lineBlockAt(pos).from;
return skipAtomic(view, pos > lineStart ? lineStart : Math.max(0, pos - 1), false);
return pos > lineStart ? lineStart : Math.max(0, pos - 1);
});
/**
Delete all whitespace directly before a line end from the
@ -1460,11 +1497,11 @@ property changed to `mac`.)
*/
const standardKeymap = [
{ key: "ArrowLeft", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true },
{ key: "Mod-ArrowLeft", mac: "Alt-ArrowLeft", run: cursorGroupLeft, shift: selectGroupLeft },
{ mac: "Cmd-ArrowLeft", run: cursorLineBoundaryBackward, shift: selectLineBoundaryBackward },
{ key: "Mod-ArrowLeft", mac: "Alt-ArrowLeft", run: cursorGroupLeft, shift: selectGroupLeft, preventDefault: true },
{ mac: "Cmd-ArrowLeft", run: cursorLineBoundaryLeft, shift: selectLineBoundaryLeft, preventDefault: true },
{ key: "ArrowRight", run: cursorCharRight, shift: selectCharRight, preventDefault: true },
{ key: "Mod-ArrowRight", mac: "Alt-ArrowRight", run: cursorGroupRight, shift: selectGroupRight },
{ mac: "Cmd-ArrowRight", run: cursorLineBoundaryForward, shift: selectLineBoundaryForward },
{ key: "Mod-ArrowRight", mac: "Alt-ArrowRight", run: cursorGroupRight, shift: selectGroupRight, preventDefault: true },
{ mac: "Cmd-ArrowRight", run: cursorLineBoundaryRight, shift: selectLineBoundaryRight, preventDefault: true },
{ key: "ArrowUp", run: cursorLineUp, shift: selectLineUp, preventDefault: true },
{ mac: "Cmd-ArrowUp", run: cursorDocStart, shift: selectDocStart },
{ mac: "Ctrl-ArrowUp", run: cursorPageUp, shift: selectPageUp },
@ -1551,6 +1588,8 @@ exports.cursorGroupLeft = cursorGroupLeft;
exports.cursorGroupRight = cursorGroupRight;
exports.cursorLineBoundaryBackward = cursorLineBoundaryBackward;
exports.cursorLineBoundaryForward = cursorLineBoundaryForward;
exports.cursorLineBoundaryLeft = cursorLineBoundaryLeft;
exports.cursorLineBoundaryRight = cursorLineBoundaryRight;
exports.cursorLineDown = cursorLineDown;
exports.cursorLineEnd = cursorLineEnd;
exports.cursorLineStart = cursorLineStart;
@ -1606,6 +1645,8 @@ exports.selectGroupRight = selectGroupRight;
exports.selectLine = selectLine;
exports.selectLineBoundaryBackward = selectLineBoundaryBackward;
exports.selectLineBoundaryForward = selectLineBoundaryForward;
exports.selectLineBoundaryLeft = selectLineBoundaryLeft;
exports.selectLineBoundaryRight = selectLineBoundaryRight;
exports.selectLineDown = selectLineDown;
exports.selectLineEnd = selectLineEnd;
exports.selectLineStart = selectLineStart;

View file

@ -88,6 +88,13 @@ interface HistoryConfig {
apart and still be grouped together. Defaults to 500.
*/
newGroupDelay?: number;
/**
By default, when close enough together in time, changes are
joined into an existing undo event if they touch any of the
changed ranges from that event. You can pass a custom predicate
here to influence that logic.
*/
joinToEvent?: (tr: Transaction, isAdjacent: boolean) => boolean;
}
/**
Create a history extension with the given configuration.
@ -131,7 +138,7 @@ declare const redoDepth: (state: EditorState) => number;
Default key bindings for the undo history.
- Mod-z: [`undo`](https://codemirror.net/6/docs/ref/#commands.undo).
- Mod-y (Mod-Shift-z on macOS): [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-y (Mod-Shift-z on macOS) + Ctrl-Shift-z on Linux: [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-u: [`undoSelection`](https://codemirror.net/6/docs/ref/#commands.undoSelection).
- Alt-u (Mod-Shift-u on macOS): [`redoSelection`](https://codemirror.net/6/docs/ref/#commands.redoSelection).
*/
@ -216,6 +223,14 @@ end of the indentation instead of the start of the line.
*/
declare const cursorLineBoundaryBackward: Command;
/**
Move the selection one line wrap point to the left.
*/
declare const cursorLineBoundaryLeft: Command;
/**
Move the selection one line wrap point to the right.
*/
declare const cursorLineBoundaryRight: Command;
/**
Move the selection to the start of the line.
*/
declare const cursorLineStart: Command;
@ -308,6 +323,14 @@ Move the selection head to the previous line boundary.
*/
declare const selectLineBoundaryBackward: Command;
/**
Move the selection head one line boundary to the left.
*/
declare const selectLineBoundaryLeft: Command;
/**
Move the selection head one line boundary to the right.
*/
declare const selectLineBoundaryRight: Command;
/**
Move the selection head to the start of the line.
*/
declare const selectLineStart: Command;
@ -540,4 +563,4 @@ this.
*/
declare const indentWithTab: KeyBinding;
export { CommentTokens, blockComment, blockUncomment, copyLineDown, copyLineUp, cursorCharBackward, cursorCharForward, cursorCharLeft, cursorCharRight, cursorDocEnd, cursorDocStart, cursorGroupBackward, cursorGroupForward, cursorGroupLeft, cursorGroupRight, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorLineDown, cursorLineEnd, cursorLineStart, cursorLineUp, cursorMatchingBracket, cursorPageDown, cursorPageUp, cursorSubwordBackward, cursorSubwordForward, cursorSyntaxLeft, cursorSyntaxRight, defaultKeymap, deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward, deleteLine, deleteToLineEnd, deleteToLineStart, deleteTrailingWhitespace, emacsStyleKeymap, history, historyField, historyKeymap, indentLess, indentMore, indentSelection, indentWithTab, insertBlankLine, insertNewline, insertNewlineAndIndent, insertTab, invertedEffects, isolateHistory, lineComment, lineUncomment, moveLineDown, moveLineUp, redo, redoDepth, redoSelection, selectAll, selectCharBackward, selectCharForward, selectCharLeft, selectCharRight, selectDocEnd, selectDocStart, selectGroupBackward, selectGroupForward, selectGroupLeft, selectGroupRight, selectLine, selectLineBoundaryBackward, selectLineBoundaryForward, selectLineDown, selectLineEnd, selectLineStart, selectLineUp, selectMatchingBracket, selectPageDown, selectPageUp, selectParentSyntax, selectSubwordBackward, selectSubwordForward, selectSyntaxLeft, selectSyntaxRight, simplifySelection, splitLine, standardKeymap, toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment, transposeChars, undo, undoDepth, undoSelection };
export { CommentTokens, blockComment, blockUncomment, copyLineDown, copyLineUp, cursorCharBackward, cursorCharForward, cursorCharLeft, cursorCharRight, cursorDocEnd, cursorDocStart, cursorGroupBackward, cursorGroupForward, cursorGroupLeft, cursorGroupRight, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorLineBoundaryLeft, cursorLineBoundaryRight, cursorLineDown, cursorLineEnd, cursorLineStart, cursorLineUp, cursorMatchingBracket, cursorPageDown, cursorPageUp, cursorSubwordBackward, cursorSubwordForward, cursorSyntaxLeft, cursorSyntaxRight, defaultKeymap, deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward, deleteLine, deleteToLineEnd, deleteToLineStart, deleteTrailingWhitespace, emacsStyleKeymap, history, historyField, historyKeymap, indentLess, indentMore, indentSelection, indentWithTab, insertBlankLine, insertNewline, insertNewlineAndIndent, insertTab, invertedEffects, isolateHistory, lineComment, lineUncomment, moveLineDown, moveLineUp, redo, redoDepth, redoSelection, selectAll, selectCharBackward, selectCharForward, selectCharLeft, selectCharRight, selectDocEnd, selectDocStart, selectGroupBackward, selectGroupForward, selectGroupLeft, selectGroupRight, selectLine, selectLineBoundaryBackward, selectLineBoundaryForward, selectLineBoundaryLeft, selectLineBoundaryRight, selectLineDown, selectLineEnd, selectLineStart, selectLineUp, selectMatchingBracket, selectPageDown, selectPageUp, selectParentSyntax, selectSubwordBackward, selectSubwordForward, selectSyntaxLeft, selectSyntaxRight, simplifySelection, splitLine, standardKeymap, toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment, transposeChars, undo, undoDepth, undoSelection };

View file

@ -1,6 +1,6 @@
import { Annotation, Facet, combineConfig, StateField, EditorSelection, Transaction, ChangeSet, ChangeDesc, StateEffect, Text, findClusterBreak, countColumn, CharCategory } from '@codemirror/state';
import { EditorView, Direction } from '@codemirror/view';
import { IndentContext, getIndentation, indentString, indentUnit, getIndentUnit, matchBrackets, syntaxTree } from '@codemirror/language';
import { IndentContext, getIndentation, indentString, matchBrackets, syntaxTree, getIndentUnit, indentUnit } from '@codemirror/language';
import { NodeProp } from '@lezer/common';
/**
@ -28,35 +28,35 @@ The line comment syntax is taken from the
[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language
data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt).
*/
const toggleLineComment = /*@__PURE__*/command(changeLineComment, 0 /* Toggle */);
const toggleLineComment = /*@__PURE__*/command(changeLineComment, 0 /* CommentOption.Toggle */);
/**
Comment the current selection using line comments.
*/
const lineComment = /*@__PURE__*/command(changeLineComment, 1 /* Comment */);
const lineComment = /*@__PURE__*/command(changeLineComment, 1 /* CommentOption.Comment */);
/**
Uncomment the current selection using line comments.
*/
const lineUncomment = /*@__PURE__*/command(changeLineComment, 2 /* Uncomment */);
const lineUncomment = /*@__PURE__*/command(changeLineComment, 2 /* CommentOption.Uncomment */);
/**
Comment or uncomment the current selection using block comments.
The block comment syntax is taken from the
[`commentTokens`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) [language
data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt).
*/
const toggleBlockComment = /*@__PURE__*/command(changeBlockComment, 0 /* Toggle */);
const toggleBlockComment = /*@__PURE__*/command(changeBlockComment, 0 /* CommentOption.Toggle */);
/**
Comment the current selection using block comments.
*/
const blockComment = /*@__PURE__*/command(changeBlockComment, 1 /* Comment */);
const blockComment = /*@__PURE__*/command(changeBlockComment, 1 /* CommentOption.Comment */);
/**
Uncomment the current selection using block comments.
*/
const blockUncomment = /*@__PURE__*/command(changeBlockComment, 2 /* Uncomment */);
const blockUncomment = /*@__PURE__*/command(changeBlockComment, 2 /* CommentOption.Uncomment */);
/**
Comment or uncomment the lines around the current selection using
block comments.
*/
const toggleBlockCommentByLine = /*@__PURE__*/command((o, s) => changeBlockComment(o, s, selectedLineRanges(s)), 0 /* Toggle */);
const toggleBlockCommentByLine = /*@__PURE__*/command((o, s) => changeBlockComment(o, s, selectedLineRanges(s)), 0 /* CommentOption.Toggle */);
function getConfig(state, pos = state.selection.main.head) {
let data = state.languageDataAt("commentTokens", pos);
return data.length ? data[0] : {};
@ -115,14 +115,14 @@ function changeBlockComment(option, state, ranges = state.selection.ranges) {
if (!tokens.every(c => c))
return null;
let comments = ranges.map((r, i) => findBlockComment(state, tokens[i], r.from, r.to));
if (option != 2 /* Uncomment */ && !comments.every(c => c)) {
if (option != 2 /* CommentOption.Uncomment */ && !comments.every(c => c)) {
return { changes: state.changes(ranges.map((range, i) => {
if (comments[i])
return [];
return [{ from: range.from, insert: tokens[i].open + " " }, { from: range.to, insert: " " + tokens[i].close }];
})) };
}
else if (option != 1 /* Comment */ && comments.some(c => c)) {
else if (option != 1 /* CommentOption.Comment */ && comments.some(c => c)) {
let changes = [];
for (let i = 0, comment; i < comments.length; i++)
if (comment = comments[i]) {
@ -162,7 +162,7 @@ function changeLineComment(option, state, ranges = state.selection.ranges) {
if (lines.length == startI + 1)
lines[startI].single = true;
}
if (option != 2 /* Uncomment */ && lines.some(l => l.comment < 0 && (!l.empty || l.single))) {
if (option != 2 /* CommentOption.Uncomment */ && lines.some(l => l.comment < 0 && (!l.empty || l.single))) {
let changes = [];
for (let { line, token, indent, empty, single } of lines)
if (single || !empty)
@ -170,7 +170,7 @@ function changeLineComment(option, state, ranges = state.selection.ranges) {
let changeSet = state.changes(changes);
return { changes: changeSet, selection: state.selection.map(changeSet, 1) };
}
else if (option != 1 /* Comment */ && lines.some(l => l.comment >= 0)) {
else if (option != 1 /* CommentOption.Comment */ && lines.some(l => l.comment >= 0)) {
let changes = [];
for (let { line, comment, token } of lines)
if (comment >= 0) {
@ -205,8 +205,13 @@ const historyConfig = /*@__PURE__*/Facet.define({
combine(configs) {
return combineConfig(configs, {
minDepth: 100,
newGroupDelay: 500
}, { minDepth: Math.max, newGroupDelay: Math.min });
newGroupDelay: 500,
joinToEvent: (_t, isAdjacent) => isAdjacent,
}, {
minDepth: Math.max,
newGroupDelay: Math.min,
joinToEvent: (a, b) => (tr, adj) => a(tr, adj) || b(tr, adj)
});
}
});
function changeEnd(changes) {
@ -224,12 +229,12 @@ const historyField_ = /*@__PURE__*/StateField.define({
if (fromHist) {
let selection = tr.docChanged ? EditorSelection.single(changeEnd(tr.changes)) : undefined;
let item = HistEvent.fromTransaction(tr, selection), from = fromHist.side;
let other = from == 0 /* Done */ ? state.undone : state.done;
let other = from == 0 /* BranchName.Done */ ? state.undone : state.done;
if (item)
other = updateBranch(other, other.length, config.minDepth, item);
else
other = addSelection(other, tr.startState.selection);
return new HistoryState(from == 0 /* Done */ ? fromHist.rest : other, from == 0 /* Done */ ? other : fromHist.rest);
return new HistoryState(from == 0 /* BranchName.Done */ ? fromHist.rest : other, from == 0 /* BranchName.Done */ ? other : fromHist.rest);
}
let isolate = tr.annotation(isolateHistory);
if (isolate == "full" || isolate == "before")
@ -239,7 +244,7 @@ const historyField_ = /*@__PURE__*/StateField.define({
let event = HistEvent.fromTransaction(tr);
let time = tr.annotation(Transaction.time), userEvent = tr.annotation(Transaction.userEvent);
if (event)
state = state.addChanges(event, time, userEvent, config.newGroupDelay, config.minDepth);
state = state.addChanges(event, time, userEvent, config, tr);
else if (tr.selection)
state = state.addSelection(tr.startState.selection, time, userEvent, config.newGroupDelay);
if (isolate == "full" || isolate == "after")
@ -297,37 +302,37 @@ function cmd(side, selection) {
Undo a single group of history events. Returns false if no group
was available.
*/
const undo = /*@__PURE__*/cmd(0 /* Done */, false);
const undo = /*@__PURE__*/cmd(0 /* BranchName.Done */, false);
/**
Redo a group of history events. Returns false if no group was
available.
*/
const redo = /*@__PURE__*/cmd(1 /* Undone */, false);
const redo = /*@__PURE__*/cmd(1 /* BranchName.Undone */, false);
/**
Undo a change or selection change.
*/
const undoSelection = /*@__PURE__*/cmd(0 /* Done */, true);
const undoSelection = /*@__PURE__*/cmd(0 /* BranchName.Done */, true);
/**
Redo a change or selection change.
*/
const redoSelection = /*@__PURE__*/cmd(1 /* Undone */, true);
const redoSelection = /*@__PURE__*/cmd(1 /* BranchName.Undone */, true);
function depth(side) {
return function (state) {
let histState = state.field(historyField_, false);
if (!histState)
return 0;
let branch = side == 0 /* Done */ ? histState.done : histState.undone;
let branch = side == 0 /* BranchName.Done */ ? histState.done : histState.undone;
return branch.length - (branch.length && !branch[0].changes ? 1 : 0);
};
}
/**
The amount of undoable change events available in a given state.
*/
const undoDepth = /*@__PURE__*/depth(0 /* Done */);
const undoDepth = /*@__PURE__*/depth(0 /* BranchName.Done */);
/**
The amount of redoable change events available in a given state.
*/
const redoDepth = /*@__PURE__*/depth(1 /* Undone */);
const redoDepth = /*@__PURE__*/depth(1 /* BranchName.Undone */);
// History events store groups of changes or effects that need to be
// undone/redone together.
class HistEvent {
@ -339,7 +344,10 @@ class HistEvent {
// changes == startSelection == undefined
changes,
// The effects associated with this event
effects, mapped,
effects,
// Accumulated mapping (from addToHistory==false) that should be
// applied to events below this one.
mapped,
// The selection before this event
startSelection,
// Stores selection changes after this event, to be used for
@ -473,19 +481,19 @@ class HistoryState {
isolate() {
return this.prevTime ? new HistoryState(this.done, this.undone) : this;
}
addChanges(event, time, userEvent, newGroupDelay, maxLen) {
addChanges(event, time, userEvent, config, tr) {
let done = this.done, lastEvent = done[done.length - 1];
if (lastEvent && lastEvent.changes && !lastEvent.changes.empty && event.changes &&
(!userEvent || joinableUserEvent.test(userEvent)) &&
((!lastEvent.selectionsAfter.length &&
time - this.prevTime < newGroupDelay &&
isAdjacent(lastEvent.changes, event.changes)) ||
time - this.prevTime < config.newGroupDelay &&
config.joinToEvent(tr, isAdjacent(lastEvent.changes, event.changes))) ||
// For compose (but not compose.start) events, always join with previous event
userEvent == "input.type.compose")) {
done = updateBranch(done, done.length - 1, maxLen, new HistEvent(event.changes.compose(lastEvent.changes), conc(event.effects, lastEvent.effects), lastEvent.mapped, lastEvent.startSelection, none));
done = updateBranch(done, done.length - 1, config.minDepth, new HistEvent(event.changes.compose(lastEvent.changes), conc(event.effects, lastEvent.effects), lastEvent.mapped, lastEvent.startSelection, none));
}
else {
done = updateBranch(done, done.length, maxLen, event);
done = updateBranch(done, done.length, config.minDepth, event);
}
return new HistoryState(done, none, time, userEvent);
}
@ -502,7 +510,7 @@ class HistoryState {
return new HistoryState(addMappingToBranch(this.done, mapping), addMappingToBranch(this.undone, mapping), this.prevTime, this.prevUserEvent);
}
pop(side, state, selection) {
let branch = side == 0 /* Done */ ? this.done : this.undone;
let branch = side == 0 /* BranchName.Done */ ? this.done : this.undone;
if (branch.length == 0)
return null;
let event = branch[branch.length - 1];
@ -510,7 +518,7 @@ class HistoryState {
return state.update({
selection: event.selectionsAfter[event.selectionsAfter.length - 1],
annotations: fromHistory.of({ side, rest: popSelection(branch) }),
userEvent: side == 0 /* Done */ ? "select.undo" : "select.redo",
userEvent: side == 0 /* BranchName.Done */ ? "select.undo" : "select.redo",
scrollIntoView: true
});
}
@ -527,7 +535,7 @@ class HistoryState {
effects: event.effects,
annotations: fromHistory.of({ side, rest }),
filter: false,
userEvent: side == 0 /* Done */ ? "undo" : "redo",
userEvent: side == 0 /* BranchName.Done */ ? "undo" : "redo",
scrollIntoView: true
});
}
@ -538,13 +546,14 @@ HistoryState.empty = /*@__PURE__*/new HistoryState(none, none);
Default key bindings for the undo history.
- Mod-z: [`undo`](https://codemirror.net/6/docs/ref/#commands.undo).
- Mod-y (Mod-Shift-z on macOS): [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-y (Mod-Shift-z on macOS) + Ctrl-Shift-z on Linux: [`redo`](https://codemirror.net/6/docs/ref/#commands.redo).
- Mod-u: [`undoSelection`](https://codemirror.net/6/docs/ref/#commands.undoSelection).
- Alt-u (Mod-Shift-u on macOS): [`redoSelection`](https://codemirror.net/6/docs/ref/#commands.redoSelection).
*/
const historyKeymap = [
{ key: "Mod-z", run: undo, preventDefault: true },
{ key: "Mod-y", mac: "Mod-Shift-z", run: redo, preventDefault: true },
{ linux: "Ctrl-Shift-z", run: redo, preventDefault: true },
{ key: "Mod-u", run: undoSelection, preventDefault: true },
{ key: "Alt-u", mac: "Mod-Shift-u", run: redoSelection, preventDefault: true }
];
@ -758,6 +767,14 @@ end of the indentation instead of the start of the line.
*/
const cursorLineBoundaryBackward = view => moveSel(view, range => moveByLineBoundary(view, range, false));
/**
Move the selection one line wrap point to the left.
*/
const cursorLineBoundaryLeft = view => moveSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
/**
Move the selection one line wrap point to the right.
*/
const cursorLineBoundaryRight = view => moveSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
/**
Move the selection to the start of the line.
*/
const cursorLineStart = view => moveSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from, 1));
@ -795,7 +812,7 @@ const selectMatchingBracket = ({ state, dispatch }) => toMatchingBracket(state,
function extendSel(view, how) {
let selection = updateSel(view.state.selection, range => {
let head = how(range);
return EditorSelection.range(range.anchor, head.head, head.goalColumn);
return EditorSelection.range(range.anchor, head.head, head.goalColumn, head.bidiLevel || undefined);
});
if (selection.eq(view.state.selection))
return false;
@ -892,6 +909,14 @@ Move the selection head to the previous line boundary.
*/
const selectLineBoundaryBackward = view => extendSel(view, range => moveByLineBoundary(view, range, false));
/**
Move the selection head one line boundary to the left.
*/
const selectLineBoundaryLeft = view => extendSel(view, range => moveByLineBoundary(view, range, !ltrAtCursor(view)));
/**
Move the selection head one line boundary to the right.
*/
const selectLineBoundaryRight = view => extendSel(view, range => moveByLineBoundary(view, range, ltrAtCursor(view)));
/**
Move the selection head to the start of the line.
*/
const selectLineStart = view => extendSel(view, range => EditorSelection.cursor(view.lineBlockAt(range.head).from));
@ -977,26 +1002,38 @@ const simplifySelection = ({ state, dispatch }) => {
dispatch(setSel(state, selection));
return true;
};
function deleteBy({ state, dispatch }, by) {
if (state.readOnly)
function deleteBy(target, by) {
if (target.state.readOnly)
return false;
let event = "delete.selection";
let event = "delete.selection", { state } = target;
let changes = state.changeByRange(range => {
let { from, to } = range;
if (from == to) {
let towards = by(from);
if (towards < from)
if (towards < from) {
event = "delete.backward";
else if (towards > from)
towards = skipAtomic(target, towards, false);
}
else if (towards > from) {
event = "delete.forward";
towards = skipAtomic(target, towards, true);
}
from = Math.min(from, towards);
to = Math.max(to, towards);
}
else {
from = skipAtomic(target, from, false);
to = skipAtomic(target, to, true);
}
return from == to ? { range } : { changes: { from, to }, range: EditorSelection.cursor(from) };
});
if (changes.changes.empty)
return false;
dispatch(state.update(changes, { scrollIntoView: true, userEvent: event }));
target.dispatch(state.update(changes, {
scrollIntoView: true,
userEvent: event,
effects: event == "delete.selection" ? EditorView.announce.of(state.phrase("Selection deleted")) : undefined
}));
return true;
}
function skipAtomic(target, pos, forward) {
@ -1024,7 +1061,7 @@ const deleteByChar = (target, forward) => deleteBy(target, pos => {
if (targetPos == pos && line.number != (forward ? state.doc.lines : 1))
targetPos += forward ? 1 : -1;
}
return skipAtomic(target, targetPos, forward);
return targetPos;
});
/**
Delete the selection, or, for cursor selections, the character
@ -1053,7 +1090,7 @@ const deleteByGroup = (target, forward) => deleteBy(target, start => {
cat = nextCat;
pos = next;
}
return skipAtomic(target, pos, forward);
return pos;
});
/**
Delete the selection or backward until the end of the next
@ -1072,7 +1109,7 @@ line, delete the line break after it.
*/
const deleteToLineEnd = view => deleteBy(view, pos => {
let lineEnd = view.lineBlockAt(pos).to;
return skipAtomic(view, pos < lineEnd ? lineEnd : Math.min(view.state.doc.length, pos + 1), true);
return pos < lineEnd ? lineEnd : Math.min(view.state.doc.length, pos + 1);
});
/**
Delete the selection, or, if it is a cursor selection, delete to
@ -1081,7 +1118,7 @@ line, delete the line break before it.
*/
const deleteToLineStart = view => deleteBy(view, pos => {
let lineStart = view.lineBlockAt(pos).from;
return skipAtomic(view, pos > lineStart ? lineStart : Math.max(0, pos - 1), false);
return pos > lineStart ? lineStart : Math.max(0, pos - 1);
});
/**
Delete all whitespace directly before a line end from the
@ -1456,11 +1493,11 @@ property changed to `mac`.)
*/
const standardKeymap = /*@__PURE__*/[
{ key: "ArrowLeft", run: cursorCharLeft, shift: selectCharLeft, preventDefault: true },
{ key: "Mod-ArrowLeft", mac: "Alt-ArrowLeft", run: cursorGroupLeft, shift: selectGroupLeft },
{ mac: "Cmd-ArrowLeft", run: cursorLineBoundaryBackward, shift: selectLineBoundaryBackward },
{ key: "Mod-ArrowLeft", mac: "Alt-ArrowLeft", run: cursorGroupLeft, shift: selectGroupLeft, preventDefault: true },
{ mac: "Cmd-ArrowLeft", run: cursorLineBoundaryLeft, shift: selectLineBoundaryLeft, preventDefault: true },
{ key: "ArrowRight", run: cursorCharRight, shift: selectCharRight, preventDefault: true },
{ key: "Mod-ArrowRight", mac: "Alt-ArrowRight", run: cursorGroupRight, shift: selectGroupRight },
{ mac: "Cmd-ArrowRight", run: cursorLineBoundaryForward, shift: selectLineBoundaryForward },
{ key: "Mod-ArrowRight", mac: "Alt-ArrowRight", run: cursorGroupRight, shift: selectGroupRight, preventDefault: true },
{ mac: "Cmd-ArrowRight", run: cursorLineBoundaryRight, shift: selectLineBoundaryRight, preventDefault: true },
{ key: "ArrowUp", run: cursorLineUp, shift: selectLineUp, preventDefault: true },
{ mac: "Cmd-ArrowUp", run: cursorDocStart, shift: selectDocStart },
{ mac: "Ctrl-ArrowUp", run: cursorPageUp, shift: selectPageUp },
@ -1531,4 +1568,4 @@ this.
*/
const indentWithTab = { key: "Tab", run: indentMore, shift: indentLess };
export { blockComment, blockUncomment, copyLineDown, copyLineUp, cursorCharBackward, cursorCharForward, cursorCharLeft, cursorCharRight, cursorDocEnd, cursorDocStart, cursorGroupBackward, cursorGroupForward, cursorGroupLeft, cursorGroupRight, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorLineDown, cursorLineEnd, cursorLineStart, cursorLineUp, cursorMatchingBracket, cursorPageDown, cursorPageUp, cursorSubwordBackward, cursorSubwordForward, cursorSyntaxLeft, cursorSyntaxRight, defaultKeymap, deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward, deleteLine, deleteToLineEnd, deleteToLineStart, deleteTrailingWhitespace, emacsStyleKeymap, history, historyField, historyKeymap, indentLess, indentMore, indentSelection, indentWithTab, insertBlankLine, insertNewline, insertNewlineAndIndent, insertTab, invertedEffects, isolateHistory, lineComment, lineUncomment, moveLineDown, moveLineUp, redo, redoDepth, redoSelection, selectAll, selectCharBackward, selectCharForward, selectCharLeft, selectCharRight, selectDocEnd, selectDocStart, selectGroupBackward, selectGroupForward, selectGroupLeft, selectGroupRight, selectLine, selectLineBoundaryBackward, selectLineBoundaryForward, selectLineDown, selectLineEnd, selectLineStart, selectLineUp, selectMatchingBracket, selectPageDown, selectPageUp, selectParentSyntax, selectSubwordBackward, selectSubwordForward, selectSyntaxLeft, selectSyntaxRight, simplifySelection, splitLine, standardKeymap, toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment, transposeChars, undo, undoDepth, undoSelection };
export { blockComment, blockUncomment, copyLineDown, copyLineUp, cursorCharBackward, cursorCharForward, cursorCharLeft, cursorCharRight, cursorDocEnd, cursorDocStart, cursorGroupBackward, cursorGroupForward, cursorGroupLeft, cursorGroupRight, cursorLineBoundaryBackward, cursorLineBoundaryForward, cursorLineBoundaryLeft, cursorLineBoundaryRight, cursorLineDown, cursorLineEnd, cursorLineStart, cursorLineUp, cursorMatchingBracket, cursorPageDown, cursorPageUp, cursorSubwordBackward, cursorSubwordForward, cursorSyntaxLeft, cursorSyntaxRight, defaultKeymap, deleteCharBackward, deleteCharForward, deleteGroupBackward, deleteGroupForward, deleteLine, deleteToLineEnd, deleteToLineStart, deleteTrailingWhitespace, emacsStyleKeymap, history, historyField, historyKeymap, indentLess, indentMore, indentSelection, indentWithTab, insertBlankLine, insertNewline, insertNewlineAndIndent, insertTab, invertedEffects, isolateHistory, lineComment, lineUncomment, moveLineDown, moveLineUp, redo, redoDepth, redoSelection, selectAll, selectCharBackward, selectCharForward, selectCharLeft, selectCharRight, selectDocEnd, selectDocStart, selectGroupBackward, selectGroupForward, selectGroupLeft, selectGroupRight, selectLine, selectLineBoundaryBackward, selectLineBoundaryForward, selectLineBoundaryLeft, selectLineBoundaryRight, selectLineDown, selectLineEnd, selectLineStart, selectLineUp, selectMatchingBracket, selectPageDown, selectPageUp, selectParentSyntax, selectSubwordBackward, selectSubwordForward, selectSyntaxLeft, selectSyntaxRight, simplifySelection, splitLine, standardKeymap, toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment, transposeChars, undo, undoDepth, undoSelection };

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/commands",
"version": "6.0.0",
"version": "6.2.0",
"description": "Collection of editing commands for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",
@ -27,7 +27,7 @@
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
},

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,67 @@
## 6.5.0 (2023-02-07)
### Bug fixes
Make indentation for stream languages more reliable by having `StringStream.indentation` return overridden indentations from the indent context.
### New features
The `toggleFold` command folds or unfolds depending on whether there's an existing folded range on the current line.
`indentUnit` now accepts any (repeated) whitespace character, not just spaces and tabs.
## 6.4.0 (2023-01-12)
### New features
The `bracketMatchingHandle` node prop can now be used to limit bracket matching behavior for larger nodes to a single subnode (for example the tag name of an HTML tag).
## 6.3.2 (2022-12-16)
### Bug fixes
Fix a bug that caused `ensureSyntaxTree` to return incomplete trees when using a viewport-aware parser like `StreamLanguage`.
## 6.3.1 (2022-11-14)
### Bug fixes
Make syntax-based folding include syntax nodes that start right at the end of a line as potential fold targets.
Fix the `indentService` protocol to allow a distinction between declining to handle the indentation and returning null to indicate the line has no definite indentation.
## 6.3.0 (2022-10-24)
### New features
`HighlightStyle` objects now have a `specs` property holding the tag styles that were used to define them.
`Language` objects now have a `name` field holding the language name.
## 6.2.1 (2022-07-21)
### Bug fixes
Fix a bug where `bracketMatching` would incorrectly match nested brackets in syntax trees that put multiple pairs of brackets in the same parent node.
Fix a bug that could cause `indentRange` to loop infinitely.
## 6.2.0 (2022-06-30)
### Bug fixes
Fix a bug that prevented bracket matching to recognize plain brackets inside a language parsed as an overlay.
### New features
The `indentRange` function provides an easy way to programatically auto-indent a range of the document.
## 6.1.0 (2022-06-20)
### New features
The `foldState` field is now public, and can be used to serialize and deserialize the fold state.
## 6.0.0 (2022-06-08)
### New features

View file

@ -1,6 +1,6 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,13 +1,13 @@
# @codemirror/language [![NPM version](https://img.shields.io/npm/v/@codemirror/language.svg)](https://www.npmjs.org/package/@codemirror/language)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#language) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/language/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#language) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/language/blob/main/CHANGELOG.md) ]
This package implements the language support infrastructure for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/language/tree/main/LICENSE).

View file

@ -49,8 +49,13 @@ class Language {
The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) facet
used for this language.
*/
data, parser, extraExtensions = []) {
data, parser, extraExtensions = [],
/**
A language name.
*/
name = "") {
this.data = data;
this.name = name;
// Kludge to define EditorState.tree as a debugging helper,
// without the EditorState package actually knowing about
// languages and lezer trees.
@ -138,8 +143,8 @@ A subclass of [`Language`](https://codemirror.net/6/docs/ref/#language.Language)
parsers.
*/
class LRLanguage extends Language {
constructor(data, parser) {
super(data, parser);
constructor(data, parser, name) {
super(data, parser, [], name);
this.parser = parser;
}
/**
@ -149,14 +154,14 @@ class LRLanguage extends Language {
let data = defineLanguageFacet(spec.languageData);
return new LRLanguage(data, spec.parser.configure({
props: [languageDataProp.add(type => type.isTop ? data : undefined)]
}));
}), spec.name);
}
/**
Create a new instance of this language with a reconfigured
version of its parser.
version of its parser and optionally a new name.
*/
configure(options) {
return new LRLanguage(this.data, this.parser.configure(options));
configure(options, name) {
return new LRLanguage(this.data, this.parser.configure(options), name || this.name);
}
get allowsNesting() { return this.parser.hasWrappers(); }
}
@ -178,7 +183,13 @@ up to that point if the tree isn't already available.
function ensureSyntaxTree(state, upto, timeout = 50) {
var _a;
let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
return !parse ? null : parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
if (!parse)
return null;
let oldVieport = parse.viewport;
parse.updateViewport({ from: 0, to: upto });
let result = parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
parse.updateViewport(oldVieport);
return result;
}
/**
Queries whether there is a full syntax tree available up to the
@ -218,13 +229,13 @@ function syntaxParserRunning(view) {
}
// Lezer-style Input object for a Text document.
class DocInput {
constructor(doc, length = doc.length) {
constructor(doc) {
this.doc = doc;
this.length = length;
this.cursorPos = 0;
this.string = "";
this.cursor = doc.iter();
}
get length() { return this.doc.length; }
syncTo(pos) {
this.string = this.cursor.next(pos - this.cursorPos).value;
this.cursorPos = pos + this.string.length;
@ -503,14 +514,14 @@ class LanguageState {
// state updates with parse work beyond the viewport.
let upto = this.context.treeLen == tr.startState.doc.length ? undefined
: Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
if (!newCx.work(20 /* Apply */, upto))
if (!newCx.work(20 /* Work.Apply */, upto))
newCx.takeTree();
return new LanguageState(newCx);
}
static init(state) {
let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
let vpTo = Math.min(3000 /* Work.InitViewport */, state.doc.length);
let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
if (!parseState.work(20 /* Apply */, vpTo))
if (!parseState.work(20 /* Work.Apply */, vpTo))
parseState.takeTree();
return new LanguageState(parseState);
}
@ -527,14 +538,14 @@ Language.state = state.StateField.define({
}
});
let requestIdle = (callback) => {
let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
let timeout = setTimeout(() => callback(), 500 /* Work.MaxPause */);
return () => clearTimeout(timeout);
};
if (typeof requestIdleCallback != "undefined")
requestIdle = (callback) => {
let idle = -1, timeout = setTimeout(() => {
idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
}, 100 /* MinPause */);
idle = requestIdleCallback(callback, { timeout: 500 /* Work.MaxPause */ - 100 /* Work.MinPause */ });
}, 100 /* Work.MinPause */);
return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
};
const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending)
@ -557,7 +568,7 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
this.scheduleWork();
if (update.docChanged) {
if (this.view.hasFocus)
this.chunkBudget += 50 /* ChangeBonus */;
this.chunkBudget += 50 /* Work.ChangeBonus */;
this.scheduleWork();
}
this.checkAsyncSchedule(cx);
@ -573,19 +584,19 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
this.working = null;
let now = Date.now();
if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
this.chunkEnd = now + 30000 /* ChunkTime */;
this.chunkBudget = 3000 /* ChunkBudget */;
this.chunkEnd = now + 30000 /* Work.ChunkTime */;
this.chunkBudget = 3000 /* Work.ChunkBudget */;
}
if (this.chunkBudget <= 0)
return; // No more budget
let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* Work.MaxParseAhead */))
return;
let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Slice */, deadline && !isInputPending ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Work.Slice */, deadline && !isInputPending ? Math.max(25 /* Work.MinSlice */, deadline.timeRemaining() - 5) : 1e9);
let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
let done = field.context.work(() => {
return isInputPending && isInputPending() || Date.now() > endTime;
}, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
}, vpTo + (viewportFirst ? 0 : 100000 /* Work.MaxParseAhead */));
this.chunkBudget -= Date.now() - now;
if (done || this.chunkBudget <= 0) {
field.context.takeTree();
@ -616,11 +627,21 @@ const parseWorker = view.ViewPlugin.fromClass(class ParseWorker {
eventHandlers: { focus() { this.scheduleWork(); } }
});
/**
The facet used to associate a language with an editor state.
The facet used to associate a language with an editor state. Used
by `Language` object's `extension` property (so you don't need to
manually wrap your languages in this). Can be used to access the
current language on a state.
*/
const language = state.Facet.define({
combine(languages) { return languages.length ? languages[0] : null; },
enables: [Language.state, parseWorker]
enables: language => [
Language.state,
parseWorker,
view.EditorView.contentAttributes.compute([language], state => {
let lang = state.facet(language);
return lang && lang.name ? { "data-language": lang.name } : {};
})
]
});
/**
This class bundles a [language](https://codemirror.net/6/docs/ref/#language.Language) with an
@ -750,22 +771,27 @@ class LanguageDescription {
/**
Facet that defines a way to provide a function that computes the
appropriate indentation depth at the start of a given line, or
`null` to indicate no appropriate indentation could be determined.
appropriate indentation depth, as a column number (see
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)), at the start of a given
line. A return value of `null` indicates no indentation can be
determined, and the line should inherit the indentation of the one
above it. A return value of `undefined` defers to the next indent
service.
*/
const indentService = state.Facet.define();
/**
Facet for overriding the unit by which indentation happens.
Should be a string consisting either entirely of spaces or
entirely of tabs. When not set, this defaults to 2 spaces.
Facet for overriding the unit by which indentation happens. Should
be a string consisting either entirely of the same whitespace
character. When not set, this defaults to 2 spaces.
*/
const indentUnit = state.Facet.define({
combine: values => {
if (!values.length)
return " ";
if (!/^(?: +|\t+)$/.test(values[0]))
let unit = values[0];
if (!unit || /\S/.test(unit) || Array.from(unit).some(e => e != unit[0]))
throw new Error("Invalid indent unit: " + JSON.stringify(values[0]));
return values[0];
return unit;
}
});
/**
@ -785,36 +811,64 @@ Will use tabs for as much of the columns as possible when the
tabs.
*/
function indentString(state, cols) {
let result = "", ts = state.tabSize;
if (state.facet(indentUnit).charCodeAt(0) == 9)
let result = "", ts = state.tabSize, ch = state.facet(indentUnit)[0];
if (ch == "\t") {
while (cols >= ts) {
result += "\t";
cols -= ts;
}
ch = " ";
}
for (let i = 0; i < cols; i++)
result += " ";
result += ch;
return result;
}
/**
Get the indentation at the given position. Will first consult any
[indent services](https://codemirror.net/6/docs/ref/#language.indentService) that are registered,
and if none of those return an indentation, this will check the
syntax tree for the [indent node prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp)
and use that if found. Returns a number when an indentation could
be determined, and null otherwise.
Get the indentation, as a column number, at the given position.
Will first consult any [indent services](https://codemirror.net/6/docs/ref/#language.indentService)
that are registered, and if none of those return an indentation,
this will check the syntax tree for the [indent node
prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp) and use that if found. Returns a
number when an indentation could be determined, and null
otherwise.
*/
function getIndentation(context, pos) {
if (context instanceof state.EditorState)
context = new IndentContext(context);
for (let service of context.state.facet(indentService)) {
let result = service(context, pos);
if (result != null)
if (result !== undefined)
return result;
}
let tree = syntaxTree(context.state);
return tree ? syntaxIndentation(context, tree, pos) : null;
}
/**
Create a change set that auto-indents all lines touched by the
given document range.
*/
function indentRange(state, from, to) {
let updated = Object.create(null);
let context = new IndentContext(state, { overrideIndentation: start => { var _a; return (_a = updated[start]) !== null && _a !== void 0 ? _a : -1; } });
let changes = [];
for (let pos = from; pos <= to;) {
let line = state.doc.lineAt(pos);
pos = line.to + 1;
let indent = getIndentation(context, line.from);
if (indent == null)
continue;
if (!/\S/.test(line.text))
indent = 0;
let cur = /^\s*/.exec(line.text)[0];
let norm = indentString(state, indent);
if (cur != norm) {
updated[line.from] = indent;
changes.push({ from: line.from, to: line.from + cur.length, insert: norm });
}
}
return state.changes(changes);
}
/**
Indentation contexts are used when calling [indentation
services](https://codemirror.net/6/docs/ref/#language.indentService). They provide helper utilities
useful in indentation logic, and can selectively override the
@ -911,8 +965,9 @@ class IndentContext {
/**
A syntax tree node prop used to associate indentation strategies
with node types. Such a strategy is a function from an indentation
context to a column number or null, where null indicates that no
definitive indentation can be determined.
context to a column number (see also
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)) or null, where null
indicates that no definitive indentation can be determined.
*/
const indentNodeProp = new common.NodeProp();
// Compute the indentation for a given position from the syntax tree.
@ -1143,7 +1198,7 @@ function syntaxFolding(state, start, end) {
let tree = syntaxTree(state);
if (tree.length < end)
return null;
let inner = tree.resolveInner(end);
let inner = tree.resolveInner(end, 1);
let found = null;
for (let cur = inner; cur; cur = cur.parent) {
if (cur.to <= end || cur.from > end)
@ -1204,6 +1259,13 @@ function selectedLines(view) {
}
return lines;
}
/**
The state field that stores the folded ranges (as a [decoration
set](https://codemirror.net/6/docs/ref/#view.DecorationSet)). Can be passed to
[`EditorState.toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) and
[`fromJSON`](https://codemirror.net/6/docs/ref/#state.EditorState^fromJSON) to serialize the fold
state.
*/
const foldState = state.StateField.define({
create() {
return view.Decoration.none;
@ -1231,7 +1293,24 @@ const foldState = state.StateField.define({
}
return folded;
},
provide: f => view.EditorView.decorations.from(f)
provide: f => view.EditorView.decorations.from(f),
toJSON(folded, state) {
let ranges = [];
folded.between(0, state.doc.length, (from, to) => { ranges.push(from, to); });
return ranges;
},
fromJSON(value) {
if (!Array.isArray(value) || value.length % 2)
throw new RangeError("Invalid JSON for fold state");
let ranges = [];
for (let i = 0; i < value.length;) {
let from = value[i++], to = value[i++];
if (typeof from != "number" || typeof to != "number")
throw new RangeError("Invalid JSON for fold state");
ranges.push(foldWidget.range(from, to));
}
return view.Decoration.set(ranges, true);
}
});
/**
Get a [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) containing the folded ranges
@ -1324,6 +1403,41 @@ const unfoldAll = view => {
view.dispatch({ effects });
return true;
};
// Find the foldable region containing the given line, if one exists
function foldableContainer(view, lineBlock) {
// Look backwards through line blocks until we find a foldable region that
// intersects with the line
for (let line = lineBlock;;) {
let foldableRegion = foldable(view.state, line.from, line.to);
if (foldableRegion && foldableRegion.to > lineBlock.from)
return foldableRegion;
if (!line.from)
return null;
line = view.lineBlockAt(line.from - 1);
}
}
/**
Toggle folding at cursors. Unfolds if there is an existing fold
starting in that line, tries to find a foldable range around it
otherwise.
*/
const toggleFold = (view) => {
let effects = [];
for (let line of selectedLines(view)) {
let folded = findFold(view.state, line.from, line.to);
if (folded) {
effects.push(unfoldEffect.of(folded), announceFold(view, folded, false));
}
else {
let foldRange = foldableContainer(view, line);
if (foldRange)
effects.push(foldEffect.of(foldRange), announceFold(view, foldRange));
}
}
if (effects.length > 0)
view.dispatch({ effects: maybeEnable(view.state, effects) });
return !!effects.length;
};
/**
Default fold-related key bindings.
@ -1479,7 +1593,12 @@ A highlight style associates CSS styles with higlighting
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
*/
class HighlightStyle {
constructor(spec, options) {
constructor(
/**
The tag styles used to create this highlight style.
*/
specs, options) {
this.specs = specs;
let modSpec;
function def(spec) {
let cls = styleMod.StyleModule.newName();
@ -1490,7 +1609,7 @@ class HighlightStyle {
const scopeOpt = options.scope;
this.scope = scopeOpt instanceof Language ? (type) => type.prop(languageDataProp) == scopeOpt.data
: scopeOpt ? (type) => type == scopeOpt : undefined;
this.style = highlight.tagHighlighter(spec.map(style => ({
this.style = highlight.tagHighlighter(specs.map(style => ({
tag: style.tag,
class: style.class || def(Object.assign({}, style, { tag: null }))
})), {
@ -1607,7 +1726,7 @@ A default highlight style (works well with light themes).
*/
const defaultHighlightStyle = HighlightStyle.define([
{ tag: highlight.tags.meta,
color: "#7a757a" },
color: "#404740" },
{ tag: highlight.tags.link,
textDecoration: "underline" },
{ tag: highlight.tags.heading,
@ -1706,6 +1825,15 @@ highlighting style is used to indicate this.
function bracketMatching(config = {}) {
return [bracketMatchingConfig.of(config), bracketMatchingUnique];
}
/**
When larger syntax nodes, such as HTML tags, are marked as
opening/closing, it can be a bit messy to treat the whole node as
a matchable bracket. This node prop allows you to define, for such
a node, a handlethe part of the node that is highlighted, and
that the cursor must be on to activate highlighting in the first
place.
*/
const bracketMatchingHandle = new common.NodeProp();
function matchingNodes(node, dir, brackets) {
let byProp = node.prop(dir < 0 ? common.NodeProp.openedBy : common.NodeProp.closedBy);
if (byProp)
@ -1717,6 +1845,10 @@ function matchingNodes(node, dir, brackets) {
}
return null;
}
function findHandle(node) {
let hasHandle = node.type.prop(bracketMatchingHandle);
return hasHandle ? hasHandle(node.node) : node;
}
/**
Find the matching bracket for the token at `pos`, scanning
direction `dir`. Only the `brackets` and `maxScanDistance`
@ -1728,31 +1860,37 @@ function matchBrackets(state, pos, dir, config = {}) {
let tree = syntaxTree(state), node = tree.resolveInner(pos, dir);
for (let cur = node; cur; cur = cur.parent) {
let matches = matchingNodes(cur.type, dir, brackets);
if (matches && cur.from < cur.to)
return matchMarkedBrackets(state, pos, dir, cur, matches, brackets);
if (matches && cur.from < cur.to) {
let handle = findHandle(cur);
if (handle && (dir > 0 ? pos >= handle.from && pos < handle.to : pos > handle.from && pos <= handle.to))
return matchMarkedBrackets(state, pos, dir, cur, handle, matches, brackets);
}
}
return matchPlainBrackets(state, pos, dir, tree, node.type, maxScanDistance, brackets);
}
function matchMarkedBrackets(_state, _pos, dir, token, matching, brackets) {
let parent = token.parent, firstToken = { from: token.from, to: token.to };
function matchMarkedBrackets(_state, _pos, dir, token, handle, matching, brackets) {
let parent = token.parent, firstToken = { from: handle.from, to: handle.to };
let depth = 0, cursor = parent === null || parent === void 0 ? void 0 : parent.cursor();
if (cursor && (dir < 0 ? cursor.childBefore(token.from) : cursor.childAfter(token.to)))
do {
if (dir < 0 ? cursor.to <= token.from : cursor.from >= token.to) {
if (depth == 0 && matching.indexOf(cursor.type.name) > -1 && cursor.from < cursor.to) {
return { start: firstToken, end: { from: cursor.from, to: cursor.to }, matched: true };
let endHandle = findHandle(cursor);
return { start: firstToken, end: endHandle ? { from: endHandle.from, to: endHandle.to } : undefined, matched: true };
}
else if (matchingNodes(cursor.type, dir, brackets)) {
depth++;
}
else if (matchingNodes(cursor.type, -dir, brackets)) {
depth--;
if (depth == 0)
if (depth == 0) {
let endHandle = findHandle(cursor);
return {
start: firstToken,
end: cursor.from == cursor.to ? undefined : { from: cursor.from, to: cursor.to },
end: endHandle && endHandle.from < endHandle.to ? { from: endHandle.from, to: endHandle.to } : undefined,
matched: false
};
}
depth--;
}
}
} while (dir < 0 ? cursor.prevSibling() : cursor.nextSibling());
@ -1772,7 +1910,7 @@ function matchPlainBrackets(state, pos, dir, tree, tokenType, maxScanDistance, b
let basePos = pos + distance * dir;
for (let pos = dir > 0 ? 0 : text.length - 1, end = dir > 0 ? text.length : -1; pos != end; pos += dir) {
let found = brackets.indexOf(text[pos]);
if (found < 0 || tree.resolve(basePos + pos, 1).type != tokenType)
if (found < 0 || tree.resolveInner(basePos + pos, 1).type != tokenType)
continue;
if ((found % 2 == 0) == (dir > 0)) {
depth++;
@ -1823,10 +1961,11 @@ class StringStream {
/**
The current indent unit size.
*/
indentUnit) {
indentUnit, overrideIndent) {
this.string = string;
this.tabSize = tabSize;
this.indentUnit = indentUnit;
this.overrideIndent = overrideIndent;
/**
The current position on the line.
*/
@ -1927,7 +2066,8 @@ class StringStream {
Get the indentation column of the current line.
*/
indentation() {
return countCol(this.string, null, this.tabSize);
var _a;
return (_a = this.overrideIndent) !== null && _a !== void 0 ? _a : countCol(this.string, null, this.tabSize);
}
/**
Match the input against the given string or regular expression
@ -1969,6 +2109,7 @@ class StringStream {
function fullParser(spec) {
return {
name: spec.name || "",
token: spec.token,
blankLine: spec.blankLine || (() => { }),
startState: spec.startState || (() => true),
@ -1988,6 +2129,7 @@ function defaultCopyState(state) {
}
return newState;
}
const IndentedFrom = new WeakMap();
/**
A [language](https://codemirror.net/6/docs/ref/#language.Language) class based on a CodeMirror
5-style [streaming parser](https://codemirror.net/6/docs/ref/#language.StreamParser).
@ -2001,7 +2143,7 @@ class StreamLanguage extends Language {
return new Parse(self, input, fragments, ranges);
}
};
super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))]);
super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))], parser.name);
this.topNode = docID(data);
self = this;
this.streamParser = p;
@ -2018,7 +2160,14 @@ class StreamLanguage extends Language {
at = at.parent;
if (!at)
return null;
let start = findState(this, tree, 0, at.from, pos), statePos, state;
let from = undefined;
let { overrideIndentation } = cx.options;
if (overrideIndentation) {
from = IndentedFrom.get(cx.state);
if (from != null && from < pos - 1e4)
from = undefined;
}
let start = findState(this, tree, 0, at.from, from !== null && from !== void 0 ? from : pos), statePos, state;
if (start) {
state = start.state;
statePos = start.pos + 1;
@ -2027,12 +2176,13 @@ class StreamLanguage extends Language {
state = this.streamParser.startState(cx.unit);
statePos = 0;
}
if (pos - statePos > 10000 /* MaxIndentScanDist */)
if (pos - statePos > 10000 /* C.MaxIndentScanDist */)
return null;
while (statePos < pos) {
let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
if (line.length) {
let stream = new StringStream(line.text, cx.state.tabSize, cx.unit);
let indentation = overrideIndentation ? overrideIndentation(line.from) : -1;
let stream = new StringStream(line.text, cx.state.tabSize, cx.unit, indentation < 0 ? undefined : indentation);
while (stream.pos < end - line.from)
readToken(this.streamParser.token, stream, state);
}
@ -2043,8 +2193,10 @@ class StreamLanguage extends Language {
break;
statePos = line.to + 1;
}
let { text } = cx.lineAt(pos);
return this.streamParser.indent(state, /^\s*(.*)/.exec(text)[1], cx);
let line = cx.lineAt(pos);
if (overrideIndentation && from == null)
IndentedFrom.set(cx.state, line.from);
return this.streamParser.indent(state, /^\s*(.*)/.exec(line.text)[1], cx);
}
get allowsNesting() { return false; }
}
@ -2106,7 +2258,7 @@ class Parse {
this.chunks.push(tree.children[i]);
this.chunkPos.push(tree.positions[i]);
}
if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
if (context && this.parsedPos < context.viewport.from - 100000 /* C.MaxDistanceBeforeViewport */) {
this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
context.skipUntilInView(this.parsedPos, context.viewport.from);
this.parsedPos = context.viewport.from;
@ -2116,7 +2268,7 @@ class Parse {
advance() {
let context = ParseContext.get();
let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
let end = Math.min(parseEnd, this.chunkStart + 2048 /* C.ChunkSize */);
if (context)
end = Math.min(end, context.viewport.to);
while (this.parsedPos < end)
@ -2200,7 +2352,7 @@ class Parse {
let token = readToken(streamParser.token, stream, this.state);
if (token)
offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
if (stream.start > 10000 /* MaxLineLength */)
if (stream.start > 10000 /* C.MaxLineLength */)
break;
}
}
@ -2216,7 +2368,7 @@ class Parse {
length: this.parsedPos - this.chunkStart,
nodeSet,
topID: 0,
maxBufferLength: 2048 /* ChunkSize */,
maxBufferLength: 2048 /* C.ChunkSize */,
reused: this.chunkReused
});
tree = new common.Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
@ -2249,8 +2401,8 @@ for (let [legacyName, name] of [
["variable-2", "variableName.special"],
["string-2", "string.special"],
["def", "variableName.definition"],
["tag", "typeName"],
["attribute", "propertyName"],
["tag", "tagName"],
["attribute", "attributeName"],
["type", "typeName"],
["builtin", "variableName.standard"],
["qualifier", "modifier"],
@ -2322,6 +2474,7 @@ exports.StreamLanguage = StreamLanguage;
exports.StringStream = StringStream;
exports.TreeIndentContext = TreeIndentContext;
exports.bracketMatching = bracketMatching;
exports.bracketMatchingHandle = bracketMatchingHandle;
exports.codeFolding = codeFolding;
exports.continuedIndent = continuedIndent;
exports.defaultHighlightStyle = defaultHighlightStyle;
@ -2337,6 +2490,7 @@ exports.foldInside = foldInside;
exports.foldKeymap = foldKeymap;
exports.foldNodeProp = foldNodeProp;
exports.foldService = foldService;
exports.foldState = foldState;
exports.foldable = foldable;
exports.foldedRanges = foldedRanges;
exports.forceParsing = forceParsing;
@ -2345,6 +2499,7 @@ exports.getIndentation = getIndentation;
exports.highlightingFor = highlightingFor;
exports.indentNodeProp = indentNodeProp;
exports.indentOnInput = indentOnInput;
exports.indentRange = indentRange;
exports.indentService = indentService;
exports.indentString = indentString;
exports.indentUnit = indentUnit;
@ -2355,6 +2510,7 @@ exports.syntaxHighlighting = syntaxHighlighting;
exports.syntaxParserRunning = syntaxParserRunning;
exports.syntaxTree = syntaxTree;
exports.syntaxTreeAvailable = syntaxTreeAvailable;
exports.toggleFold = toggleFold;
exports.unfoldAll = unfoldAll;
exports.unfoldCode = unfoldCode;
exports.unfoldEffect = unfoldEffect;

View file

@ -1,7 +1,7 @@
import { NodeProp, Parser, Tree, TreeFragment, SyntaxNode, NodeType } from '@lezer/common';
import { LRParser, ParserConfig } from '@lezer/lr';
import * as _codemirror_state from '@codemirror/state';
import { Facet, Extension, EditorState, Range } from '@codemirror/state';
import { Facet, Extension, EditorState, StateField, Range } from '@codemirror/state';
import { EditorView, DecorationSet, Command, KeyBinding, ViewUpdate, BlockInfo, Decoration } from '@codemirror/view';
import { Highlighter, Tag } from '@lezer/highlight';
import { StyleModule, StyleSpec } from 'style-mod';
@ -48,6 +48,10 @@ declare class Language {
[name: string]: any;
}>;
/**
A language name.
*/
readonly name: string;
/**
The extension value to install this as the document language.
*/
readonly extension: Extension;
@ -70,7 +74,11 @@ declare class Language {
*/
data: Facet<{
[name: string]: any;
}>, parser: Parser, extraExtensions?: Extension[]);
}>, parser: Parser, extraExtensions?: Extension[],
/**
A language name.
*/
name?: string);
/**
Query whether this language is active at the given position.
*/
@ -102,6 +110,10 @@ declare class LRLanguage extends Language {
Define a language from a parser.
*/
static define(spec: {
/**
The [name](https://codemirror.net/6/docs/ref/#Language.name) of the language.
*/
name?: string;
/**
The parser to use. Should already have added editor-relevant
node props (and optionally things like dialect and top rule)
@ -118,9 +130,9 @@ declare class LRLanguage extends Language {
}): LRLanguage;
/**
Create a new instance of this language with a reconfigured
version of its parser.
version of its parser and optionally a new name.
*/
configure(options: ParserConfig): LRLanguage;
configure(options: ParserConfig, name?: string): LRLanguage;
get allowsNesting(): boolean;
}
/**
@ -214,7 +226,10 @@ declare class ParseContext {
static get(): ParseContext | null;
}
/**
The facet used to associate a language with an editor state.
The facet used to associate a language with an editor state. Used
by `Language` object's `extension` property (so you don't need to
manually wrap your languages in this). Can be used to access the
current language on a state.
*/
declare const language: Facet<Language, Language | null>;
/**
@ -346,14 +361,18 @@ declare class LanguageDescription {
/**
Facet that defines a way to provide a function that computes the
appropriate indentation depth at the start of a given line, or
`null` to indicate no appropriate indentation could be determined.
appropriate indentation depth, as a column number (see
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)), at the start of a given
line. A return value of `null` indicates no indentation can be
determined, and the line should inherit the indentation of the one
above it. A return value of `undefined` defers to the next indent
service.
*/
declare const indentService: Facet<(context: IndentContext, pos: number) => number | null, readonly ((context: IndentContext, pos: number) => number | null)[]>;
declare const indentService: Facet<(context: IndentContext, pos: number) => number | null | undefined, readonly ((context: IndentContext, pos: number) => number | null | undefined)[]>;
/**
Facet for overriding the unit by which indentation happens.
Should be a string consisting either entirely of spaces or
entirely of tabs. When not set, this defaults to 2 spaces.
Facet for overriding the unit by which indentation happens. Should
be a string consisting either entirely of the same whitespace
character. When not set, this defaults to 2 spaces.
*/
declare const indentUnit: Facet<string, string>;
/**
@ -371,15 +390,21 @@ tabs.
*/
declare function indentString(state: EditorState, cols: number): string;
/**
Get the indentation at the given position. Will first consult any
[indent services](https://codemirror.net/6/docs/ref/#language.indentService) that are registered,
and if none of those return an indentation, this will check the
syntax tree for the [indent node prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp)
and use that if found. Returns a number when an indentation could
be determined, and null otherwise.
Get the indentation, as a column number, at the given position.
Will first consult any [indent services](https://codemirror.net/6/docs/ref/#language.indentService)
that are registered, and if none of those return an indentation,
this will check the syntax tree for the [indent node
prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp) and use that if found. Returns a
number when an indentation could be determined, and null
otherwise.
*/
declare function getIndentation(context: IndentContext | EditorState, pos: number): number | null;
/**
Create a change set that auto-indents all lines touched by the
given document range.
*/
declare function indentRange(state: EditorState, from: number, to: number): _codemirror_state.ChangeSet;
/**
Indentation contexts are used when calling [indentation
services](https://codemirror.net/6/docs/ref/#language.indentService). They provide helper utilities
useful in indentation logic, and can selectively override the
@ -425,7 +450,7 @@ declare class IndentContext {
simulateBreak?: number;
/**
When `simulateBreak` is given, this can be used to make the
simulate break behave like a double line break.
simulated break behave like a double line break.
*/
simulateDoubleBreak?: boolean;
});
@ -469,8 +494,9 @@ declare class IndentContext {
/**
A syntax tree node prop used to associate indentation strategies
with node types. Such a strategy is a function from an indentation
context to a column number or null, where null indicates that no
definitive indentation can be determined.
context to a column number (see also
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)) or null, where null
indicates that no definitive indentation can be determined.
*/
declare const indentNodeProp: NodeProp<(context: TreeIndentContext) => number | null>;
/**
@ -617,6 +643,14 @@ State effect that unfolds the given range (if it was folded).
*/
declare const unfoldEffect: _codemirror_state.StateEffectType<DocRange>;
/**
The state field that stores the folded ranges (as a [decoration
set](https://codemirror.net/6/docs/ref/#view.DecorationSet)). Can be passed to
[`EditorState.toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) and
[`fromJSON`](https://codemirror.net/6/docs/ref/#state.EditorState^fromJSON) to serialize the fold
state.
*/
declare const foldState: StateField<DecorationSet>;
/**
Get a [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) containing the folded ranges
in the given state.
*/
@ -644,6 +678,12 @@ Unfold all folded code.
*/
declare const unfoldAll: Command;
/**
Toggle folding at cursors. Unfolds if there is an existing fold
starting in that line, tries to find a foldable range around it
otherwise.
*/
declare const toggleFold: Command;
/**
Default fold-related key bindings.
- Ctrl-Shift-[ (Cmd-Alt-[ on macOS): [`foldCode`](https://codemirror.net/6/docs/ref/#language.foldCode).
@ -716,6 +756,10 @@ A highlight style associates CSS styles with higlighting
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
*/
declare class HighlightStyle implements Highlighter {
/**
The tag styles used to create this highlight style.
*/
readonly specs: readonly TagStyle[];
/**
A style module holding the CSS rules for this highlight style.
When using
@ -851,6 +895,15 @@ highlighting style is used to indicate this.
*/
declare function bracketMatching(config?: Config): Extension;
/**
When larger syntax nodes, such as HTML tags, are marked as
opening/closing, it can be a bit messy to treat the whole node as
a matchable bracket. This node prop allows you to define, for such
a node, a handlethe part of the node that is highlighted, and
that the cursor must be on to activate highlighting in the first
place.
*/
declare const bracketMatchingHandle: NodeProp<(node: SyntaxNode) => SyntaxNode | null>;
/**
The result returned from `matchBrackets`.
*/
interface MatchResult {
@ -896,6 +949,7 @@ declare class StringStream {
The current indent unit size.
*/
indentUnit: number;
private overrideIndent?;
/**
The current position on the line.
*/
@ -917,7 +971,7 @@ declare class StringStream {
/**
The current indent unit size.
*/
indentUnit: number);
indentUnit: number, overrideIndent?: number | undefined);
/**
True if we are at the end of the line.
*/
@ -997,6 +1051,10 @@ copyable) object with state, in which it can store information
about the current context.
*/
interface StreamParser<State> {
/**
A name for this language.
*/
name?: string;
/**
Produce a start state for the parser.
*/
@ -1061,4 +1119,4 @@ declare class StreamLanguage<State> extends Language {
get allowsNesting(): boolean;
}
export { Config, HighlightStyle, IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, MatchResult, ParseContext, StreamLanguage, StreamParser, StringStream, TagStyle, TreeIndentContext, bracketMatching, codeFolding, continuedIndent, defaultHighlightStyle, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldAll, foldCode, foldEffect, foldGutter, foldInside, foldKeymap, foldNodeProp, foldService, foldable, foldedRanges, forceParsing, getIndentUnit, getIndentation, highlightingFor, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, matchBrackets, syntaxHighlighting, syntaxParserRunning, syntaxTree, syntaxTreeAvailable, unfoldAll, unfoldCode, unfoldEffect };
export { Config, HighlightStyle, IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, MatchResult, ParseContext, StreamLanguage, StreamParser, StringStream, TagStyle, TreeIndentContext, bracketMatching, bracketMatchingHandle, codeFolding, continuedIndent, defaultHighlightStyle, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldAll, foldCode, foldEffect, foldGutter, foldInside, foldKeymap, foldNodeProp, foldService, foldState, foldable, foldedRanges, forceParsing, getIndentUnit, getIndentation, highlightingFor, indentNodeProp, indentOnInput, indentRange, indentService, indentString, indentUnit, language, languageDataProp, matchBrackets, syntaxHighlighting, syntaxParserRunning, syntaxTree, syntaxTreeAvailable, toggleFold, unfoldAll, unfoldCode, unfoldEffect };

View file

@ -1,6 +1,6 @@
import { NodeProp, Tree, IterMode, TreeFragment, Parser, NodeType, NodeSet } from '@lezer/common';
import { NodeProp, IterMode, Tree, TreeFragment, Parser, NodeType, NodeSet } from '@lezer/common';
import { StateEffect, StateField, Facet, EditorState, countColumn, combineConfig, RangeSet, RangeSetBuilder, Prec } from '@codemirror/state';
import { ViewPlugin, logException, Decoration, EditorView, WidgetType, gutter, GutterMarker } from '@codemirror/view';
import { ViewPlugin, logException, EditorView, Decoration, WidgetType, gutter, GutterMarker } from '@codemirror/view';
import { tags, tagHighlighter, highlightTree, styleTags } from '@lezer/highlight';
import { StyleModule } from 'style-mod';
@ -45,8 +45,13 @@ class Language {
The [language data](https://codemirror.net/6/docs/ref/#state.EditorState.languageDataAt) facet
used for this language.
*/
data, parser, extraExtensions = []) {
data, parser, extraExtensions = [],
/**
A language name.
*/
name = "") {
this.data = data;
this.name = name;
// Kludge to define EditorState.tree as a debugging helper,
// without the EditorState package actually knowing about
// languages and lezer trees.
@ -134,8 +139,8 @@ A subclass of [`Language`](https://codemirror.net/6/docs/ref/#language.Language)
parsers.
*/
class LRLanguage extends Language {
constructor(data, parser) {
super(data, parser);
constructor(data, parser, name) {
super(data, parser, [], name);
this.parser = parser;
}
/**
@ -145,14 +150,14 @@ class LRLanguage extends Language {
let data = defineLanguageFacet(spec.languageData);
return new LRLanguage(data, spec.parser.configure({
props: [languageDataProp.add(type => type.isTop ? data : undefined)]
}));
}), spec.name);
}
/**
Create a new instance of this language with a reconfigured
version of its parser.
version of its parser and optionally a new name.
*/
configure(options) {
return new LRLanguage(this.data, this.parser.configure(options));
configure(options, name) {
return new LRLanguage(this.data, this.parser.configure(options), name || this.name);
}
get allowsNesting() { return this.parser.hasWrappers(); }
}
@ -174,7 +179,13 @@ up to that point if the tree isn't already available.
function ensureSyntaxTree(state, upto, timeout = 50) {
var _a;
let parse = (_a = state.field(Language.state, false)) === null || _a === void 0 ? void 0 : _a.context;
return !parse ? null : parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
if (!parse)
return null;
let oldVieport = parse.viewport;
parse.updateViewport({ from: 0, to: upto });
let result = parse.isDone(upto) || parse.work(timeout, upto) ? parse.tree : null;
parse.updateViewport(oldVieport);
return result;
}
/**
Queries whether there is a full syntax tree available up to the
@ -214,13 +225,13 @@ function syntaxParserRunning(view) {
}
// Lezer-style Input object for a Text document.
class DocInput {
constructor(doc, length = doc.length) {
constructor(doc) {
this.doc = doc;
this.length = length;
this.cursorPos = 0;
this.string = "";
this.cursor = doc.iter();
}
get length() { return this.doc.length; }
syncTo(pos) {
this.string = this.cursor.next(pos - this.cursorPos).value;
this.cursorPos = pos + this.string.length;
@ -499,14 +510,14 @@ class LanguageState {
// state updates with parse work beyond the viewport.
let upto = this.context.treeLen == tr.startState.doc.length ? undefined
: Math.max(tr.changes.mapPos(this.context.treeLen), newCx.viewport.to);
if (!newCx.work(20 /* Apply */, upto))
if (!newCx.work(20 /* Work.Apply */, upto))
newCx.takeTree();
return new LanguageState(newCx);
}
static init(state) {
let vpTo = Math.min(3000 /* InitViewport */, state.doc.length);
let vpTo = Math.min(3000 /* Work.InitViewport */, state.doc.length);
let parseState = ParseContext.create(state.facet(language).parser, state, { from: 0, to: vpTo });
if (!parseState.work(20 /* Apply */, vpTo))
if (!parseState.work(20 /* Work.Apply */, vpTo))
parseState.takeTree();
return new LanguageState(parseState);
}
@ -523,14 +534,14 @@ Language.state = /*@__PURE__*/StateField.define({
}
});
let requestIdle = (callback) => {
let timeout = setTimeout(() => callback(), 500 /* MaxPause */);
let timeout = setTimeout(() => callback(), 500 /* Work.MaxPause */);
return () => clearTimeout(timeout);
};
if (typeof requestIdleCallback != "undefined")
requestIdle = (callback) => {
let idle = -1, timeout = setTimeout(() => {
idle = requestIdleCallback(callback, { timeout: 500 /* MaxPause */ - 100 /* MinPause */ });
}, 100 /* MinPause */);
idle = requestIdleCallback(callback, { timeout: 500 /* Work.MaxPause */ - 100 /* Work.MinPause */ });
}, 100 /* Work.MinPause */);
return () => idle < 0 ? clearTimeout(timeout) : cancelIdleCallback(idle);
};
const isInputPending = typeof navigator != "undefined" && ((_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending)
@ -553,7 +564,7 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
this.scheduleWork();
if (update.docChanged) {
if (this.view.hasFocus)
this.chunkBudget += 50 /* ChangeBonus */;
this.chunkBudget += 50 /* Work.ChangeBonus */;
this.scheduleWork();
}
this.checkAsyncSchedule(cx);
@ -569,19 +580,19 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
this.working = null;
let now = Date.now();
if (this.chunkEnd < now && (this.chunkEnd < 0 || this.view.hasFocus)) { // Start a new chunk
this.chunkEnd = now + 30000 /* ChunkTime */;
this.chunkBudget = 3000 /* ChunkBudget */;
this.chunkEnd = now + 30000 /* Work.ChunkTime */;
this.chunkBudget = 3000 /* Work.ChunkBudget */;
}
if (this.chunkBudget <= 0)
return; // No more budget
let { state, viewport: { to: vpTo } } = this.view, field = state.field(Language.state);
if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* MaxParseAhead */))
if (field.tree == field.context.tree && field.context.isDone(vpTo + 100000 /* Work.MaxParseAhead */))
return;
let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Slice */, deadline && !isInputPending ? Math.max(25 /* MinSlice */, deadline.timeRemaining() - 5) : 1e9);
let endTime = Date.now() + Math.min(this.chunkBudget, 100 /* Work.Slice */, deadline && !isInputPending ? Math.max(25 /* Work.MinSlice */, deadline.timeRemaining() - 5) : 1e9);
let viewportFirst = field.context.treeLen < vpTo && state.doc.length > vpTo + 1000;
let done = field.context.work(() => {
return isInputPending && isInputPending() || Date.now() > endTime;
}, vpTo + (viewportFirst ? 0 : 100000 /* MaxParseAhead */));
}, vpTo + (viewportFirst ? 0 : 100000 /* Work.MaxParseAhead */));
this.chunkBudget -= Date.now() - now;
if (done || this.chunkBudget <= 0) {
field.context.takeTree();
@ -612,11 +623,21 @@ const parseWorker = /*@__PURE__*/ViewPlugin.fromClass(class ParseWorker {
eventHandlers: { focus() { this.scheduleWork(); } }
});
/**
The facet used to associate a language with an editor state.
The facet used to associate a language with an editor state. Used
by `Language` object's `extension` property (so you don't need to
manually wrap your languages in this). Can be used to access the
current language on a state.
*/
const language = /*@__PURE__*/Facet.define({
combine(languages) { return languages.length ? languages[0] : null; },
enables: [Language.state, parseWorker]
enables: language => [
Language.state,
parseWorker,
EditorView.contentAttributes.compute([language], state => {
let lang = state.facet(language);
return lang && lang.name ? { "data-language": lang.name } : {};
})
]
});
/**
This class bundles a [language](https://codemirror.net/6/docs/ref/#language.Language) with an
@ -746,22 +767,27 @@ class LanguageDescription {
/**
Facet that defines a way to provide a function that computes the
appropriate indentation depth at the start of a given line, or
`null` to indicate no appropriate indentation could be determined.
appropriate indentation depth, as a column number (see
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)), at the start of a given
line. A return value of `null` indicates no indentation can be
determined, and the line should inherit the indentation of the one
above it. A return value of `undefined` defers to the next indent
service.
*/
const indentService = /*@__PURE__*/Facet.define();
/**
Facet for overriding the unit by which indentation happens.
Should be a string consisting either entirely of spaces or
entirely of tabs. When not set, this defaults to 2 spaces.
Facet for overriding the unit by which indentation happens. Should
be a string consisting either entirely of the same whitespace
character. When not set, this defaults to 2 spaces.
*/
const indentUnit = /*@__PURE__*/Facet.define({
combine: values => {
if (!values.length)
return " ";
if (!/^(?: +|\t+)$/.test(values[0]))
let unit = values[0];
if (!unit || /\S/.test(unit) || Array.from(unit).some(e => e != unit[0]))
throw new Error("Invalid indent unit: " + JSON.stringify(values[0]));
return values[0];
return unit;
}
});
/**
@ -781,36 +807,64 @@ Will use tabs for as much of the columns as possible when the
tabs.
*/
function indentString(state, cols) {
let result = "", ts = state.tabSize;
if (state.facet(indentUnit).charCodeAt(0) == 9)
let result = "", ts = state.tabSize, ch = state.facet(indentUnit)[0];
if (ch == "\t") {
while (cols >= ts) {
result += "\t";
cols -= ts;
}
ch = " ";
}
for (let i = 0; i < cols; i++)
result += " ";
result += ch;
return result;
}
/**
Get the indentation at the given position. Will first consult any
[indent services](https://codemirror.net/6/docs/ref/#language.indentService) that are registered,
and if none of those return an indentation, this will check the
syntax tree for the [indent node prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp)
and use that if found. Returns a number when an indentation could
be determined, and null otherwise.
Get the indentation, as a column number, at the given position.
Will first consult any [indent services](https://codemirror.net/6/docs/ref/#language.indentService)
that are registered, and if none of those return an indentation,
this will check the syntax tree for the [indent node
prop](https://codemirror.net/6/docs/ref/#language.indentNodeProp) and use that if found. Returns a
number when an indentation could be determined, and null
otherwise.
*/
function getIndentation(context, pos) {
if (context instanceof EditorState)
context = new IndentContext(context);
for (let service of context.state.facet(indentService)) {
let result = service(context, pos);
if (result != null)
if (result !== undefined)
return result;
}
let tree = syntaxTree(context.state);
return tree ? syntaxIndentation(context, tree, pos) : null;
}
/**
Create a change set that auto-indents all lines touched by the
given document range.
*/
function indentRange(state, from, to) {
let updated = Object.create(null);
let context = new IndentContext(state, { overrideIndentation: start => { var _a; return (_a = updated[start]) !== null && _a !== void 0 ? _a : -1; } });
let changes = [];
for (let pos = from; pos <= to;) {
let line = state.doc.lineAt(pos);
pos = line.to + 1;
let indent = getIndentation(context, line.from);
if (indent == null)
continue;
if (!/\S/.test(line.text))
indent = 0;
let cur = /^\s*/.exec(line.text)[0];
let norm = indentString(state, indent);
if (cur != norm) {
updated[line.from] = indent;
changes.push({ from: line.from, to: line.from + cur.length, insert: norm });
}
}
return state.changes(changes);
}
/**
Indentation contexts are used when calling [indentation
services](https://codemirror.net/6/docs/ref/#language.indentService). They provide helper utilities
useful in indentation logic, and can selectively override the
@ -907,8 +961,9 @@ class IndentContext {
/**
A syntax tree node prop used to associate indentation strategies
with node types. Such a strategy is a function from an indentation
context to a column number or null, where null indicates that no
definitive indentation can be determined.
context to a column number (see also
[`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)) or null, where null
indicates that no definitive indentation can be determined.
*/
const indentNodeProp = /*@__PURE__*/new NodeProp();
// Compute the indentation for a given position from the syntax tree.
@ -1139,7 +1194,7 @@ function syntaxFolding(state, start, end) {
let tree = syntaxTree(state);
if (tree.length < end)
return null;
let inner = tree.resolveInner(end);
let inner = tree.resolveInner(end, 1);
let found = null;
for (let cur = inner; cur; cur = cur.parent) {
if (cur.to <= end || cur.from > end)
@ -1200,6 +1255,13 @@ function selectedLines(view) {
}
return lines;
}
/**
The state field that stores the folded ranges (as a [decoration
set](https://codemirror.net/6/docs/ref/#view.DecorationSet)). Can be passed to
[`EditorState.toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) and
[`fromJSON`](https://codemirror.net/6/docs/ref/#state.EditorState^fromJSON) to serialize the fold
state.
*/
const foldState = /*@__PURE__*/StateField.define({
create() {
return Decoration.none;
@ -1227,7 +1289,24 @@ const foldState = /*@__PURE__*/StateField.define({
}
return folded;
},
provide: f => EditorView.decorations.from(f)
provide: f => EditorView.decorations.from(f),
toJSON(folded, state) {
let ranges = [];
folded.between(0, state.doc.length, (from, to) => { ranges.push(from, to); });
return ranges;
},
fromJSON(value) {
if (!Array.isArray(value) || value.length % 2)
throw new RangeError("Invalid JSON for fold state");
let ranges = [];
for (let i = 0; i < value.length;) {
let from = value[i++], to = value[i++];
if (typeof from != "number" || typeof to != "number")
throw new RangeError("Invalid JSON for fold state");
ranges.push(foldWidget.range(from, to));
}
return Decoration.set(ranges, true);
}
});
/**
Get a [range set](https://codemirror.net/6/docs/ref/#state.RangeSet) containing the folded ranges
@ -1320,6 +1399,41 @@ const unfoldAll = view => {
view.dispatch({ effects });
return true;
};
// Find the foldable region containing the given line, if one exists
function foldableContainer(view, lineBlock) {
// Look backwards through line blocks until we find a foldable region that
// intersects with the line
for (let line = lineBlock;;) {
let foldableRegion = foldable(view.state, line.from, line.to);
if (foldableRegion && foldableRegion.to > lineBlock.from)
return foldableRegion;
if (!line.from)
return null;
line = view.lineBlockAt(line.from - 1);
}
}
/**
Toggle folding at cursors. Unfolds if there is an existing fold
starting in that line, tries to find a foldable range around it
otherwise.
*/
const toggleFold = (view) => {
let effects = [];
for (let line of selectedLines(view)) {
let folded = findFold(view.state, line.from, line.to);
if (folded) {
effects.push(unfoldEffect.of(folded), announceFold(view, folded, false));
}
else {
let foldRange = foldableContainer(view, line);
if (foldRange)
effects.push(foldEffect.of(foldRange), announceFold(view, foldRange));
}
}
if (effects.length > 0)
view.dispatch({ effects: maybeEnable(view.state, effects) });
return !!effects.length;
};
/**
Default fold-related key bindings.
@ -1475,7 +1589,12 @@ A highlight style associates CSS styles with higlighting
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
*/
class HighlightStyle {
constructor(spec, options) {
constructor(
/**
The tag styles used to create this highlight style.
*/
specs, options) {
this.specs = specs;
let modSpec;
function def(spec) {
let cls = StyleModule.newName();
@ -1486,7 +1605,7 @@ class HighlightStyle {
const scopeOpt = options.scope;
this.scope = scopeOpt instanceof Language ? (type) => type.prop(languageDataProp) == scopeOpt.data
: scopeOpt ? (type) => type == scopeOpt : undefined;
this.style = tagHighlighter(spec.map(style => ({
this.style = tagHighlighter(specs.map(style => ({
tag: style.tag,
class: style.class || def(Object.assign({}, style, { tag: null }))
})), {
@ -1603,7 +1722,7 @@ A default highlight style (works well with light themes).
*/
const defaultHighlightStyle = /*@__PURE__*/HighlightStyle.define([
{ tag: tags.meta,
color: "#7a757a" },
color: "#404740" },
{ tag: tags.link,
textDecoration: "underline" },
{ tag: tags.heading,
@ -1702,6 +1821,15 @@ highlighting style is used to indicate this.
function bracketMatching(config = {}) {
return [bracketMatchingConfig.of(config), bracketMatchingUnique];
}
/**
When larger syntax nodes, such as HTML tags, are marked as
opening/closing, it can be a bit messy to treat the whole node as
a matchable bracket. This node prop allows you to define, for such
a node, a handlethe part of the node that is highlighted, and
that the cursor must be on to activate highlighting in the first
place.
*/
const bracketMatchingHandle = /*@__PURE__*/new NodeProp();
function matchingNodes(node, dir, brackets) {
let byProp = node.prop(dir < 0 ? NodeProp.openedBy : NodeProp.closedBy);
if (byProp)
@ -1713,6 +1841,10 @@ function matchingNodes(node, dir, brackets) {
}
return null;
}
function findHandle(node) {
let hasHandle = node.type.prop(bracketMatchingHandle);
return hasHandle ? hasHandle(node.node) : node;
}
/**
Find the matching bracket for the token at `pos`, scanning
direction `dir`. Only the `brackets` and `maxScanDistance`
@ -1724,31 +1856,37 @@ function matchBrackets(state, pos, dir, config = {}) {
let tree = syntaxTree(state), node = tree.resolveInner(pos, dir);
for (let cur = node; cur; cur = cur.parent) {
let matches = matchingNodes(cur.type, dir, brackets);
if (matches && cur.from < cur.to)
return matchMarkedBrackets(state, pos, dir, cur, matches, brackets);
if (matches && cur.from < cur.to) {
let handle = findHandle(cur);
if (handle && (dir > 0 ? pos >= handle.from && pos < handle.to : pos > handle.from && pos <= handle.to))
return matchMarkedBrackets(state, pos, dir, cur, handle, matches, brackets);
}
}
return matchPlainBrackets(state, pos, dir, tree, node.type, maxScanDistance, brackets);
}
function matchMarkedBrackets(_state, _pos, dir, token, matching, brackets) {
let parent = token.parent, firstToken = { from: token.from, to: token.to };
function matchMarkedBrackets(_state, _pos, dir, token, handle, matching, brackets) {
let parent = token.parent, firstToken = { from: handle.from, to: handle.to };
let depth = 0, cursor = parent === null || parent === void 0 ? void 0 : parent.cursor();
if (cursor && (dir < 0 ? cursor.childBefore(token.from) : cursor.childAfter(token.to)))
do {
if (dir < 0 ? cursor.to <= token.from : cursor.from >= token.to) {
if (depth == 0 && matching.indexOf(cursor.type.name) > -1 && cursor.from < cursor.to) {
return { start: firstToken, end: { from: cursor.from, to: cursor.to }, matched: true };
let endHandle = findHandle(cursor);
return { start: firstToken, end: endHandle ? { from: endHandle.from, to: endHandle.to } : undefined, matched: true };
}
else if (matchingNodes(cursor.type, dir, brackets)) {
depth++;
}
else if (matchingNodes(cursor.type, -dir, brackets)) {
depth--;
if (depth == 0)
if (depth == 0) {
let endHandle = findHandle(cursor);
return {
start: firstToken,
end: cursor.from == cursor.to ? undefined : { from: cursor.from, to: cursor.to },
end: endHandle && endHandle.from < endHandle.to ? { from: endHandle.from, to: endHandle.to } : undefined,
matched: false
};
}
depth--;
}
}
} while (dir < 0 ? cursor.prevSibling() : cursor.nextSibling());
@ -1768,7 +1906,7 @@ function matchPlainBrackets(state, pos, dir, tree, tokenType, maxScanDistance, b
let basePos = pos + distance * dir;
for (let pos = dir > 0 ? 0 : text.length - 1, end = dir > 0 ? text.length : -1; pos != end; pos += dir) {
let found = brackets.indexOf(text[pos]);
if (found < 0 || tree.resolve(basePos + pos, 1).type != tokenType)
if (found < 0 || tree.resolveInner(basePos + pos, 1).type != tokenType)
continue;
if ((found % 2 == 0) == (dir > 0)) {
depth++;
@ -1819,10 +1957,11 @@ class StringStream {
/**
The current indent unit size.
*/
indentUnit) {
indentUnit, overrideIndent) {
this.string = string;
this.tabSize = tabSize;
this.indentUnit = indentUnit;
this.overrideIndent = overrideIndent;
/**
The current position on the line.
*/
@ -1923,7 +2062,8 @@ class StringStream {
Get the indentation column of the current line.
*/
indentation() {
return countCol(this.string, null, this.tabSize);
var _a;
return (_a = this.overrideIndent) !== null && _a !== void 0 ? _a : countCol(this.string, null, this.tabSize);
}
/**
Match the input against the given string or regular expression
@ -1965,6 +2105,7 @@ class StringStream {
function fullParser(spec) {
return {
name: spec.name || "",
token: spec.token,
blankLine: spec.blankLine || (() => { }),
startState: spec.startState || (() => true),
@ -1984,6 +2125,7 @@ function defaultCopyState(state) {
}
return newState;
}
const IndentedFrom = /*@__PURE__*/new WeakMap();
/**
A [language](https://codemirror.net/6/docs/ref/#language.Language) class based on a CodeMirror
5-style [streaming parser](https://codemirror.net/6/docs/ref/#language.StreamParser).
@ -1997,7 +2139,7 @@ class StreamLanguage extends Language {
return new Parse(self, input, fragments, ranges);
}
};
super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))]);
super(data, impl, [indentService.of((cx, pos) => this.getIndent(cx, pos))], parser.name);
this.topNode = docID(data);
self = this;
this.streamParser = p;
@ -2014,7 +2156,14 @@ class StreamLanguage extends Language {
at = at.parent;
if (!at)
return null;
let start = findState(this, tree, 0, at.from, pos), statePos, state;
let from = undefined;
let { overrideIndentation } = cx.options;
if (overrideIndentation) {
from = IndentedFrom.get(cx.state);
if (from != null && from < pos - 1e4)
from = undefined;
}
let start = findState(this, tree, 0, at.from, from !== null && from !== void 0 ? from : pos), statePos, state;
if (start) {
state = start.state;
statePos = start.pos + 1;
@ -2023,12 +2172,13 @@ class StreamLanguage extends Language {
state = this.streamParser.startState(cx.unit);
statePos = 0;
}
if (pos - statePos > 10000 /* MaxIndentScanDist */)
if (pos - statePos > 10000 /* C.MaxIndentScanDist */)
return null;
while (statePos < pos) {
let line = cx.state.doc.lineAt(statePos), end = Math.min(pos, line.to);
if (line.length) {
let stream = new StringStream(line.text, cx.state.tabSize, cx.unit);
let indentation = overrideIndentation ? overrideIndentation(line.from) : -1;
let stream = new StringStream(line.text, cx.state.tabSize, cx.unit, indentation < 0 ? undefined : indentation);
while (stream.pos < end - line.from)
readToken(this.streamParser.token, stream, state);
}
@ -2039,8 +2189,10 @@ class StreamLanguage extends Language {
break;
statePos = line.to + 1;
}
let { text } = cx.lineAt(pos);
return this.streamParser.indent(state, /^\s*(.*)/.exec(text)[1], cx);
let line = cx.lineAt(pos);
if (overrideIndentation && from == null)
IndentedFrom.set(cx.state, line.from);
return this.streamParser.indent(state, /^\s*(.*)/.exec(line.text)[1], cx);
}
get allowsNesting() { return false; }
}
@ -2102,7 +2254,7 @@ class Parse {
this.chunks.push(tree.children[i]);
this.chunkPos.push(tree.positions[i]);
}
if (context && this.parsedPos < context.viewport.from - 100000 /* MaxDistanceBeforeViewport */) {
if (context && this.parsedPos < context.viewport.from - 100000 /* C.MaxDistanceBeforeViewport */) {
this.state = this.lang.streamParser.startState(getIndentUnit(context.state));
context.skipUntilInView(this.parsedPos, context.viewport.from);
this.parsedPos = context.viewport.from;
@ -2112,7 +2264,7 @@ class Parse {
advance() {
let context = ParseContext.get();
let parseEnd = this.stoppedAt == null ? this.to : Math.min(this.to, this.stoppedAt);
let end = Math.min(parseEnd, this.chunkStart + 2048 /* ChunkSize */);
let end = Math.min(parseEnd, this.chunkStart + 2048 /* C.ChunkSize */);
if (context)
end = Math.min(end, context.viewport.to);
while (this.parsedPos < end)
@ -2196,7 +2348,7 @@ class Parse {
let token = readToken(streamParser.token, stream, this.state);
if (token)
offset = this.emitToken(this.lang.tokenTable.resolve(token), this.parsedPos + stream.start, this.parsedPos + stream.pos, 4, offset);
if (stream.start > 10000 /* MaxLineLength */)
if (stream.start > 10000 /* C.MaxLineLength */)
break;
}
}
@ -2212,7 +2364,7 @@ class Parse {
length: this.parsedPos - this.chunkStart,
nodeSet,
topID: 0,
maxBufferLength: 2048 /* ChunkSize */,
maxBufferLength: 2048 /* C.ChunkSize */,
reused: this.chunkReused
});
tree = new Tree(tree.type, tree.children, tree.positions, tree.length, [[this.lang.stateAfter, this.lang.streamParser.copyState(this.state)]]);
@ -2245,8 +2397,8 @@ for (let [legacyName, name] of [
["variable-2", "variableName.special"],
["string-2", "string.special"],
["def", "variableName.definition"],
["tag", "typeName"],
["attribute", "propertyName"],
["tag", "tagName"],
["attribute", "attributeName"],
["type", "typeName"],
["builtin", "variableName.standard"],
["qualifier", "modifier"],
@ -2307,4 +2459,4 @@ function docID(data) {
return type;
}
export { HighlightStyle, IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, StreamLanguage, StringStream, TreeIndentContext, bracketMatching, codeFolding, continuedIndent, defaultHighlightStyle, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldAll, foldCode, foldEffect, foldGutter, foldInside, foldKeymap, foldNodeProp, foldService, foldable, foldedRanges, forceParsing, getIndentUnit, getIndentation, highlightingFor, indentNodeProp, indentOnInput, indentService, indentString, indentUnit, language, languageDataProp, matchBrackets, syntaxHighlighting, syntaxParserRunning, syntaxTree, syntaxTreeAvailable, unfoldAll, unfoldCode, unfoldEffect };
export { HighlightStyle, IndentContext, LRLanguage, Language, LanguageDescription, LanguageSupport, ParseContext, StreamLanguage, StringStream, TreeIndentContext, bracketMatching, bracketMatchingHandle, codeFolding, continuedIndent, defaultHighlightStyle, defineLanguageFacet, delimitedIndent, ensureSyntaxTree, flatIndent, foldAll, foldCode, foldEffect, foldGutter, foldInside, foldKeymap, foldNodeProp, foldService, foldState, foldable, foldedRanges, forceParsing, getIndentUnit, getIndentation, highlightingFor, indentNodeProp, indentOnInput, indentRange, indentService, indentString, indentUnit, language, languageDataProp, matchBrackets, syntaxHighlighting, syntaxParserRunning, syntaxTree, syntaxTreeAvailable, toggleFold, unfoldAll, unfoldCode, unfoldEffect };

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/language",
"version": "6.0.0",
"version": "6.5.0",
"description": "Language support infrastructure for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",
@ -12,7 +12,7 @@
],
"author": {
"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"email": "marijn@haverbeke.berlin",
"url": "http://marijnhaverbeke.nl"
},
"type": "module",

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,9 @@
## 6.1.0 (2022-11-15)
### New features
The new `forEachDiagnostic` function can be used to iterate over the diagnostics in an editor state.
## 6.0.0 (2022-06-08)
### Breaking changes

View file

@ -1,13 +1,13 @@
# @codemirror/lint [![NPM version](https://img.shields.io/npm/v/@codemirror/lint.svg)](https://www.npmjs.org/package/@codemirror/lint)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#lint) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lint/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#lint) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/lint/blob/main/CHANGELOG.md) ]
This package implements linting support for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/lint/tree/main/LICENSE).

View file

@ -197,7 +197,7 @@ A set of default key bindings for the lint functionality.
- F8: [`nextDiagnostic`](https://codemirror.net/6/docs/ref/#lint.nextDiagnostic)
*/
const lintKeymap = [
{ key: "Mod-Shift-m", run: openLintPanel },
{ key: "Mod-Shift-m", run: openLintPanel, preventDefault: true },
{ key: "F8", run: nextDiagnostic }
];
const lintPlugin = view.ViewPlugin.fromClass(class {
@ -611,8 +611,8 @@ class LintGutterMarker extends view.GutterMarker {
function trackHoverOn(view, marker) {
let mousemove = (event) => {
let rect = marker.getBoundingClientRect();
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
if (event.clientX > rect.left - 10 /* Hover.Margin */ && event.clientX < rect.right + 10 /* Hover.Margin */ &&
event.clientY > rect.top - 10 /* Hover.Margin */ && event.clientY < rect.bottom + 10 /* Hover.Margin */)
return;
for (let target = event.target; target; target = target.parentNode) {
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
@ -715,14 +715,14 @@ const lintGutterTheme = view.EditorView.baseTheme({
".cm-lint-marker-warning": {
content: svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
},
".cm-lint-marker-error:before": {
".cm-lint-marker-error": {
content: svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
},
});
const lintGutterConfig = state.Facet.define({
combine(configs) {
return state.combineConfig(configs, {
hoverTime: 300 /* Time */,
hoverTime: 300 /* Hover.Time */,
markerFilter: null,
tooltipFilter: null
});
@ -736,9 +736,23 @@ the diagnostics.
function lintGutter(config = {}) {
return [lintGutterConfig.of(config), lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
}
/**
Iterate over the marked diagnostics for the given editor state,
calling `f` for each of them. Note that, if the document changed
since the diagnostics werecreated, the `Diagnostic` object will
hold the original outdated position, whereas the `to` and `from`
arguments hold the diagnostic's current position.
*/
function forEachDiagnostic(state$1, f) {
let lState = state$1.field(lintState, false);
if (lState && lState.diagnostics.size)
for (let iter = state.RangeSet.iter([lState.diagnostics]); iter.value; iter.next())
f(iter.value.spec.diagnostic, iter.from, iter.to);
}
exports.closeLintPanel = closeLintPanel;
exports.diagnosticCount = diagnosticCount;
exports.forEachDiagnostic = forEachDiagnostic;
exports.forceLinting = forceLinting;
exports.lintGutter = lintGutter;
exports.lintKeymap = lintKeymap;

View file

@ -145,5 +145,13 @@ each line that has diagnostics, which can be hovered over to see
the diagnostics.
*/
declare function lintGutter(config?: LintGutterConfig): Extension;
/**
Iterate over the marked diagnostics for the given editor state,
calling `f` for each of them. Note that, if the document changed
since the diagnostics werecreated, the `Diagnostic` object will
hold the original outdated position, whereas the `to` and `from`
arguments hold the diagnostic's current position.
*/
declare function forEachDiagnostic(state: EditorState, f: (d: Diagnostic, from: number, to: number) => void): void;
export { Action, Diagnostic, LintSource, closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
export { Action, Diagnostic, LintSource, closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };

View file

@ -1,4 +1,4 @@
import { Decoration, showPanel, EditorView, ViewPlugin, hoverTooltip, logException, gutter, showTooltip, getPanel, WidgetType, GutterMarker } from '@codemirror/view';
import { Decoration, showPanel, EditorView, ViewPlugin, logException, gutter, showTooltip, getPanel, WidgetType, hoverTooltip, GutterMarker } from '@codemirror/view';
import { StateEffect, StateField, Facet, combineConfig, RangeSet } from '@codemirror/state';
import elt from 'crelt';
@ -189,7 +189,7 @@ A set of default key bindings for the lint functionality.
- F8: [`nextDiagnostic`](https://codemirror.net/6/docs/ref/#lint.nextDiagnostic)
*/
const lintKeymap = [
{ key: "Mod-Shift-m", run: openLintPanel },
{ key: "Mod-Shift-m", run: openLintPanel, preventDefault: true },
{ key: "F8", run: nextDiagnostic }
];
const lintPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
@ -603,8 +603,8 @@ class LintGutterMarker extends GutterMarker {
function trackHoverOn(view, marker) {
let mousemove = (event) => {
let rect = marker.getBoundingClientRect();
if (event.clientX > rect.left - 10 /* Margin */ && event.clientX < rect.right + 10 /* Margin */ &&
event.clientY > rect.top - 10 /* Margin */ && event.clientY < rect.bottom + 10 /* Margin */)
if (event.clientX > rect.left - 10 /* Hover.Margin */ && event.clientX < rect.right + 10 /* Hover.Margin */ &&
event.clientY > rect.top - 10 /* Hover.Margin */ && event.clientY < rect.bottom + 10 /* Hover.Margin */)
return;
for (let target = event.target; target; target = target.parentNode) {
if (target.nodeType == 1 && target.classList.contains("cm-tooltip-lint"))
@ -707,14 +707,14 @@ const lintGutterTheme = /*@__PURE__*/EditorView.baseTheme({
".cm-lint-marker-warning": {
content: /*@__PURE__*/svg(`<path fill="#fe8" stroke="#fd7" stroke-width="6" stroke-linejoin="round" d="M20 6L37 35L3 35Z"/>`),
},
".cm-lint-marker-error:before": {
".cm-lint-marker-error": {
content: /*@__PURE__*/svg(`<circle cx="20" cy="20" r="15" fill="#f87" stroke="#f43" stroke-width="6"/>`)
},
});
const lintGutterConfig = /*@__PURE__*/Facet.define({
combine(configs) {
return combineConfig(configs, {
hoverTime: 300 /* Time */,
hoverTime: 300 /* Hover.Time */,
markerFilter: null,
tooltipFilter: null
});
@ -728,5 +728,18 @@ the diagnostics.
function lintGutter(config = {}) {
return [lintGutterConfig.of(config), lintGutterMarkers, lintGutterExtension, lintGutterTheme, lintGutterTooltip];
}
/**
Iterate over the marked diagnostics for the given editor state,
calling `f` for each of them. Note that, if the document changed
since the diagnostics werecreated, the `Diagnostic` object will
hold the original outdated position, whereas the `to` and `from`
arguments hold the diagnostic's current position.
*/
function forEachDiagnostic(state, f) {
let lState = state.field(lintState, false);
if (lState && lState.diagnostics.size)
for (let iter = RangeSet.iter([lState.diagnostics]); iter.value; iter.next())
f(iter.value.spec.diagnostic, iter.from, iter.to);
}
export { closeLintPanel, diagnosticCount, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };
export { closeLintPanel, diagnosticCount, forEachDiagnostic, forceLinting, lintGutter, lintKeymap, linter, nextDiagnostic, openLintPanel, setDiagnostics, setDiagnosticsEffect };

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/lint",
"version": "6.0.0",
"version": "6.1.0",
"description": "Linting support for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,51 @@
## 6.2.3 (2022-11-14)
### Bug fixes
Fix a bug that hid the search dialog's close button when the editor was read-only.
## 6.2.2 (2022-10-18)
### Bug fixes
When `literal` is off, \n, \r, and \t escapes are now also supported in replacement text.
Make sure search dialog inputs don't get treated as form fields when the editor is created inside a form.
Fix a bug in `RegExpCursor` that would cause it to stop matching in the middle of a line when its current match position was equal to the length of the line.
## 6.2.1 (2022-09-26)
### Bug fixes
By-word search queries will now skip any result that had word characters both before and after a match boundary.
## 6.2.0 (2022-08-25)
### New features
A new `wholeWord` search query flag can be used to limit matches to whole words.
`SearchCursor` and `RegExpCursor` now support a `test` parameter that can be used to ignore certain matches.
## 6.1.0 (2022-08-16)
### Bug fixes
Fix an infinite loop when the match position of a `RegExpCursor` ended up in the middle of an UTF16 surrogate pair.
### New features
The `literal` search option can now be set to make literal queries the default.
The new `searchPanelOpen` function can be used to find out whether the search panel is open for a given state.
## 6.0.1 (2022-07-22)
### Bug fixes
`findNext` and `findPrevious` will now return to the current result (and scroll it into view) if no other matches are found.
## 6.0.0 (2022-06-08)
### Bug fixes

View file

@ -1,13 +1,13 @@
# @codemirror/search [![NPM version](https://img.shields.io/npm/v/@codemirror/search.svg)](https://www.npmjs.org/package/@codemirror/search)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#search) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/search/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#search) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/search/blob/main/CHANGELOG.md) ]
This package implements search functionality for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/search/tree/main/LICENSE).

View file

@ -30,7 +30,8 @@ class SearchCursor {
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text, query, from = 0, to = text.length, normalize) {
constructor(text, query, from = 0, to = text.length, normalize, test) {
this.test = test;
/**
The current match (only holds a meaningful value after
[`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when
@ -124,6 +125,8 @@ class SearchCursor {
else
this.matches.push(1, pos);
}
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferPos))
match = null;
return match;
}
}
@ -144,6 +147,7 @@ class RegExpCursor {
`new RegExp`).
*/
constructor(text, query, options, from = 0, to = text.length) {
this.text = text;
this.to = to;
this.curLine = "";
/**
@ -160,10 +164,11 @@ class RegExpCursor {
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
return new MultilineRegExpCursor(text, query, options, from, to);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.getLine(this.curLineStart);
}
getLine(skip) {
@ -194,10 +199,10 @@ class RegExpCursor {
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
if (match) {
let from = this.curLineStart + match.index, to = from + match[0].length;
this.matchPos = to + (from == to ? 1 : 0);
if (from == this.curLine.length)
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
if (from == this.curLineStart + this.curLine.length)
this.nextLine();
if (from < to || from > this.value.to) {
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
return this;
}
@ -248,9 +253,10 @@ class MultilineRegExpCursor {
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Base */));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
@ -264,24 +270,23 @@ class MultilineRegExpCursor {
this.re.lastIndex = off + 1;
match = this.re.exec(this.flat.text);
}
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if (match && this.flat.to < this.to && match.index + match[0].length > this.flat.text.length - 10)
match = null;
if (match) {
let from = this.flat.from + match.index, to = from + match[0].length;
this.value = { from, to, match };
this.matchPos = to + (from == to ? 1 : 0);
return this;
}
else {
if (this.flat.to == this.to) {
this.done = true;
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&
(!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
if (this.flat.to == this.to) {
this.done = true;
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
}
}
@ -298,6 +303,14 @@ function validRegExp(source) {
return false;
}
}
function toCharEnd(text, pos) {
if (pos >= text.length)
return pos;
let line = text.lineAt(pos), next;
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)
pos++;
return pos;
}
function createLineDialog(view) {
let input = elt__default["default"]("input", { class: "cm-textfield", name: "line" });
@ -549,12 +562,13 @@ const selectNextOccurrence = ({ state: state$1, dispatch }) => {
const searchConfigFacet = state.Facet.define({
combine(configs) {
var _a;
return {
top: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.top, undefined) || false,
caseSensitive: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.caseSensitive, undefined) || false,
createPanel: ((_a = configs.find(c => c.createPanel)) === null || _a === void 0 ? void 0 : _a.createPanel) || (view => new SearchPanel(view))
};
return state.combineConfig(configs, {
top: false,
caseSensitive: false,
literal: false,
wholeWord: false,
createPanel: view => new SearchPanel(view)
});
}
});
/**
@ -576,17 +590,27 @@ class SearchQuery {
constructor(config) {
this.search = config.search;
this.caseSensitive = !!config.caseSensitive;
this.literal = !!config.literal;
this.regexp = !!config.regexp;
this.replace = config.replace || "";
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = config.literal ? this.search : this.search.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
}
/**
@internal
*/
unquote(text) {
return this.literal ? text :
text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
/**
Compare this query to another query.
*/
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp;
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
this.wholeWord == other.wholeWord;
}
/**
@internal
@ -596,10 +620,13 @@ class SearchQuery {
}
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc, from = 0, to = doc.length) {
return this.regexp ? regexpCursor(this, doc, from, to) : stringCursor(this, doc, from, to);
getCursor(state$1, from = 0, to) {
let st = state$1.doc ? state$1 : state.EditorState.create({ doc: state$1 });
if (to == null)
to = st.doc.length;
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
}
}
class QueryType {
@ -607,41 +634,53 @@ class QueryType {
this.spec = spec;
}
}
function stringCursor(spec, doc, from, to) {
return new SearchCursor(doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase());
function stringCursor(spec, state, from, to) {
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(charBefore(buf, from - bufPos)) != state.CharCategory.Word ||
categorizer(charAfter(buf, from - bufPos)) != state.CharCategory.Word) &&
(categorizer(charAfter(buf, to - bufPos)) != state.CharCategory.Word ||
categorizer(charBefore(buf, to - bufPos)) != state.CharCategory.Word);
};
}
class StringQuery extends QueryType {
constructor(spec) {
super(spec);
}
nextMatch(doc, curFrom, curTo) {
let cursor = stringCursor(this.spec, doc, curTo, doc.length).nextOverlapping();
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done)
cursor = stringCursor(this.spec, doc, 0, curFrom).nextOverlapping();
cursor = stringCursor(this.spec, state, 0, curFrom).nextOverlapping();
return cursor.done ? null : cursor.value;
}
// Searching in reverse is, rather than implementing inverted search
// cursor, done by scanning chunk after chunk forward.
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, doc, start, pos), range = null;
let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, state, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* ChunkSize */;
pos -= 10000 /* FindPrev.ChunkSize */;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(_result) { return this.spec.replace; }
matchAll(doc, limit) {
let cursor = stringCursor(this.spec, doc, 0, doc.length), ranges = [];
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -649,26 +688,42 @@ class StringQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = stringCursor(this.spec, doc, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, doc.length));
highlight(state, from, to, add) {
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
function regexpCursor(spec, doc, from, to) {
return new RegExpCursor(doc, spec.search, spec.caseSensitive ? undefined : { ignoreCase: true }, from, to);
function regexpCursor(spec, state, from, to) {
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
}, from, to);
}
function charBefore(str, index) {
return str.slice(state.findClusterBreak(str, index, false), index);
}
function charAfter(str, index) {
return str.slice(index, state.findClusterBreak(str, index));
}
function regexpWordTest(categorizer) {
return (_from, _to, match) => !match[0].length ||
(categorizer(charBefore(match.input, match.index)) != state.CharCategory.Word ||
categorizer(charAfter(match.input, match.index)) != state.CharCategory.Word) &&
(categorizer(charAfter(match.input, match.index + match[0].length)) != state.CharCategory.Word ||
categorizer(charBefore(match.input, match.index + match[0].length)) != state.CharCategory.Word);
}
class RegExpQuery extends QueryType {
nextMatch(doc, curFrom, curTo) {
let cursor = regexpCursor(this.spec, doc, curTo, doc.length).next();
nextMatch(state, curFrom, curTo) {
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
if (cursor.done)
cursor = regexpCursor(this.spec, doc, 0, curFrom).next();
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* ChunkSize */);
let cursor = regexpCursor(this.spec, doc, start, to), range = null;
let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);
let cursor = regexpCursor(this.spec, state, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
@ -677,18 +732,18 @@ class RegExpQuery extends QueryType {
return null;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
return this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
return this.spec.unquote(this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
: i == "&" ? result.match[0]
: i != "0" && +i < result.match.length ? result.match[i]
: m);
: m));
}
matchAll(doc, limit) {
let cursor = regexpCursor(this.spec, doc, 0, doc.length), ranges = [];
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -696,8 +751,8 @@ class RegExpQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = regexpCursor(this.spec, doc, Math.max(0, from - 250 /* HighlightMargin */), Math.min(to + 250 /* HighlightMargin */, doc.length));
highlight(state, from, to, add) {
let cursor = regexpCursor(this.spec, state, Math.max(0, from - 250 /* RegExp.HighlightMargin */), Math.min(to + 250 /* RegExp.HighlightMargin */, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
@ -733,6 +788,13 @@ function getSearchQuery(state) {
let curState = state.field(searchState, false);
return curState ? curState.query.spec : defaultQuery(state);
}
/**
Query whether the search panel is open in the given editor state.
*/
function searchPanelOpen(state) {
var _a;
return ((_a = state.field(searchState, false)) === null || _a === void 0 ? void 0 : _a.panel) != null;
}
class SearchState {
constructor(query, panel) {
this.query = query;
@ -757,9 +819,9 @@ const searchHighlighter = view.ViewPlugin.fromClass(class {
let builder = new state.RangeSetBuilder();
for (let i = 0, ranges = view$1.visibleRanges, l = ranges.length; i < l; i++) {
let { from, to } = ranges[i];
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* HighlightMargin */)
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* RegExp.HighlightMargin */)
to = ranges[++i].to;
query.highlight(view$1.state.doc, from, to, (from, to) => {
query.highlight(view$1.state, from, to, (from, to) => {
let selected = view$1.state.selection.ranges.some(r => r.from == from && r.to == to);
builder.add(from, to, selected ? selectedMatchMark : matchMark);
});
@ -782,9 +844,9 @@ Will wrap around to the start of the document when it reaches the
end.
*/
const findNext = searchCommand((view, { query }) => {
let { from, to } = view.state.selection.main;
let next = query.nextMatch(view.state.doc, from, to);
if (!next || next.from == from && next.to == to)
let { to } = view.state.selection.main;
let next = query.nextMatch(view.state, to, to);
if (!next)
return false;
view.dispatch({
selection: { anchor: next.from, head: next.to },
@ -800,8 +862,8 @@ before the current main selection. Will wrap past the start
of the document to start searching at the end again.
*/
const findPrevious = searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
let range = query.prevMatch(state.doc, from, to);
let { state } = view, { from } = state.selection.main;
let range = query.prevMatch(state, from, from);
if (!range)
return false;
view.dispatch({
@ -816,7 +878,7 @@ const findPrevious = searchCommand((view, { query }) => {
Select all instances of the search query.
*/
const selectMatches = searchCommand((view, { query }) => {
let ranges = query.matchAll(view.state.doc, 1000);
let ranges = query.matchAll(view.state, 1000);
if (!ranges || !ranges.length)
return false;
view.dispatch({
@ -854,7 +916,7 @@ const replaceNext = searchCommand((view$1, { query }) => {
let { state } = view$1, { from, to } = state.selection.main;
if (state.readOnly)
return false;
let next = query.nextMatch(state.doc, from, from);
let next = query.nextMatch(state, from, from);
if (!next)
return false;
let changes = [], selection, replacement;
@ -862,7 +924,7 @@ const replaceNext = searchCommand((view$1, { query }) => {
if (next.from == from && next.to == to) {
replacement = state.toText(query.getReplacement(next));
changes.push({ from: next.from, to: next.to, insert: replacement });
next = query.nextMatch(state.doc, next.from, next.to);
next = query.nextMatch(state, next.from, next.to);
announce.push(view.EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
}
if (next) {
@ -885,7 +947,7 @@ replacement.
const replaceAll = searchCommand((view$1, { query }) => {
if (view$1.state.readOnly)
return false;
let changes = query.matchAll(view$1.state.doc, 1e9).map(match => {
let changes = query.matchAll(view$1.state, 1e9).map(match => {
let { from, to } = match;
return { from, to, insert: query.getReplacement(match) };
});
@ -903,11 +965,18 @@ function createSearchPanel(view) {
return view.state.facet(searchConfigFacet).createPanel(view);
}
function defaultQuery(state, fallback) {
var _a;
var _a, _b, _c, _d;
let sel = state.selection.main;
let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
let caseSensitive = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _a !== void 0 ? _a : state.facet(searchConfigFacet).caseSensitive;
return fallback && !selText ? fallback : new SearchQuery({ search: selText.replace(/\n/g, "\\n"), caseSensitive });
if (fallback && !selText)
return fallback;
let config = state.facet(searchConfigFacet);
return new SearchQuery({
search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
wholeWord: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _d !== void 0 ? _d : config.wholeWord
});
}
/**
Make sure the search panel is open and focused.
@ -977,6 +1046,7 @@ class SearchPanel {
"aria-label": phrase(view, "Find"),
class: "cm-textfield",
name: "search",
form: "",
"main-field": "true",
onchange: this.commit,
onkeyup: this.commit
@ -987,21 +1057,31 @@ class SearchPanel {
"aria-label": phrase(view, "Replace"),
class: "cm-textfield",
name: "replace",
form: "",
onchange: this.commit,
onkeyup: this.commit
});
this.caseField = elt__default["default"]("input", {
type: "checkbox",
name: "case",
form: "",
checked: query.caseSensitive,
onchange: this.commit
});
this.reField = elt__default["default"]("input", {
type: "checkbox",
name: "re",
form: "",
checked: query.regexp,
onchange: this.commit
});
this.wordField = elt__default["default"]("input", {
type: "checkbox",
name: "word",
form: "",
checked: query.wholeWord,
onchange: this.commit
});
function button(name, onclick, content) {
return elt__default["default"]("button", { class: "cm-button", name, onclick, type: "button" }, content);
}
@ -1012,18 +1092,19 @@ class SearchPanel {
button("select", () => selectMatches(view), [phrase(view, "all")]),
elt__default["default"]("label", null, [this.caseField, phrase(view, "match case")]),
elt__default["default"]("label", null, [this.reField, phrase(view, "regexp")]),
elt__default["default"]("label", null, [this.wordField, phrase(view, "by word")]),
...view.state.readOnly ? [] : [
elt__default["default"]("br"),
this.replaceField,
button("replace", () => replaceNext(view), [phrase(view, "replace")]),
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")]),
elt__default["default"]("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
],
elt__default["default"]("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]);
}
commit() {
@ -1031,7 +1112,8 @@ class SearchPanel {
search: this.searchField.value,
caseSensitive: this.caseField.checked,
regexp: this.reField.checked,
replace: this.replaceField.value
wholeWord: this.wordField.checked,
replace: this.replaceField.value,
});
if (!query.eq(this.query)) {
this.query = query;
@ -1064,6 +1146,7 @@ class SearchPanel {
this.replaceField.value = query.replace;
this.caseField.checked = query.caseSensitive;
this.reField.checked = query.regexp;
this.wordField.checked = query.wholeWord;
}
mount() {
this.searchField.select();
@ -1144,6 +1227,7 @@ exports.replaceAll = replaceAll;
exports.replaceNext = replaceNext;
exports.search = search;
exports.searchKeymap = searchKeymap;
exports.searchPanelOpen = searchPanelOpen;
exports.selectMatches = selectMatches;
exports.selectNextOccurrence = selectNextOccurrence;
exports.selectSelectionMatches = selectSelectionMatches;

View file

@ -10,6 +10,7 @@ declare class SearchCursor implements Iterator<{
from: number;
to: number;
}> {
private test?;
private iter;
/**
The current match (only holds a meaningful value after
@ -43,7 +44,7 @@ declare class SearchCursor implements Iterator<{
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text: Text, query: string, from?: number, to?: number, normalize?: (string: string) => string);
constructor(text: Text, query: string, from?: number, to?: number, normalize?: (string: string) => string, test?: ((from: number, to: number, buffer: string, bufferPos: number) => boolean) | undefined);
private peek;
/**
Look for the next match. Updates the iterator's
@ -65,6 +66,10 @@ declare class SearchCursor implements Iterator<{
}>;
}
interface RegExpCursorOptions {
ignoreCase?: boolean;
test?: (from: number, to: number, match: RegExpExecArray) => boolean;
}
/**
This class is similar to [`SearchCursor`](https://codemirror.net/6/docs/ref/#search.SearchCursor)
but searches for a regular expression pattern instead of a plain
@ -75,9 +80,11 @@ declare class RegExpCursor implements Iterator<{
to: number;
match: RegExpExecArray;
}> {
private text;
private to;
private iter;
private re;
private test?;
private curLine;
private curLineStart;
private matchPos;
@ -101,9 +108,7 @@ declare class RegExpCursor implements Iterator<{
document. `query` should be the raw pattern (as you'd pass it to
`new RegExp`).
*/
constructor(text: Text, query: string, options?: {
ignoreCase?: boolean;
}, from?: number, to?: number);
constructor(text: Text, query: string, options?: RegExpCursorOptions, from?: number, to?: number);
private getLine;
private nextLine;
/**
@ -177,6 +182,15 @@ interface SearchConfig {
*/
caseSensitive?: boolean;
/**
Whether to treat string searches literally by default (defaults to false).
*/
literal?: boolean;
/**
Controls whether the default query has by-word matching enabled.
Defaults to false.
*/
wholeWord?: boolean;
/**
Can be used to override the way the search panel is implemented.
Should create a [Panel](https://codemirror.net/6/docs/ref/#view.Panel) that contains a form
which lets the user:
@ -188,6 +202,9 @@ interface SearchConfig {
- Notice external changes to the query by reacting to the
appropriate [state effect](https://codemirror.net/6/docs/ref/#search.setSearchQuery).
- Run some of the search commands.
The field that should be focused when opening the panel must be
tagged with a `main-field=true` DOM attribute.
*/
createPanel?: (view: EditorView) => Panel;
}
@ -211,7 +228,13 @@ declare class SearchQuery {
*/
readonly caseSensitive: boolean;
/**
Then true, the search string is interpreted as a regular
By default, string search will replace `\n`, `\r`, and `\t` in
the query with newline, return, and tab characters. When this
is set to true, that behavior is disabled.
*/
readonly literal: boolean;
/**
When true, the search string is interpreted as a regular
expression.
*/
readonly regexp: boolean;
@ -226,6 +249,11 @@ declare class SearchQuery {
*/
readonly valid: boolean;
/**
When true, matches that contain words are ignored when there are
further word characters around them.
*/
readonly wholeWord: boolean;
/**
Create a query object.
*/
constructor(config: {
@ -251,6 +279,10 @@ declare class SearchQuery {
The replace text.
*/
replace?: string;
/**
Enable whole-word matching.
*/
wholeWord?: boolean;
});
/**
Compare this query to another query.
@ -258,9 +290,9 @@ declare class SearchQuery {
eq(other: SearchQuery): boolean;
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc: Text, from?: number, to?: number): Iterator<{
getCursor(state: EditorState | Text, from?: number, to?: number): Iterator<{
from: number;
to: number;
}>;
@ -278,6 +310,10 @@ Get the current search query from an editor state.
*/
declare function getSearchQuery(state: EditorState): SearchQuery;
/**
Query whether the search panel is open in the given editor state.
*/
declare function searchPanelOpen(state: EditorState): boolean;
/**
Open the search panel if it isn't already open, and move the
selection to the first match after the current main selection.
Will wrap around to the start of the document when it reaches the
@ -326,4 +362,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };

View file

@ -1,5 +1,5 @@
import { showPanel, EditorView, getPanel, Decoration, ViewPlugin, runScopeHandlers } from '@codemirror/view';
import { codePointAt, fromCodePoint, codePointSize, StateEffect, StateField, EditorSelection, Facet, combineConfig, CharCategory, RangeSetBuilder, Prec } from '@codemirror/state';
import { codePointAt, fromCodePoint, codePointSize, StateEffect, StateField, EditorSelection, Facet, combineConfig, CharCategory, RangeSetBuilder, Prec, EditorState, findClusterBreak } from '@codemirror/state';
import elt from 'crelt';
const basicNormalize = typeof String.prototype.normalize == "function"
@ -22,7 +22,8 @@ class SearchCursor {
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text, query, from = 0, to = text.length, normalize) {
constructor(text, query, from = 0, to = text.length, normalize, test) {
this.test = test;
/**
The current match (only holds a meaningful value after
[`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when
@ -116,6 +117,8 @@ class SearchCursor {
else
this.matches.push(1, pos);
}
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferPos))
match = null;
return match;
}
}
@ -136,6 +139,7 @@ class RegExpCursor {
`new RegExp`).
*/
constructor(text, query, options, from = 0, to = text.length) {
this.text = text;
this.to = to;
this.curLine = "";
/**
@ -152,10 +156,11 @@ class RegExpCursor {
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
return new MultilineRegExpCursor(text, query, options, from, to);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.getLine(this.curLineStart);
}
getLine(skip) {
@ -186,10 +191,10 @@ class RegExpCursor {
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
if (match) {
let from = this.curLineStart + match.index, to = from + match[0].length;
this.matchPos = to + (from == to ? 1 : 0);
if (from == this.curLine.length)
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
if (from == this.curLineStart + this.curLine.length)
this.nextLine();
if (from < to || from > this.value.to) {
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
return this;
}
@ -240,9 +245,10 @@ class MultilineRegExpCursor {
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Base */));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
@ -256,24 +262,23 @@ class MultilineRegExpCursor {
this.re.lastIndex = off + 1;
match = this.re.exec(this.flat.text);
}
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if (match && this.flat.to < this.to && match.index + match[0].length > this.flat.text.length - 10)
match = null;
if (match) {
let from = this.flat.from + match.index, to = from + match[0].length;
this.value = { from, to, match };
this.matchPos = to + (from == to ? 1 : 0);
return this;
}
else {
if (this.flat.to == this.to) {
this.done = true;
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&
(!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
if (this.flat.to == this.to) {
this.done = true;
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
}
}
@ -290,6 +295,14 @@ function validRegExp(source) {
return false;
}
}
function toCharEnd(text, pos) {
if (pos >= text.length)
return pos;
let line = text.lineAt(pos), next;
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)
pos++;
return pos;
}
function createLineDialog(view) {
let input = elt("input", { class: "cm-textfield", name: "line" });
@ -541,12 +554,13 @@ const selectNextOccurrence = ({ state, dispatch }) => {
const searchConfigFacet = /*@__PURE__*/Facet.define({
combine(configs) {
var _a;
return {
top: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.top, undefined) || false,
caseSensitive: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.caseSensitive, undefined) || false,
createPanel: ((_a = configs.find(c => c.createPanel)) === null || _a === void 0 ? void 0 : _a.createPanel) || (view => new SearchPanel(view))
};
return combineConfig(configs, {
top: false,
caseSensitive: false,
literal: false,
wholeWord: false,
createPanel: view => new SearchPanel(view)
});
}
});
/**
@ -568,17 +582,27 @@ class SearchQuery {
constructor(config) {
this.search = config.search;
this.caseSensitive = !!config.caseSensitive;
this.literal = !!config.literal;
this.regexp = !!config.regexp;
this.replace = config.replace || "";
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = config.literal ? this.search : this.search.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
}
/**
@internal
*/
unquote(text) {
return this.literal ? text :
text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
/**
Compare this query to another query.
*/
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp;
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
this.wholeWord == other.wholeWord;
}
/**
@internal
@ -588,10 +612,13 @@ class SearchQuery {
}
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc, from = 0, to = doc.length) {
return this.regexp ? regexpCursor(this, doc, from, to) : stringCursor(this, doc, from, to);
getCursor(state, from = 0, to) {
let st = state.doc ? state : EditorState.create({ doc: state });
if (to == null)
to = st.doc.length;
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
}
}
class QueryType {
@ -599,41 +626,53 @@ class QueryType {
this.spec = spec;
}
}
function stringCursor(spec, doc, from, to) {
return new SearchCursor(doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase());
function stringCursor(spec, state, from, to) {
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word ||
categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) &&
(categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word ||
categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);
};
}
class StringQuery extends QueryType {
constructor(spec) {
super(spec);
}
nextMatch(doc, curFrom, curTo) {
let cursor = stringCursor(this.spec, doc, curTo, doc.length).nextOverlapping();
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done)
cursor = stringCursor(this.spec, doc, 0, curFrom).nextOverlapping();
cursor = stringCursor(this.spec, state, 0, curFrom).nextOverlapping();
return cursor.done ? null : cursor.value;
}
// Searching in reverse is, rather than implementing inverted search
// cursor, done by scanning chunk after chunk forward.
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, doc, start, pos), range = null;
let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, state, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* ChunkSize */;
pos -= 10000 /* FindPrev.ChunkSize */;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(_result) { return this.spec.replace; }
matchAll(doc, limit) {
let cursor = stringCursor(this.spec, doc, 0, doc.length), ranges = [];
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -641,26 +680,42 @@ class StringQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = stringCursor(this.spec, doc, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, doc.length));
highlight(state, from, to, add) {
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
function regexpCursor(spec, doc, from, to) {
return new RegExpCursor(doc, spec.search, spec.caseSensitive ? undefined : { ignoreCase: true }, from, to);
function regexpCursor(spec, state, from, to) {
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
}, from, to);
}
function charBefore(str, index) {
return str.slice(findClusterBreak(str, index, false), index);
}
function charAfter(str, index) {
return str.slice(index, findClusterBreak(str, index));
}
function regexpWordTest(categorizer) {
return (_from, _to, match) => !match[0].length ||
(categorizer(charBefore(match.input, match.index)) != CharCategory.Word ||
categorizer(charAfter(match.input, match.index)) != CharCategory.Word) &&
(categorizer(charAfter(match.input, match.index + match[0].length)) != CharCategory.Word ||
categorizer(charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);
}
class RegExpQuery extends QueryType {
nextMatch(doc, curFrom, curTo) {
let cursor = regexpCursor(this.spec, doc, curTo, doc.length).next();
nextMatch(state, curFrom, curTo) {
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
if (cursor.done)
cursor = regexpCursor(this.spec, doc, 0, curFrom).next();
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* ChunkSize */);
let cursor = regexpCursor(this.spec, doc, start, to), range = null;
let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);
let cursor = regexpCursor(this.spec, state, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
@ -669,18 +724,18 @@ class RegExpQuery extends QueryType {
return null;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
return this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
return this.spec.unquote(this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
: i == "&" ? result.match[0]
: i != "0" && +i < result.match.length ? result.match[i]
: m);
: m));
}
matchAll(doc, limit) {
let cursor = regexpCursor(this.spec, doc, 0, doc.length), ranges = [];
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -688,8 +743,8 @@ class RegExpQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = regexpCursor(this.spec, doc, Math.max(0, from - 250 /* HighlightMargin */), Math.min(to + 250 /* HighlightMargin */, doc.length));
highlight(state, from, to, add) {
let cursor = regexpCursor(this.spec, state, Math.max(0, from - 250 /* RegExp.HighlightMargin */), Math.min(to + 250 /* RegExp.HighlightMargin */, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
@ -725,6 +780,13 @@ function getSearchQuery(state) {
let curState = state.field(searchState, false);
return curState ? curState.query.spec : defaultQuery(state);
}
/**
Query whether the search panel is open in the given editor state.
*/
function searchPanelOpen(state) {
var _a;
return ((_a = state.field(searchState, false)) === null || _a === void 0 ? void 0 : _a.panel) != null;
}
class SearchState {
constructor(query, panel) {
this.query = query;
@ -749,9 +811,9 @@ const searchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
let builder = new RangeSetBuilder();
for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) {
let { from, to } = ranges[i];
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* HighlightMargin */)
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* RegExp.HighlightMargin */)
to = ranges[++i].to;
query.highlight(view.state.doc, from, to, (from, to) => {
query.highlight(view.state, from, to, (from, to) => {
let selected = view.state.selection.ranges.some(r => r.from == from && r.to == to);
builder.add(from, to, selected ? selectedMatchMark : matchMark);
});
@ -774,9 +836,9 @@ Will wrap around to the start of the document when it reaches the
end.
*/
const findNext = /*@__PURE__*/searchCommand((view, { query }) => {
let { from, to } = view.state.selection.main;
let next = query.nextMatch(view.state.doc, from, to);
if (!next || next.from == from && next.to == to)
let { to } = view.state.selection.main;
let next = query.nextMatch(view.state, to, to);
if (!next)
return false;
view.dispatch({
selection: { anchor: next.from, head: next.to },
@ -792,8 +854,8 @@ before the current main selection. Will wrap past the start
of the document to start searching at the end again.
*/
const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
let range = query.prevMatch(state.doc, from, to);
let { state } = view, { from } = state.selection.main;
let range = query.prevMatch(state, from, from);
if (!range)
return false;
view.dispatch({
@ -808,7 +870,7 @@ const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => {
Select all instances of the search query.
*/
const selectMatches = /*@__PURE__*/searchCommand((view, { query }) => {
let ranges = query.matchAll(view.state.doc, 1000);
let ranges = query.matchAll(view.state, 1000);
if (!ranges || !ranges.length)
return false;
view.dispatch({
@ -846,7 +908,7 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
if (state.readOnly)
return false;
let next = query.nextMatch(state.doc, from, from);
let next = query.nextMatch(state, from, from);
if (!next)
return false;
let changes = [], selection, replacement;
@ -854,7 +916,7 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
if (next.from == from && next.to == to) {
replacement = state.toText(query.getReplacement(next));
changes.push({ from: next.from, to: next.to, insert: replacement });
next = query.nextMatch(state.doc, next.from, next.to);
next = query.nextMatch(state, next.from, next.to);
announce.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
}
if (next) {
@ -877,7 +939,7 @@ replacement.
const replaceAll = /*@__PURE__*/searchCommand((view, { query }) => {
if (view.state.readOnly)
return false;
let changes = query.matchAll(view.state.doc, 1e9).map(match => {
let changes = query.matchAll(view.state, 1e9).map(match => {
let { from, to } = match;
return { from, to, insert: query.getReplacement(match) };
});
@ -895,11 +957,18 @@ function createSearchPanel(view) {
return view.state.facet(searchConfigFacet).createPanel(view);
}
function defaultQuery(state, fallback) {
var _a;
var _a, _b, _c, _d;
let sel = state.selection.main;
let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
let caseSensitive = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _a !== void 0 ? _a : state.facet(searchConfigFacet).caseSensitive;
return fallback && !selText ? fallback : new SearchQuery({ search: selText.replace(/\n/g, "\\n"), caseSensitive });
if (fallback && !selText)
return fallback;
let config = state.facet(searchConfigFacet);
return new SearchQuery({
search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
wholeWord: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _d !== void 0 ? _d : config.wholeWord
});
}
/**
Make sure the search panel is open and focused.
@ -969,6 +1038,7 @@ class SearchPanel {
"aria-label": phrase(view, "Find"),
class: "cm-textfield",
name: "search",
form: "",
"main-field": "true",
onchange: this.commit,
onkeyup: this.commit
@ -979,21 +1049,31 @@ class SearchPanel {
"aria-label": phrase(view, "Replace"),
class: "cm-textfield",
name: "replace",
form: "",
onchange: this.commit,
onkeyup: this.commit
});
this.caseField = elt("input", {
type: "checkbox",
name: "case",
form: "",
checked: query.caseSensitive,
onchange: this.commit
});
this.reField = elt("input", {
type: "checkbox",
name: "re",
form: "",
checked: query.regexp,
onchange: this.commit
});
this.wordField = elt("input", {
type: "checkbox",
name: "word",
form: "",
checked: query.wholeWord,
onchange: this.commit
});
function button(name, onclick, content) {
return elt("button", { class: "cm-button", name, onclick, type: "button" }, content);
}
@ -1004,18 +1084,19 @@ class SearchPanel {
button("select", () => selectMatches(view), [phrase(view, "all")]),
elt("label", null, [this.caseField, phrase(view, "match case")]),
elt("label", null, [this.reField, phrase(view, "regexp")]),
elt("label", null, [this.wordField, phrase(view, "by word")]),
...view.state.readOnly ? [] : [
elt("br"),
this.replaceField,
button("replace", () => replaceNext(view), [phrase(view, "replace")]),
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")]),
elt("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
],
elt("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]);
}
commit() {
@ -1023,7 +1104,8 @@ class SearchPanel {
search: this.searchField.value,
caseSensitive: this.caseField.checked,
regexp: this.reField.checked,
replace: this.replaceField.value
wholeWord: this.wordField.checked,
replace: this.replaceField.value,
});
if (!query.eq(this.query)) {
this.query = query;
@ -1056,6 +1138,7 @@ class SearchPanel {
this.replaceField.value = query.replace;
this.caseField.checked = query.caseSensitive;
this.reField.checked = query.regexp;
this.wordField.checked = query.wholeWord;
}
mount() {
this.searchField.select();
@ -1122,4 +1205,4 @@ const searchExtensions = [
baseTheme
];
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/search",
"version": "6.0.0",
"version": "6.2.3",
"description": "Search functionality for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,51 @@
## 6.2.0 (2022-12-26)
### New features
`EditorSelection.range` now accepts an optional 4th argument to specify the bidi level of the range's head position.
## 6.1.4 (2022-11-15)
### Bug fixes
Fix a bug that caused the `openStart` value passed to span iterators to be incorrect around widgets in some circumstances.
## 6.1.3 (2022-11-10)
### Bug fixes
Avoid unnecessary calls to computed facet getters when a state is reconfigured but no dependencies of the computed facet change.
Fix an infinite loop in `RangeSet.eq` when the `to` parameter isn't given.
## 6.1.2 (2022-09-21)
### Bug fixes
Fix an issue where, when multiple transaction extenders took effect, only the highest-precedence one was actually included in the transaction.
## 6.1.1 (2022-08-03)
### Bug fixes
Fix a bug in range set span iteration that would cause decorations to be inappropriately split in some situations.
## 6.1.0 (2022-06-30)
### Bug fixes
Refine change mapping to preserve insertions made by concurrent changes.
### New features
The `enables` option to `Facet.define` may now take a function, which will be called with the facet value to create the extensions.
## 6.0.1 (2022-06-17)
### Bug fixes
Fix a problem that caused effects' `map` methods to be called with an incorrect change set when filtering changes.
## 6.0.0 (2022-06-08)
### Breaking changes

View file

@ -1,13 +1,13 @@
# @codemirror/state [![NPM version](https://img.shields.io/npm/v/@codemirror/state.svg)](https://www.npmjs.org/package/@codemirror/state)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#state) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/state/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#state) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/state/blob/main/CHANGELOG.md) ]
This package implements the editor state data structures for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/state/tree/main/LICENSE).

View file

@ -31,10 +31,10 @@ class Text {
*/
replace(from, to, text) {
let parts = [];
this.decompose(0, from, parts, 2 /* To */);
this.decompose(0, from, parts, 2 /* Open.To */);
if (text.length)
text.decompose(0, text.length, parts, 1 /* From */ | 2 /* To */);
this.decompose(to, this.length, parts, 1 /* From */);
text.decompose(0, text.length, parts, 1 /* Open.From */ | 2 /* Open.To */);
this.decompose(to, this.length, parts, 1 /* Open.From */);
return TextNode.from(parts, this.length - (to - from) + text.length);
}
/**
@ -124,7 +124,7 @@ class Text {
throw new RangeError("A document must have at least one line");
if (text.length == 1 && !text[0])
return Text.empty;
return text.length <= 32 /* Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
return text.length <= 32 /* Tree.Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
}
}
// Leaves store an array of line strings. There are always line breaks
@ -150,10 +150,10 @@ class TextLeaf extends Text {
decompose(from, to, target, open) {
let text = from <= 0 && to >= this.length ? this
: new TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from));
if (open & 1 /* From */) {
if (open & 1 /* Open.From */) {
let prev = target.pop();
let joined = appendText(text.text, prev.text.slice(), 0, text.length);
if (joined.length <= 32 /* Branch */) {
if (joined.length <= 32 /* Tree.Branch */) {
target.push(new TextLeaf(joined, prev.length + text.length));
}
else {
@ -170,7 +170,7 @@ class TextLeaf extends Text {
return super.replace(from, to, text);
let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to);
let newLen = this.length + text.length - (to - from);
if (lines.length <= 32 /* Branch */)
if (lines.length <= 32 /* Tree.Branch */)
return new TextLeaf(lines, newLen);
return TextNode.from(TextLeaf.split(lines, []), newLen);
}
@ -196,7 +196,7 @@ class TextLeaf extends Text {
for (let line of text) {
part.push(line);
len += line.length + 1;
if (part.length == 32 /* Branch */) {
if (part.length == 32 /* Tree.Branch */) {
target.push(new TextLeaf(part, len));
part = [];
len = -1;
@ -233,7 +233,7 @@ class TextNode extends Text {
for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) {
let child = this.children[i], end = pos + child.length;
if (from <= end && to >= pos) {
let childOpen = open & ((pos <= from ? 1 /* From */ : 0) | (end >= to ? 2 /* To */ : 0));
let childOpen = open & ((pos <= from ? 1 /* Open.From */ : 0) | (end >= to ? 2 /* Open.To */ : 0));
if (pos >= from && end <= to && !childOpen)
target.push(child);
else
@ -252,8 +252,8 @@ class TextNode extends Text {
if (from >= pos && to <= end) {
let updated = child.replace(from - pos, to - pos, text);
let totalLines = this.lines - child.lines + updated.lines;
if (updated.lines < (totalLines >> (5 /* BranchShift */ - 1)) &&
updated.lines > (totalLines >> (5 /* BranchShift */ + 1))) {
if (updated.lines < (totalLines >> (5 /* Tree.BranchShift */ - 1)) &&
updated.lines > (totalLines >> (5 /* Tree.BranchShift */ + 1))) {
let copy = this.children.slice();
copy[i] = updated;
return new TextNode(copy, this.length - (to - from) + text.length);
@ -299,13 +299,13 @@ class TextNode extends Text {
let lines = 0;
for (let ch of children)
lines += ch.lines;
if (lines < 32 /* Branch */) {
if (lines < 32 /* Tree.Branch */) {
let flat = [];
for (let ch of children)
ch.flatten(flat);
return new TextLeaf(flat, length);
}
let chunk = Math.max(32 /* Branch */, lines >> 5 /* BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1;
let chunk = Math.max(32 /* Tree.Branch */, lines >> 5 /* Tree.BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1;
let chunked = [], currentLines = 0, currentLen = -1, currentChunk = [];
function add(child) {
let last;
@ -319,7 +319,7 @@ class TextNode extends Text {
}
else if (child instanceof TextLeaf && currentLines &&
(last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf &&
child.lines + last.lines <= 32 /* Branch */) {
child.lines + last.lines <= 32 /* Tree.Branch */) {
currentLines += child.lines;
currentLen += child.length + 1;
currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length);
@ -1130,51 +1130,65 @@ function iterChanges(desc, f, individual) {
}
}
function mapSet(setA, setB, before, mkSet = false) {
// Produce a copy of setA that applies to the document after setB
// has been applied (assuming both start at the same document).
let sections = [], insert = mkSet ? [] : null;
let a = new SectionIter(setA), b = new SectionIter(setB);
for (let posA = 0, posB = 0;;) {
if (a.ins == -1) {
posA += a.len;
a.next();
// Iterate over both sets in parallel. inserted tracks, for changes
// in A that have to be processed piece-by-piece, whether their
// content has been inserted already, and refers to the section
// index.
for (let inserted = -1;;) {
if (a.ins == -1 && b.ins == -1) {
// Move across ranges skipped by both sets.
let len = Math.min(a.len, b.len);
addSection(sections, len, -1);
a.forward(len);
b.forward(len);
}
else if (b.ins == -1 && posB < posA) {
let skip = Math.min(b.len, posA - posB);
b.forward(skip);
addSection(sections, skip, -1);
posB += skip;
}
else if (b.ins >= 0 && (a.done || posB < posA || posB == posA && (b.len < a.len || b.len == a.len && !before))) {
else if (b.ins >= 0 && (a.ins < 0 || inserted == a.i || a.off == 0 && (b.len < a.len || b.len == a.len && !before))) {
// If there's a change in B that comes before the next change in
// A (ordered by start pos, then len, then before flag), skip
// that (and process any changes in A it covers).
let len = b.len;
addSection(sections, b.ins, -1);
while (posA > posB && !a.done && posA + a.len < posB + b.len) {
posA += a.len;
a.next();
while (len) {
let piece = Math.min(a.len, len);
if (a.ins >= 0 && inserted < a.i && a.len <= piece) {
addSection(sections, 0, a.ins);
if (insert)
addInsert(insert, sections, a.text);
inserted = a.i;
}
a.forward(piece);
len -= piece;
}
posB += b.len;
b.next();
}
else if (a.ins >= 0) {
let len = 0, end = posA + a.len;
for (;;) {
if (b.ins >= 0 && posB > posA && posB + b.len < end) {
len += b.ins;
posB += b.len;
b.next();
// Process the part of a change in A up to the start of the next
// non-deletion change in B (if overlapping).
let len = 0, left = a.len;
while (left) {
if (b.ins == -1) {
let piece = Math.min(left, b.len);
len += piece;
left -= piece;
b.forward(piece);
}
else if (b.ins == -1 && posB < end) {
let skip = Math.min(b.len, end - posB);
len += skip;
b.forward(skip);
posB += skip;
else if (b.ins == 0 && b.len < left) {
left -= b.len;
b.next();
}
else {
break;
}
}
addSection(sections, len, a.ins);
if (insert)
addSection(sections, len, inserted < a.i ? a.ins : 0);
if (insert && inserted < a.i)
addInsert(insert, sections, a.text);
posA = end;
a.next();
inserted = a.i;
a.forward(a.len - left);
}
else if (a.done && b.done) {
return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections);
@ -1302,12 +1316,12 @@ class SelectionRange {
The anchor of the rangethe side that doesn't move when you
extend it.
*/
get anchor() { return this.flags & 16 /* Inverted */ ? this.to : this.from; }
get anchor() { return this.flags & 16 /* RangeFlag.Inverted */ ? this.to : this.from; }
/**
The head of the range, which is moved when the range is
[extended](https://codemirror.net/6/docs/ref/#state.SelectionRange.extend).
*/
get head() { return this.flags & 16 /* Inverted */ ? this.from : this.to; }
get head() { return this.flags & 16 /* RangeFlag.Inverted */ ? this.from : this.to; }
/**
True when `anchor` and `head` are at the same position.
*/
@ -1318,13 +1332,13 @@ class SelectionRange {
the character before its position, 1 the character after, and 0
means no association.
*/
get assoc() { return this.flags & 4 /* AssocBefore */ ? -1 : this.flags & 8 /* AssocAfter */ ? 1 : 0; }
get assoc() { return this.flags & 4 /* RangeFlag.AssocBefore */ ? -1 : this.flags & 8 /* RangeFlag.AssocAfter */ ? 1 : 0; }
/**
The bidirectional text level associated with this cursor, if
any.
*/
get bidiLevel() {
let level = this.flags & 3 /* BidiLevelMask */;
let level = this.flags & 3 /* RangeFlag.BidiLevelMask */;
return level == 3 ? null : level;
}
/**
@ -1334,8 +1348,8 @@ class SelectionRange {
lines of different length.
*/
get goalColumn() {
let value = this.flags >> 5 /* GoalColumnOffset */;
return value == 33554431 /* NoGoalColumn */ ? undefined : value;
let value = this.flags >> 5 /* RangeFlag.GoalColumnOffset */;
return value == 33554431 /* RangeFlag.NoGoalColumn */ ? undefined : value;
}
/**
Map this range through a change, producing a valid range in the
@ -1495,17 +1509,18 @@ class EditorSelection {
safely ignore the optional arguments in most situations.
*/
static cursor(pos, assoc = 0, bidiLevel, goalColumn) {
return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* AssocBefore */ : 8 /* AssocAfter */) |
return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* RangeFlag.AssocBefore */ : 8 /* RangeFlag.AssocAfter */) |
(bidiLevel == null ? 3 : Math.min(2, bidiLevel)) |
((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */));
((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */));
}
/**
Create a selection range.
*/
static range(anchor, head, goalColumn) {
let goal = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */;
return head < anchor ? SelectionRange.create(head, anchor, 16 /* Inverted */ | goal | 8 /* AssocAfter */)
: SelectionRange.create(anchor, head, goal | (head > anchor ? 4 /* AssocBefore */ : 0));
static range(anchor, head, goalColumn, bidiLevel) {
let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */) |
(bidiLevel == null ? 3 : Math.min(2, bidiLevel));
return head < anchor ? SelectionRange.create(head, anchor, 16 /* RangeFlag.Inverted */ | 8 /* RangeFlag.AssocAfter */ | flags)
: SelectionRange.create(anchor, head, (head > anchor ? 4 /* RangeFlag.AssocBefore */ : 0) | flags);
}
/**
@internal
@ -1556,21 +1571,17 @@ class Facet {
/**
@internal
*/
compare, isStatic,
/**
@internal
*/
extensions) {
compare, isStatic, enables) {
this.combine = combine;
this.compareInput = compareInput;
this.compare = compare;
this.isStatic = isStatic;
this.extensions = extensions;
/**
@internal
*/
this.id = nextID++;
this.default = combine([]);
this.extensions = typeof enables == "function" ? enables(this) : enables;
}
/**
Define a new facet.
@ -1582,7 +1593,7 @@ class Facet {
Returns an extension that adds the given value to this facet.
*/
of(value) {
return new FacetProvider([], this, 0 /* Static */, value);
return new FacetProvider([], this, 0 /* Provider.Static */, value);
}
/**
Create an extension that computes a value for the facet from a
@ -1596,7 +1607,7 @@ class Facet {
compute(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 1 /* Single */, get);
return new FacetProvider(deps, this, 1 /* Provider.Single */, get);
}
/**
Create an extension that computes zero or more values for this
@ -1605,7 +1616,7 @@ class Facet {
computeN(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 2 /* Multi */, get);
return new FacetProvider(deps, this, 2 /* Provider.Multi */, get);
}
from(field, get) {
if (!get)
@ -1628,7 +1639,7 @@ class FacetProvider {
var _a;
let getter = this.value;
let compare = this.facet.compareInput;
let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Multi */;
let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Provider.Multi */;
let depDoc = false, depSel = false, depAddrs = [];
for (let dep of this.dependencies) {
if (dep == "doc")
@ -1641,33 +1652,35 @@ class FacetProvider {
return {
create(state) {
state.values[idx] = getter(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update(state, tr) {
if ((depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || ensureAll(state, depAddrs)) {
let newVal = getter(state);
if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) {
state.values[idx] = newVal;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
}
return 0;
},
reconfigure: (state, oldState) => {
let newVal = getter(state);
let oldAddr = oldState.config.address[id];
let newVal, oldAddr = oldState.config.address[id];
if (oldAddr != null) {
let oldVal = getAddr(oldState, oldAddr);
if (this.dependencies.every(dep => {
return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) :
dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true;
}) || (multi ? compareArray(newVal, oldVal, compare) : compare(newVal, oldVal))) {
}) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) {
state.values[idx] = oldVal;
return 0;
}
}
else {
newVal = getter(state);
}
state.values[idx] = newVal;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1683,7 +1696,7 @@ function compareArray(a, b, compare) {
function ensureAll(state, addrs) {
let changed = false;
for (let addr of addrs)
if (ensureAddr(state, addr) & 1 /* Changed */)
if (ensureAddr(state, addr) & 1 /* SlotStatus.Changed */)
changed = true;
return changed;
}
@ -1696,7 +1709,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
let values = [];
for (let i = 0; i < providerAddrs.length; i++) {
let value = getAddr(state, providerAddrs[i]);
if (providerTypes[i] == 2 /* Multi */)
if (providerTypes[i] == 2 /* Provider.Multi */)
for (let val of value)
values.push(val);
else
@ -1709,7 +1722,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
for (let addr of providerAddrs)
ensureAddr(state, addr);
state.values[idx] = get(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update(state, tr) {
if (!ensureAll(state, dynamic))
@ -1718,7 +1731,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
if (facet.compare(value, state.values[idx]))
return 0;
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
reconfigure(state, oldState) {
let depChanged = ensureAll(state, providerAddrs);
@ -1733,7 +1746,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
return 0;
}
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1783,7 +1796,7 @@ class StateField {
return {
create: (state) => {
state.values[idx] = this.create(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update: (state, tr) => {
let oldVal = state.values[idx];
@ -1791,7 +1804,7 @@ class StateField {
if (this.compareF(oldVal, value))
return 0;
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
reconfigure: (state, oldState) => {
if (oldState.config.address[this.id] != null) {
@ -1799,7 +1812,7 @@ class StateField {
return 0;
}
state.values[idx] = this.create(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1908,7 +1921,7 @@ class Configuration {
this.facets = facets;
this.statusTemplate = [];
while (this.statusTemplate.length < dynamicSlots.length)
this.statusTemplate.push(0 /* Unresolved */);
this.statusTemplate.push(0 /* SlotStatus.Unresolved */);
}
staticFacet(facet) {
let addr = this.address[facet.id];
@ -1935,7 +1948,7 @@ class Configuration {
for (let id in facets) {
let providers = facets[id], facet = providers[0].facet;
let oldProviders = oldFacets && oldFacets[id] || [];
if (providers.every(p => p.type == 0 /* Static */)) {
if (providers.every(p => p.type == 0 /* Provider.Static */)) {
address[facet.id] = (staticValues.length << 1) | 1;
if (sameArray(oldProviders, providers)) {
staticValues.push(oldState.facet(facet));
@ -1947,7 +1960,7 @@ class Configuration {
}
else {
for (let p of providers) {
if (p.type == 0 /* Static */) {
if (p.type == 0 /* Provider.Static */) {
address[p.id] = (staticValues.length << 1) | 1;
staticValues.push(p.value);
}
@ -2001,7 +2014,7 @@ function flatten(extension, compartments, newCompartments) {
else if (ext instanceof FacetProvider) {
result[prec].push(ext);
if (ext.facet.extensions)
inner(ext.facet.extensions, prec);
inner(ext.facet.extensions, Prec_.default);
}
else {
let content = ext.extension;
@ -2015,16 +2028,16 @@ function flatten(extension, compartments, newCompartments) {
}
function ensureAddr(state, addr) {
if (addr & 1)
return 2 /* Computed */;
return 2 /* SlotStatus.Computed */;
let idx = addr >> 1;
let status = state.status[idx];
if (status == 4 /* Computing */)
if (status == 4 /* SlotStatus.Computing */)
throw new Error("Cyclic dependency between fields and/or facets");
if (status & 2 /* Computed */)
if (status & 2 /* SlotStatus.Computed */)
return status;
state.status[idx] = 4 /* Computing */;
state.status[idx] = 4 /* SlotStatus.Computing */;
let changed = state.computeSlot(state, state.config.dynamicSlots[idx]);
return state.status[idx] = 2 /* Computed */ | changed;
return state.status[idx] = 2 /* SlotStatus.Computed */ | changed;
}
function getAddr(state, addr) {
return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1];
@ -2439,7 +2452,7 @@ function filterTransaction(tr) {
else {
let filtered = tr.changes.filter(result);
changes = filtered.changes;
back = filtered.filtered.invertedDesc;
back = filtered.filtered.mapDesc(filtered.changes).invertedDesc;
}
tr = Transaction.create(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.scrollIntoView);
}
@ -2461,7 +2474,7 @@ function extendTransaction(tr) {
for (let i = extenders.length - 1; i >= 0; i--) {
let extension = extenders[i](tr);
if (extension && Object.keys(extension).length)
spec = mergeTransaction(tr, resolveTransactionInner(state, extension, tr.changes.newLength), true);
spec = mergeTransaction(spec, resolveTransactionInner(state, extension, tr.changes.newLength), true);
}
return spec == tr ? tr : Transaction.create(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.scrollIntoView);
}
@ -2712,7 +2725,7 @@ class EditorState {
if (fields)
for (let prop in fields) {
let value = fields[prop];
if (value instanceof StateField)
if (value instanceof StateField && this.config.address[value.id] != null)
result[prop] = value.spec.toJSON(this.field(fields[prop]), this);
}
return result;
@ -2729,8 +2742,10 @@ class EditorState {
let fieldInit = [];
if (fields)
for (let prop in fields) {
let field = fields[prop], value = json[prop];
fieldInit.push(field.init(state => field.spec.fromJSON(value, state)));
if (Object.prototype.hasOwnProperty.call(json, prop)) {
let field = fields[prop], value = json[prop];
fieldInit.push(field.init(state => field.spec.fromJSON(value, state)));
}
}
return EditorState.create({
doc: json.doc,
@ -2791,13 +2806,25 @@ class EditorState {
if (i == "$")
return "$";
let n = +(i || 1);
return n > insert.length ? m : insert[n - 1];
return !n || n > insert.length ? m : insert[n - 1];
});
return phrase;
}
/**
Find the values for a given language data field, provided by the
the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet.
Examples of language data fields are...
- [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying
comment syntax.
- [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override)
for providing language-specific completion sources.
- [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding
characters that should be considered part of words in this
language.
- [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls
bracket closing behavior.
*/
languageDataAt(name, pos, side = -1) {
let values = [];
@ -3069,7 +3096,7 @@ class Chunk {
}
}
between(offset, from, to, f) {
for (let i = this.findIndex(from, -1000000000 /* Far */, true), e = this.findIndex(to, 1000000000 /* Far */, false, i); i < e; i++)
for (let i = this.findIndex(from, -1000000000 /* C.Far */, true), e = this.findIndex(to, 1000000000 /* C.Far */, false, i); i < e; i++)
if (f(this.from[i] + offset, this.to[i] + offset, this.value[i]) === false)
return false;
}
@ -3302,7 +3329,7 @@ class RangeSet {
*/
static eq(oldSets, newSets, from = 0, to) {
if (to == null)
to = 1000000000 /* Far */;
to = 1000000000 /* C.Far */ - 1;
let a = oldSets.filter(set => !set.isEmpty && newSets.indexOf(set) < 0);
let b = newSets.filter(set => !set.isEmpty && oldSets.indexOf(set) < 0);
if (a.length != b.length)
@ -3336,23 +3363,24 @@ class RangeSet {
*/
minPointSize = -1) {
let cursor = new SpanCursor(sets, null, minPointSize).goto(from), pos = from;
let open = cursor.openStart;
let openRanges = cursor.openStart;
for (;;) {
let curTo = Math.min(cursor.to, to);
if (cursor.point) {
iterator.point(pos, curTo, cursor.point, cursor.activeForPoint(cursor.to), open, cursor.pointRank);
open = cursor.openEnd(curTo) + (cursor.to > curTo ? 1 : 0);
let active = cursor.activeForPoint(cursor.to);
let openCount = cursor.pointFrom < from ? active.length + 1 : Math.min(active.length, openRanges);
iterator.point(pos, curTo, cursor.point, active, openCount, cursor.pointRank);
openRanges = Math.min(cursor.openEnd(curTo), active.length);
}
else if (curTo > pos) {
iterator.span(pos, curTo, cursor.active, open);
open = cursor.openEnd(curTo);
iterator.span(pos, curTo, cursor.active, openRanges);
openRanges = cursor.openEnd(curTo);
}
if (cursor.to > to)
break;
return openRanges + (cursor.point && cursor.to > to ? 1 : 0);
pos = cursor.to;
cursor.next();
}
return open;
}
/**
Create a range set for the given range or array of ranges. By
@ -3397,8 +3425,8 @@ class RangeSetBuilder {
this.chunkPos = [];
this.chunkStart = -1;
this.last = null;
this.lastFrom = -1000000000 /* Far */;
this.lastTo = -1000000000 /* Far */;
this.lastFrom = -1000000000 /* C.Far */;
this.lastTo = -1000000000 /* C.Far */;
this.from = [];
this.to = [];
this.value = [];
@ -3435,7 +3463,7 @@ class RangeSetBuilder {
throw new Error("Ranges must be added sorted by `from` position and `startSide`");
if (diff < 0)
return false;
if (this.from.length == 250 /* ChunkSize */)
if (this.from.length == 250 /* C.ChunkSize */)
this.finishChunk(true);
if (this.chunkStart < 0)
this.chunkStart = from;
@ -3509,7 +3537,7 @@ class LayerCursor {
}
get startSide() { return this.value ? this.value.startSide : 0; }
get endSide() { return this.value ? this.value.endSide : 0; }
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
this.chunkIndex = this.rangeIndex = 0;
this.gotoInner(pos, side, false);
return this;
@ -3538,7 +3566,7 @@ class LayerCursor {
next() {
for (;;) {
if (this.chunkIndex == this.layer.chunk.length) {
this.from = this.to = 1000000000 /* Far */;
this.from = this.to = 1000000000 /* C.Far */;
this.value = null;
break;
}
@ -3592,7 +3620,7 @@ class HeapCursor {
return heap.length == 1 ? heap[0] : new HeapCursor(heap);
}
get startSide() { return this.value ? this.value.startSide : 0; }
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
for (let cur of this.heap)
cur.goto(pos, side);
for (let i = this.heap.length >> 1; i >= 0; i--)
@ -3610,7 +3638,7 @@ class HeapCursor {
}
next() {
if (this.heap.length == 0) {
this.from = this.to = 1000000000 /* Far */;
this.from = this.to = 1000000000 /* C.Far */;
this.value = null;
this.rank = -1;
}
@ -3654,12 +3682,14 @@ class SpanCursor {
this.point = null;
this.pointFrom = 0;
this.pointRank = 0;
this.to = -1000000000 /* Far */;
this.to = -1000000000 /* C.Far */;
this.endSide = 0;
// The amount of open active ranges at the start of the iterator.
// Not including points.
this.openStart = -1;
this.cursor = HeapCursor.from(sets, skip, minPoint);
}
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
this.cursor.goto(pos, side);
this.active.length = this.activeTo.length = this.activeRank.length = 0;
this.minActive = -1;
@ -3696,7 +3726,7 @@ class SpanCursor {
next() {
let from = this.to, wasPoint = this.point;
this.point = null;
let trackOpen = this.openStart < 0 ? [] : null, trackExtra = 0;
let trackOpen = this.openStart < 0 ? [] : null;
for (;;) {
let a = this.minActive;
if (a > -1 && (this.activeTo[a] - this.cursor.from || this.active[a].endSide - this.cursor.startSide) < 0) {
@ -3710,7 +3740,7 @@ class SpanCursor {
remove(trackOpen, a);
}
else if (!this.cursor.value) {
this.to = this.endSide = 1000000000 /* Far */;
this.to = this.endSide = 1000000000 /* C.Far */;
break;
}
else if (this.cursor.from > from) {
@ -3734,8 +3764,6 @@ class SpanCursor {
this.pointRank = this.cursor.rank;
this.to = this.cursor.to;
this.endSide = nextVal.endSide;
if (this.cursor.from < from)
trackExtra = 1;
this.cursor.next();
this.forward(this.to, this.endSide);
break;
@ -3743,10 +3771,9 @@ class SpanCursor {
}
}
if (trackOpen) {
let openStart = 0;
while (openStart < trackOpen.length && trackOpen[openStart] < from)
openStart++;
this.openStart = openStart + trackExtra;
this.openStart = 0;
for (let i = trackOpen.length - 1; i >= 0 && trackOpen[i] < from; i--)
this.openStart++;
}
}
activeForPoint(to) {
@ -3813,7 +3840,7 @@ function insert(array, index, value) {
array[index] = value;
}
function findMinIndex(value, array) {
let found = -1, foundPos = 1000000000 /* Far */;
let found = -1, foundPos = 1000000000 /* C.Far */;
for (let i = 0; i < array.length; i++)
if ((array[i] - foundPos || value[i].endSide - value[found].endSide) < 0) {
found = i;

View file

@ -476,7 +476,7 @@ declare class EditorSelection {
/**
Create a selection range.
*/
static range(anchor: number, head: number, goalColumn?: number): SelectionRange;
static range(anchor: number, head: number, goalColumn?: number, bidiLevel?: number): SelectionRange;
}
declare type FacetConfig<Input, Output> = {
@ -505,13 +505,14 @@ declare type FacetConfig<Input, Output> = {
*/
static?: boolean;
/**
If given, these extension(s) will be added to any state where
this facet is provided. (Note that, while a facet's default
value can be read from a state even if the facet wasn't present
in the state at all, these extensions won't be added in that
If given, these extension(s) (or the result of calling the given
function with the facet) will be added to any state where this
facet is provided. (Note that, while a facet's default value can
be read from a state even if the facet wasn't present in the
state at all, these extensions won't be added in that
situation.)
*/
enables?: Extension;
enables?: Extension | ((self: Facet<Input, Output>) => Extension);
};
/**
A facet is a labeled value that is associated with an editor
@ -1227,6 +1228,18 @@ declare class EditorState {
/**
Find the values for a given language data field, provided by the
the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet.
Examples of language data fields are...
- [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying
comment syntax.
- [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override)
for providing language-specific completion sources.
- [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding
characters that should be considered part of words in this
language.
- [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls
bracket closing behavior.
*/
languageDataAt<T>(name: string, pos: number, side?: -1 | 0 | 1): readonly T[];
/**

View file

@ -27,10 +27,10 @@ class Text {
*/
replace(from, to, text) {
let parts = [];
this.decompose(0, from, parts, 2 /* To */);
this.decompose(0, from, parts, 2 /* Open.To */);
if (text.length)
text.decompose(0, text.length, parts, 1 /* From */ | 2 /* To */);
this.decompose(to, this.length, parts, 1 /* From */);
text.decompose(0, text.length, parts, 1 /* Open.From */ | 2 /* Open.To */);
this.decompose(to, this.length, parts, 1 /* Open.From */);
return TextNode.from(parts, this.length - (to - from) + text.length);
}
/**
@ -120,7 +120,7 @@ class Text {
throw new RangeError("A document must have at least one line");
if (text.length == 1 && !text[0])
return Text.empty;
return text.length <= 32 /* Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
return text.length <= 32 /* Tree.Branch */ ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, []));
}
}
// Leaves store an array of line strings. There are always line breaks
@ -146,10 +146,10 @@ class TextLeaf extends Text {
decompose(from, to, target, open) {
let text = from <= 0 && to >= this.length ? this
: new TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from));
if (open & 1 /* From */) {
if (open & 1 /* Open.From */) {
let prev = target.pop();
let joined = appendText(text.text, prev.text.slice(), 0, text.length);
if (joined.length <= 32 /* Branch */) {
if (joined.length <= 32 /* Tree.Branch */) {
target.push(new TextLeaf(joined, prev.length + text.length));
}
else {
@ -166,7 +166,7 @@ class TextLeaf extends Text {
return super.replace(from, to, text);
let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to);
let newLen = this.length + text.length - (to - from);
if (lines.length <= 32 /* Branch */)
if (lines.length <= 32 /* Tree.Branch */)
return new TextLeaf(lines, newLen);
return TextNode.from(TextLeaf.split(lines, []), newLen);
}
@ -192,7 +192,7 @@ class TextLeaf extends Text {
for (let line of text) {
part.push(line);
len += line.length + 1;
if (part.length == 32 /* Branch */) {
if (part.length == 32 /* Tree.Branch */) {
target.push(new TextLeaf(part, len));
part = [];
len = -1;
@ -229,7 +229,7 @@ class TextNode extends Text {
for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) {
let child = this.children[i], end = pos + child.length;
if (from <= end && to >= pos) {
let childOpen = open & ((pos <= from ? 1 /* From */ : 0) | (end >= to ? 2 /* To */ : 0));
let childOpen = open & ((pos <= from ? 1 /* Open.From */ : 0) | (end >= to ? 2 /* Open.To */ : 0));
if (pos >= from && end <= to && !childOpen)
target.push(child);
else
@ -248,8 +248,8 @@ class TextNode extends Text {
if (from >= pos && to <= end) {
let updated = child.replace(from - pos, to - pos, text);
let totalLines = this.lines - child.lines + updated.lines;
if (updated.lines < (totalLines >> (5 /* BranchShift */ - 1)) &&
updated.lines > (totalLines >> (5 /* BranchShift */ + 1))) {
if (updated.lines < (totalLines >> (5 /* Tree.BranchShift */ - 1)) &&
updated.lines > (totalLines >> (5 /* Tree.BranchShift */ + 1))) {
let copy = this.children.slice();
copy[i] = updated;
return new TextNode(copy, this.length - (to - from) + text.length);
@ -295,13 +295,13 @@ class TextNode extends Text {
let lines = 0;
for (let ch of children)
lines += ch.lines;
if (lines < 32 /* Branch */) {
if (lines < 32 /* Tree.Branch */) {
let flat = [];
for (let ch of children)
ch.flatten(flat);
return new TextLeaf(flat, length);
}
let chunk = Math.max(32 /* Branch */, lines >> 5 /* BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1;
let chunk = Math.max(32 /* Tree.Branch */, lines >> 5 /* Tree.BranchShift */), maxChunk = chunk << 1, minChunk = chunk >> 1;
let chunked = [], currentLines = 0, currentLen = -1, currentChunk = [];
function add(child) {
let last;
@ -315,7 +315,7 @@ class TextNode extends Text {
}
else if (child instanceof TextLeaf && currentLines &&
(last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf &&
child.lines + last.lines <= 32 /* Branch */) {
child.lines + last.lines <= 32 /* Tree.Branch */) {
currentLines += child.lines;
currentLen += child.length + 1;
currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length);
@ -1125,51 +1125,65 @@ function iterChanges(desc, f, individual) {
}
}
function mapSet(setA, setB, before, mkSet = false) {
// Produce a copy of setA that applies to the document after setB
// has been applied (assuming both start at the same document).
let sections = [], insert = mkSet ? [] : null;
let a = new SectionIter(setA), b = new SectionIter(setB);
for (let posA = 0, posB = 0;;) {
if (a.ins == -1) {
posA += a.len;
a.next();
// Iterate over both sets in parallel. inserted tracks, for changes
// in A that have to be processed piece-by-piece, whether their
// content has been inserted already, and refers to the section
// index.
for (let inserted = -1;;) {
if (a.ins == -1 && b.ins == -1) {
// Move across ranges skipped by both sets.
let len = Math.min(a.len, b.len);
addSection(sections, len, -1);
a.forward(len);
b.forward(len);
}
else if (b.ins == -1 && posB < posA) {
let skip = Math.min(b.len, posA - posB);
b.forward(skip);
addSection(sections, skip, -1);
posB += skip;
}
else if (b.ins >= 0 && (a.done || posB < posA || posB == posA && (b.len < a.len || b.len == a.len && !before))) {
else if (b.ins >= 0 && (a.ins < 0 || inserted == a.i || a.off == 0 && (b.len < a.len || b.len == a.len && !before))) {
// If there's a change in B that comes before the next change in
// A (ordered by start pos, then len, then before flag), skip
// that (and process any changes in A it covers).
let len = b.len;
addSection(sections, b.ins, -1);
while (posA > posB && !a.done && posA + a.len < posB + b.len) {
posA += a.len;
a.next();
while (len) {
let piece = Math.min(a.len, len);
if (a.ins >= 0 && inserted < a.i && a.len <= piece) {
addSection(sections, 0, a.ins);
if (insert)
addInsert(insert, sections, a.text);
inserted = a.i;
}
a.forward(piece);
len -= piece;
}
posB += b.len;
b.next();
}
else if (a.ins >= 0) {
let len = 0, end = posA + a.len;
for (;;) {
if (b.ins >= 0 && posB > posA && posB + b.len < end) {
len += b.ins;
posB += b.len;
b.next();
// Process the part of a change in A up to the start of the next
// non-deletion change in B (if overlapping).
let len = 0, left = a.len;
while (left) {
if (b.ins == -1) {
let piece = Math.min(left, b.len);
len += piece;
left -= piece;
b.forward(piece);
}
else if (b.ins == -1 && posB < end) {
let skip = Math.min(b.len, end - posB);
len += skip;
b.forward(skip);
posB += skip;
else if (b.ins == 0 && b.len < left) {
left -= b.len;
b.next();
}
else {
break;
}
}
addSection(sections, len, a.ins);
if (insert)
addSection(sections, len, inserted < a.i ? a.ins : 0);
if (insert && inserted < a.i)
addInsert(insert, sections, a.text);
posA = end;
a.next();
inserted = a.i;
a.forward(a.len - left);
}
else if (a.done && b.done) {
return insert ? ChangeSet.createSet(sections, insert) : ChangeDesc.create(sections);
@ -1297,12 +1311,12 @@ class SelectionRange {
The anchor of the rangethe side that doesn't move when you
extend it.
*/
get anchor() { return this.flags & 16 /* Inverted */ ? this.to : this.from; }
get anchor() { return this.flags & 16 /* RangeFlag.Inverted */ ? this.to : this.from; }
/**
The head of the range, which is moved when the range is
[extended](https://codemirror.net/6/docs/ref/#state.SelectionRange.extend).
*/
get head() { return this.flags & 16 /* Inverted */ ? this.from : this.to; }
get head() { return this.flags & 16 /* RangeFlag.Inverted */ ? this.from : this.to; }
/**
True when `anchor` and `head` are at the same position.
*/
@ -1313,13 +1327,13 @@ class SelectionRange {
the character before its position, 1 the character after, and 0
means no association.
*/
get assoc() { return this.flags & 4 /* AssocBefore */ ? -1 : this.flags & 8 /* AssocAfter */ ? 1 : 0; }
get assoc() { return this.flags & 4 /* RangeFlag.AssocBefore */ ? -1 : this.flags & 8 /* RangeFlag.AssocAfter */ ? 1 : 0; }
/**
The bidirectional text level associated with this cursor, if
any.
*/
get bidiLevel() {
let level = this.flags & 3 /* BidiLevelMask */;
let level = this.flags & 3 /* RangeFlag.BidiLevelMask */;
return level == 3 ? null : level;
}
/**
@ -1329,8 +1343,8 @@ class SelectionRange {
lines of different length.
*/
get goalColumn() {
let value = this.flags >> 5 /* GoalColumnOffset */;
return value == 33554431 /* NoGoalColumn */ ? undefined : value;
let value = this.flags >> 5 /* RangeFlag.GoalColumnOffset */;
return value == 33554431 /* RangeFlag.NoGoalColumn */ ? undefined : value;
}
/**
Map this range through a change, producing a valid range in the
@ -1490,17 +1504,18 @@ class EditorSelection {
safely ignore the optional arguments in most situations.
*/
static cursor(pos, assoc = 0, bidiLevel, goalColumn) {
return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* AssocBefore */ : 8 /* AssocAfter */) |
return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 4 /* RangeFlag.AssocBefore */ : 8 /* RangeFlag.AssocAfter */) |
(bidiLevel == null ? 3 : Math.min(2, bidiLevel)) |
((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */));
((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */));
}
/**
Create a selection range.
*/
static range(anchor, head, goalColumn) {
let goal = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* NoGoalColumn */) << 5 /* GoalColumnOffset */;
return head < anchor ? SelectionRange.create(head, anchor, 16 /* Inverted */ | goal | 8 /* AssocAfter */)
: SelectionRange.create(anchor, head, goal | (head > anchor ? 4 /* AssocBefore */ : 0));
static range(anchor, head, goalColumn, bidiLevel) {
let flags = ((goalColumn !== null && goalColumn !== void 0 ? goalColumn : 33554431 /* RangeFlag.NoGoalColumn */) << 5 /* RangeFlag.GoalColumnOffset */) |
(bidiLevel == null ? 3 : Math.min(2, bidiLevel));
return head < anchor ? SelectionRange.create(head, anchor, 16 /* RangeFlag.Inverted */ | 8 /* RangeFlag.AssocAfter */ | flags)
: SelectionRange.create(anchor, head, (head > anchor ? 4 /* RangeFlag.AssocBefore */ : 0) | flags);
}
/**
@internal
@ -1551,21 +1566,17 @@ class Facet {
/**
@internal
*/
compare, isStatic,
/**
@internal
*/
extensions) {
compare, isStatic, enables) {
this.combine = combine;
this.compareInput = compareInput;
this.compare = compare;
this.isStatic = isStatic;
this.extensions = extensions;
/**
@internal
*/
this.id = nextID++;
this.default = combine([]);
this.extensions = typeof enables == "function" ? enables(this) : enables;
}
/**
Define a new facet.
@ -1577,7 +1588,7 @@ class Facet {
Returns an extension that adds the given value to this facet.
*/
of(value) {
return new FacetProvider([], this, 0 /* Static */, value);
return new FacetProvider([], this, 0 /* Provider.Static */, value);
}
/**
Create an extension that computes a value for the facet from a
@ -1591,7 +1602,7 @@ class Facet {
compute(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 1 /* Single */, get);
return new FacetProvider(deps, this, 1 /* Provider.Single */, get);
}
/**
Create an extension that computes zero or more values for this
@ -1600,7 +1611,7 @@ class Facet {
computeN(deps, get) {
if (this.isStatic)
throw new Error("Can't compute a static facet");
return new FacetProvider(deps, this, 2 /* Multi */, get);
return new FacetProvider(deps, this, 2 /* Provider.Multi */, get);
}
from(field, get) {
if (!get)
@ -1623,7 +1634,7 @@ class FacetProvider {
var _a;
let getter = this.value;
let compare = this.facet.compareInput;
let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Multi */;
let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2 /* Provider.Multi */;
let depDoc = false, depSel = false, depAddrs = [];
for (let dep of this.dependencies) {
if (dep == "doc")
@ -1636,33 +1647,35 @@ class FacetProvider {
return {
create(state) {
state.values[idx] = getter(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update(state, tr) {
if ((depDoc && tr.docChanged) || (depSel && (tr.docChanged || tr.selection)) || ensureAll(state, depAddrs)) {
let newVal = getter(state);
if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) {
state.values[idx] = newVal;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
}
return 0;
},
reconfigure: (state, oldState) => {
let newVal = getter(state);
let oldAddr = oldState.config.address[id];
let newVal, oldAddr = oldState.config.address[id];
if (oldAddr != null) {
let oldVal = getAddr(oldState, oldAddr);
if (this.dependencies.every(dep => {
return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) :
dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true;
}) || (multi ? compareArray(newVal, oldVal, compare) : compare(newVal, oldVal))) {
}) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) {
state.values[idx] = oldVal;
return 0;
}
}
else {
newVal = getter(state);
}
state.values[idx] = newVal;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1678,7 +1691,7 @@ function compareArray(a, b, compare) {
function ensureAll(state, addrs) {
let changed = false;
for (let addr of addrs)
if (ensureAddr(state, addr) & 1 /* Changed */)
if (ensureAddr(state, addr) & 1 /* SlotStatus.Changed */)
changed = true;
return changed;
}
@ -1691,7 +1704,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
let values = [];
for (let i = 0; i < providerAddrs.length; i++) {
let value = getAddr(state, providerAddrs[i]);
if (providerTypes[i] == 2 /* Multi */)
if (providerTypes[i] == 2 /* Provider.Multi */)
for (let val of value)
values.push(val);
else
@ -1704,7 +1717,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
for (let addr of providerAddrs)
ensureAddr(state, addr);
state.values[idx] = get(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update(state, tr) {
if (!ensureAll(state, dynamic))
@ -1713,7 +1726,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
if (facet.compare(value, state.values[idx]))
return 0;
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
reconfigure(state, oldState) {
let depChanged = ensureAll(state, providerAddrs);
@ -1728,7 +1741,7 @@ function dynamicFacetSlot(addresses, facet, providers) {
return 0;
}
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1778,7 +1791,7 @@ class StateField {
return {
create: (state) => {
state.values[idx] = this.create(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
update: (state, tr) => {
let oldVal = state.values[idx];
@ -1786,7 +1799,7 @@ class StateField {
if (this.compareF(oldVal, value))
return 0;
state.values[idx] = value;
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
},
reconfigure: (state, oldState) => {
if (oldState.config.address[this.id] != null) {
@ -1794,7 +1807,7 @@ class StateField {
return 0;
}
state.values[idx] = this.create(state);
return 1 /* Changed */;
return 1 /* SlotStatus.Changed */;
}
};
}
@ -1903,7 +1916,7 @@ class Configuration {
this.facets = facets;
this.statusTemplate = [];
while (this.statusTemplate.length < dynamicSlots.length)
this.statusTemplate.push(0 /* Unresolved */);
this.statusTemplate.push(0 /* SlotStatus.Unresolved */);
}
staticFacet(facet) {
let addr = this.address[facet.id];
@ -1930,7 +1943,7 @@ class Configuration {
for (let id in facets) {
let providers = facets[id], facet = providers[0].facet;
let oldProviders = oldFacets && oldFacets[id] || [];
if (providers.every(p => p.type == 0 /* Static */)) {
if (providers.every(p => p.type == 0 /* Provider.Static */)) {
address[facet.id] = (staticValues.length << 1) | 1;
if (sameArray(oldProviders, providers)) {
staticValues.push(oldState.facet(facet));
@ -1942,7 +1955,7 @@ class Configuration {
}
else {
for (let p of providers) {
if (p.type == 0 /* Static */) {
if (p.type == 0 /* Provider.Static */) {
address[p.id] = (staticValues.length << 1) | 1;
staticValues.push(p.value);
}
@ -1996,7 +2009,7 @@ function flatten(extension, compartments, newCompartments) {
else if (ext instanceof FacetProvider) {
result[prec].push(ext);
if (ext.facet.extensions)
inner(ext.facet.extensions, prec);
inner(ext.facet.extensions, Prec_.default);
}
else {
let content = ext.extension;
@ -2010,16 +2023,16 @@ function flatten(extension, compartments, newCompartments) {
}
function ensureAddr(state, addr) {
if (addr & 1)
return 2 /* Computed */;
return 2 /* SlotStatus.Computed */;
let idx = addr >> 1;
let status = state.status[idx];
if (status == 4 /* Computing */)
if (status == 4 /* SlotStatus.Computing */)
throw new Error("Cyclic dependency between fields and/or facets");
if (status & 2 /* Computed */)
if (status & 2 /* SlotStatus.Computed */)
return status;
state.status[idx] = 4 /* Computing */;
state.status[idx] = 4 /* SlotStatus.Computing */;
let changed = state.computeSlot(state, state.config.dynamicSlots[idx]);
return state.status[idx] = 2 /* Computed */ | changed;
return state.status[idx] = 2 /* SlotStatus.Computed */ | changed;
}
function getAddr(state, addr) {
return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1];
@ -2434,7 +2447,7 @@ function filterTransaction(tr) {
else {
let filtered = tr.changes.filter(result);
changes = filtered.changes;
back = filtered.filtered.invertedDesc;
back = filtered.filtered.mapDesc(filtered.changes).invertedDesc;
}
tr = Transaction.create(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.scrollIntoView);
}
@ -2456,7 +2469,7 @@ function extendTransaction(tr) {
for (let i = extenders.length - 1; i >= 0; i--) {
let extension = extenders[i](tr);
if (extension && Object.keys(extension).length)
spec = mergeTransaction(tr, resolveTransactionInner(state, extension, tr.changes.newLength), true);
spec = mergeTransaction(spec, resolveTransactionInner(state, extension, tr.changes.newLength), true);
}
return spec == tr ? tr : Transaction.create(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.scrollIntoView);
}
@ -2706,7 +2719,7 @@ class EditorState {
if (fields)
for (let prop in fields) {
let value = fields[prop];
if (value instanceof StateField)
if (value instanceof StateField && this.config.address[value.id] != null)
result[prop] = value.spec.toJSON(this.field(fields[prop]), this);
}
return result;
@ -2723,8 +2736,10 @@ class EditorState {
let fieldInit = [];
if (fields)
for (let prop in fields) {
let field = fields[prop], value = json[prop];
fieldInit.push(field.init(state => field.spec.fromJSON(value, state)));
if (Object.prototype.hasOwnProperty.call(json, prop)) {
let field = fields[prop], value = json[prop];
fieldInit.push(field.init(state => field.spec.fromJSON(value, state)));
}
}
return EditorState.create({
doc: json.doc,
@ -2785,13 +2800,25 @@ class EditorState {
if (i == "$")
return "$";
let n = +(i || 1);
return n > insert.length ? m : insert[n - 1];
return !n || n > insert.length ? m : insert[n - 1];
});
return phrase;
}
/**
Find the values for a given language data field, provided by the
the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet.
Examples of language data fields are...
- [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying
comment syntax.
- [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override)
for providing language-specific completion sources.
- [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding
characters that should be considered part of words in this
language.
- [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls
bracket closing behavior.
*/
languageDataAt(name, pos, side = -1) {
let values = [];
@ -3063,7 +3090,7 @@ class Chunk {
}
}
between(offset, from, to, f) {
for (let i = this.findIndex(from, -1000000000 /* Far */, true), e = this.findIndex(to, 1000000000 /* Far */, false, i); i < e; i++)
for (let i = this.findIndex(from, -1000000000 /* C.Far */, true), e = this.findIndex(to, 1000000000 /* C.Far */, false, i); i < e; i++)
if (f(this.from[i] + offset, this.to[i] + offset, this.value[i]) === false)
return false;
}
@ -3296,7 +3323,7 @@ class RangeSet {
*/
static eq(oldSets, newSets, from = 0, to) {
if (to == null)
to = 1000000000 /* Far */;
to = 1000000000 /* C.Far */ - 1;
let a = oldSets.filter(set => !set.isEmpty && newSets.indexOf(set) < 0);
let b = newSets.filter(set => !set.isEmpty && oldSets.indexOf(set) < 0);
if (a.length != b.length)
@ -3330,23 +3357,24 @@ class RangeSet {
*/
minPointSize = -1) {
let cursor = new SpanCursor(sets, null, minPointSize).goto(from), pos = from;
let open = cursor.openStart;
let openRanges = cursor.openStart;
for (;;) {
let curTo = Math.min(cursor.to, to);
if (cursor.point) {
iterator.point(pos, curTo, cursor.point, cursor.activeForPoint(cursor.to), open, cursor.pointRank);
open = cursor.openEnd(curTo) + (cursor.to > curTo ? 1 : 0);
let active = cursor.activeForPoint(cursor.to);
let openCount = cursor.pointFrom < from ? active.length + 1 : Math.min(active.length, openRanges);
iterator.point(pos, curTo, cursor.point, active, openCount, cursor.pointRank);
openRanges = Math.min(cursor.openEnd(curTo), active.length);
}
else if (curTo > pos) {
iterator.span(pos, curTo, cursor.active, open);
open = cursor.openEnd(curTo);
iterator.span(pos, curTo, cursor.active, openRanges);
openRanges = cursor.openEnd(curTo);
}
if (cursor.to > to)
break;
return openRanges + (cursor.point && cursor.to > to ? 1 : 0);
pos = cursor.to;
cursor.next();
}
return open;
}
/**
Create a range set for the given range or array of ranges. By
@ -3391,8 +3419,8 @@ class RangeSetBuilder {
this.chunkPos = [];
this.chunkStart = -1;
this.last = null;
this.lastFrom = -1000000000 /* Far */;
this.lastTo = -1000000000 /* Far */;
this.lastFrom = -1000000000 /* C.Far */;
this.lastTo = -1000000000 /* C.Far */;
this.from = [];
this.to = [];
this.value = [];
@ -3429,7 +3457,7 @@ class RangeSetBuilder {
throw new Error("Ranges must be added sorted by `from` position and `startSide`");
if (diff < 0)
return false;
if (this.from.length == 250 /* ChunkSize */)
if (this.from.length == 250 /* C.ChunkSize */)
this.finishChunk(true);
if (this.chunkStart < 0)
this.chunkStart = from;
@ -3503,7 +3531,7 @@ class LayerCursor {
}
get startSide() { return this.value ? this.value.startSide : 0; }
get endSide() { return this.value ? this.value.endSide : 0; }
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
this.chunkIndex = this.rangeIndex = 0;
this.gotoInner(pos, side, false);
return this;
@ -3532,7 +3560,7 @@ class LayerCursor {
next() {
for (;;) {
if (this.chunkIndex == this.layer.chunk.length) {
this.from = this.to = 1000000000 /* Far */;
this.from = this.to = 1000000000 /* C.Far */;
this.value = null;
break;
}
@ -3586,7 +3614,7 @@ class HeapCursor {
return heap.length == 1 ? heap[0] : new HeapCursor(heap);
}
get startSide() { return this.value ? this.value.startSide : 0; }
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
for (let cur of this.heap)
cur.goto(pos, side);
for (let i = this.heap.length >> 1; i >= 0; i--)
@ -3604,7 +3632,7 @@ class HeapCursor {
}
next() {
if (this.heap.length == 0) {
this.from = this.to = 1000000000 /* Far */;
this.from = this.to = 1000000000 /* C.Far */;
this.value = null;
this.rank = -1;
}
@ -3648,12 +3676,14 @@ class SpanCursor {
this.point = null;
this.pointFrom = 0;
this.pointRank = 0;
this.to = -1000000000 /* Far */;
this.to = -1000000000 /* C.Far */;
this.endSide = 0;
// The amount of open active ranges at the start of the iterator.
// Not including points.
this.openStart = -1;
this.cursor = HeapCursor.from(sets, skip, minPoint);
}
goto(pos, side = -1000000000 /* Far */) {
goto(pos, side = -1000000000 /* C.Far */) {
this.cursor.goto(pos, side);
this.active.length = this.activeTo.length = this.activeRank.length = 0;
this.minActive = -1;
@ -3690,7 +3720,7 @@ class SpanCursor {
next() {
let from = this.to, wasPoint = this.point;
this.point = null;
let trackOpen = this.openStart < 0 ? [] : null, trackExtra = 0;
let trackOpen = this.openStart < 0 ? [] : null;
for (;;) {
let a = this.minActive;
if (a > -1 && (this.activeTo[a] - this.cursor.from || this.active[a].endSide - this.cursor.startSide) < 0) {
@ -3704,7 +3734,7 @@ class SpanCursor {
remove(trackOpen, a);
}
else if (!this.cursor.value) {
this.to = this.endSide = 1000000000 /* Far */;
this.to = this.endSide = 1000000000 /* C.Far */;
break;
}
else if (this.cursor.from > from) {
@ -3728,8 +3758,6 @@ class SpanCursor {
this.pointRank = this.cursor.rank;
this.to = this.cursor.to;
this.endSide = nextVal.endSide;
if (this.cursor.from < from)
trackExtra = 1;
this.cursor.next();
this.forward(this.to, this.endSide);
break;
@ -3737,10 +3765,9 @@ class SpanCursor {
}
}
if (trackOpen) {
let openStart = 0;
while (openStart < trackOpen.length && trackOpen[openStart] < from)
openStart++;
this.openStart = openStart + trackExtra;
this.openStart = 0;
for (let i = trackOpen.length - 1; i >= 0 && trackOpen[i] < from; i--)
this.openStart++;
}
}
activeForPoint(to) {
@ -3807,7 +3834,7 @@ function insert(array, index, value) {
array[index] = value;
}
function findMinIndex(value, array) {
let found = -1, foundPos = 1000000000 /* Far */;
let found = -1, foundPos = 1000000000 /* C.Far */;
for (let i = 0; i < array.length; i++)
if ((array[i] - foundPos || value[i].endSide - value[found].endSide) < 0) {
found = i;

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/state",
"version": "6.0.0",
"version": "6.2.0",
"description": "Editor state data structures for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,285 @@
## 6.8.1 (2023-02-08)
### Bug fixes
Fix an issue where tooltips that have their height reduced have their height flicker when scrolling or otherwise interacting with the editor.
## 6.8.0 (2023-02-07)
### Bug fixes
Fix a regression that caused clicking on the scrollbar to move the selection.
Fix an issue where focus or blur event handlers that dispatched editor transactions could corrupt the mouse selection state.
Fix a CSS regression that prevented the drop cursor from being positioned properly.
### New features
`WidgetType.updateDOM` is now passed the editor view object.
## 6.7.3 (2023-01-12)
### Bug fixes
Fix a bug in `posAtCoords` that could cause incorrect results for positions to the left of a wrapped line.
## 6.7.2 (2023-01-04)
### Bug fixes
Fix a regression where the cursor didn't restart its blink cycle when moving it with the pointer.
Even without a `key` property, measure request objects that are already scheduled will not be scheduled again by `requestMeasure`.
Fix an issue where keymaps incorrectly interpreted key events that used Ctrl+Alt modifiers to simulate AltGr on Windows.
Fix a bug where line decorations with a different `class` property would be treated as equal.
Fix a bug that caused `drawSelection` to not notice when it was reconfigured.
Fix a crash in the gutter extension caused by sharing of mutable arrays.
Fix a regression that caused touch selection on mobile platforms to not work in an uneditable editor.
Fix a bug where DOM events on the boundary between lines could get assigned to the wrong line.
## 6.7.1 (2022-12-12)
### Bug fixes
Make the editor properly scroll when moving the pointer out of it during drag selection.
Fix a regression where clicking below the content element in an editor with its own height didn't focus the editor.
## 6.7.0 (2022-12-07)
### Bug fixes
Make the editor notice widget height changes to automatically adjust its height information.
Fix an issue where widget buffers could be incorrectly omitted after empty lines.
Fix an issue in content redrawing that could cause `coordsAtPos` to return incorrect results.
### New features
The static `RectangleMarker.forRange` method exposes the logic used by the editor to draw rectangles covering a selection range.
Layers can now provide a `destroy` function to be called when the layer is removed.
The new `highlightWhitespace` extension makes spaces and tabs in the editor visible.
The `highlightTrailingWhitespace` extension can be used to make trailing whitespace stand out.
## 6.6.0 (2022-11-24)
### New features
The `layer` function can now be used to define extensions that draw DOM elements over or below the document text.
Tooltips that are bigger than the available vertical space for them will now have their height set so that they don't stick out of the window. The new `resize` property on `TooltipView` can be used to opt out of this behavior.
## 6.5.1 (2022-11-15)
### Bug fixes
Fix a bug that caused marked unnecessary splitting of mark decoration DOM elements in some cases.
## 6.5.0 (2022-11-14)
### Bug fixes
Fix an issue where key bindings were activated for the wrong key in some situations with non-US keyboards.
### New features
A tooltip's `positioned` callback is now passed the available space for tooltips.
## 6.4.2 (2022-11-10)
### Bug fixes
Typing into a read-only editor no longer moves the cursor.
Fix an issue where hover tooltips were closed when the mouse was moved over them if they had a custom parent element.
Fix an issue where the editor could end up displaying incorrect height measurements (typically after initializing).
## 6.4.1 (2022-11-07)
### Bug fixes
Fix an issue where coordinates next to replaced widgets were returned incorrectly, causing the cursor to be drawn in the wrong place.
Update the `crosshairCursor` state on every mousemove event.
Avoid an issue in the way that the editor enforces cursor associativity that could cause the cursor to get stuck on single-character wrapped lines.
## 6.4.0 (2022-10-18)
### Bug fixes
Avoid an issue where `scrollPastEnd` makes a single-line editor have a vertical scrollbar.
Work around a Chrome bug where it inserts a newline when you press space at the start of a wrapped line.
Align `rectangularSelection`'s behavior with other popular editors by making it create cursors at the end of lines that are too short to touch the rectangle.
Fix an issue where coordinates on mark decoration boundaries were sometimes taken from the wrong side of the position.
Prevent scrolling artifacts caused by attempts to scroll stuff into view when the editor isn't being displayed.
### New features
`TooltipView` objects can now provide a `destroy` method to be called when the tooltip is removed.
## 6.3.1 (2022-10-10)
### Bug fixes
Fix a crash when trying to scroll something into view in an editor that wasn't in the visible DOM.
Fix an issue where `coordsAtPos` returned the coordinates on the wrong side of a widget decoration wrapped in a mark decoration.
Fix an issue where content on long wrapped lines could fail to properly scroll into view.
Fix an issue where DOM change reading on Chrome Android could get confused when a transaction came in right after a beforeinput event for backspace, enter, or delete.
## 6.3.0 (2022-09-28)
### Bug fixes
Reduce the amount of wrap-point jittering when scrolling through a very long wrapped line.
Fix an issue where scrolling to content that wasn't currently drawn due to being on a very long line would often fail to scroll to the right position.
Suppress double-space-adds-period behavior on Chrome Mac when it behaves weirdly next to widget.
### New features
Key binding objects with an `any` property will now add handlers that are called for any key, within the ordering of the keybindings.
## 6.2.5 (2022-09-24)
### Bug fixes
Don't override double/triple tap behavior on touch screen devices, so that the mobile selection menu pops up properly.
Fix an issue where updating the selection could crash on Safari when the editor was hidden.
## 6.2.4 (2022-09-16)
### Bug fixes
Highlight the active line even when there is a selection. Prevent the active line background from obscuring the selection backdrop.
Fix an issue where elements with negative margins would confuse the editor's scrolling-into-view logic.
Fix scrolling to a specific position in an editor that has not been in view yet.
## 6.2.3 (2022-09-08)
### Bug fixes
Fix a bug where cursor motion, when starting from a non-empty selection range, could get stuck on atomic ranges in some circumstances.
Avoid triggering Chrome Android's text-duplication issue when a period is typed in the middle of a word.
## 6.2.2 (2022-08-31)
### Bug fixes
Don't reset the selection for selection change events that were suppressed by a node view.
## 6.2.1 (2022-08-25)
### Bug fixes
Don't use the global `document` variable to track focus, since that doesn't work in another window/frame.
Fix an issue where key handlers that didn't return true were sometimes called twice for the same keypress.
Avoid editing glitches when using deletion keys like ctrl-d on iOS.
Properly treat characters from the 'Arabic Presentation Forms-A' Unicode block as right-to-left.
Work around a Firefox bug that inserts text at the wrong point for specific cross-line selections.
## 6.2.0 (2022-08-05)
### Bug fixes
Fix a bug where `posAtCoords` would return the wrong results for positions to the right of wrapped lines.
### New features
The new `EditorView.setRoot` method can be used when an editor view is moved to a new document or shadow root.
## 6.1.4 (2022-08-04)
### Bug fixes
Make selection-restoration on focus more reliable.
## 6.1.3 (2022-08-03)
### Bug fixes
Fix a bug where a document that contains only non-printing characters would lead to bogus text measurements (and, from those, to crashing).
Make sure differences between estimated and actual block heights don't cause visible scroll glitches.
## 6.1.2 (2022-07-27)
### Bug fixes
Fix an issue where double tapping enter to confirm IME input and insert a newline on iOS would sometimes insert two newlines.
Fix an issue on iOS where a composition could get aborted if the editor scrolled on backspace.
## 6.1.1 (2022-07-25)
### Bug fixes
Make `highlightSpecialChars` replace directional isolate characters by default.
The editor will now try to suppress browsers' native behavior of resetting the selection in the editable content when the editable element is focused (programmatically, with tab, etc).
Fix a CSS issue that made it possible, when the gutters were wide enough, for them to overlap with the content.
## 6.1.0 (2022-07-19)
### New features
`MatchDecorator` now supports a `decorate` option that can be used to customize the way decorations are added for each match.
## 6.0.3 (2022-07-08)
### Bug fixes
Fix a problem where `posAtCoords` could incorrectly return the start of the next line when querying positions between lines.
Fix an issue where registering a high-precedence keymap made keymap handling take precedence over other keydown event handlers.
Ctrl/Cmd-clicking can now remove ranges from a multi-range selection.
## 6.0.2 (2022-06-23)
### Bug fixes
Fix a CSS issue that broke horizontal scroll width stabilization.
Fix a bug where `defaultLineHeight` could get an incorrect value in very narrow editors.
## 6.0.1 (2022-06-17)
### Bug fixes
Avoid DOM selection corruption when the editor doesn't have focus but has selection and updates its content.
Fall back to dispatching by key code when a key event produces a non-ASCII character (so that Cyrillic and Arabic keyboards can still use bindings specified with Latin characters).
## 6.0.0 (2022-06-08)
### New features

View file

@ -1,6 +1,6 @@
MIT License
Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
Copyright (C) 2018-2021 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,13 +1,13 @@
# @codemirror/view [![NPM version](https://img.shields.io/npm/v/@codemirror/view.svg)](https://www.npmjs.org/package/@codemirror/view)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#view) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/view/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#view) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/view/blob/main/CHANGELOG.md) ]
This package implements the DOM view component for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/view/tree/main/LICENSE).

File diff suppressed because it is too large Load diff

View file

@ -154,7 +154,7 @@ declare abstract class WidgetType {
couldn't (in which case the widget will be redrawn). The default
implementation just returns false.
*/
updateDOM(dom: HTMLElement): boolean;
updateDOM(dom: HTMLElement, view: EditorView): boolean;
/**
The estimated height this widget will have, to be used when
estimating the height of content that hasn't been drawn. May
@ -647,10 +647,11 @@ declare class EditorView {
*/
get compositionStarted(): boolean;
private _dispatch;
private _root;
/**
The document or shadow root that the view lives in.
*/
readonly root: DocumentOrShadowRoot;
get root(): DocumentOrShadowRoot;
/**
The DOM element that wraps the entire editor view.
*/
@ -735,7 +736,7 @@ declare class EditorView {
know you registered a given plugin, it is recommended to check
the return value of this method.
*/
plugin<T>(plugin: ViewPlugin<T>): T | null;
plugin<T extends PluginValue>(plugin: ViewPlugin<T>): T | null;
/**
The top position of the document, in screen coordinates. This
may be negative when the editor is scrolled down. Points
@ -752,13 +753,14 @@ declare class EditorView {
/**
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)
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)).
*/
elementAtHeight(height: number): BlockInfo;
/**
Find the line block (see
[`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
height.
height, again interpreted relative to the [top of the
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
*/
lineBlockAtHeight(height: number): BlockInfo;
/**
@ -921,6 +923,11 @@ declare class EditorView {
*/
focus(): void;
/**
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: Document | ShadowRoot): void;
/**
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
@ -1047,6 +1054,11 @@ declare class EditorView {
functions are called _after_ the new viewport has been computed,
and thus **must not** introduce block widgets or replacing
decorations that cover line breaks.
If you want decorated ranges to behave like atomic units for
cursor motion and deletion purposes, also provide the range set
containing the decorations to
[`EditorView.atomicRanges`](https://codemirror.net/6/docs/ref/#view.EditorView^atomicRanges).
*/
static decorations: Facet<DecorationSet | ((view: EditorView) => DecorationSet), readonly (DecorationSet | ((view: EditorView) => DecorationSet))[]>;
/**
@ -1207,7 +1219,7 @@ interface KeyBinding {
command function returns `false`, further bindings will be tried
for the key.
*/
run: Command;
run?: Command;
/**
When given, this defines a second binding, using the (possibly
platform-specific) key name prefixed with `Shift-` to activate
@ -1215,6 +1227,11 @@ interface KeyBinding {
*/
shift?: Command;
/**
When this property is present, the function is called for every
key that is not a multi-stroke prefix.
*/
any?: (view: EditorView, event: KeyboardEvent) => boolean;
/**
By default, key bindings apply when focus is on the editor
content (the `"editor"` scope). Some extensions, mostly those
that define their own panels, might want to allow you to
@ -1349,6 +1366,94 @@ to show when the editor is empty.
*/
declare function placeholder(content: string | HTMLElement): Extension;
/**
Markers shown in a [layer](https://codemirror.net/6/docs/ref/#view.layer) must conform to this
interface. They are created in a measuring phase, and have to
contain all their positioning information, so that they can be
drawn without further DOM layout reading.
Markers are automatically absolutely positioned. Their parent
element has the same top-left corner as the document, so they
should be positioned relative to the document.
*/
interface LayerMarker {
/**
Compare this marker to a marker of the same type. Used to avoid
unnecessary redraws.
*/
eq(other: LayerMarker): boolean;
/**
Draw the marker to the DOM.
*/
draw(): HTMLElement;
/**
Update an existing marker of this type to this marker.
*/
update?(dom: HTMLElement, oldMarker: LayerMarker): boolean;
}
/**
Implementation of [`LayerMarker`](https://codemirror.net/6/docs/ref/#view.LayerMarker) that creates
a rectangle at a given set of coordinates.
*/
declare class RectangleMarker implements LayerMarker {
private className;
private left;
private top;
private width;
private height;
/**
Create a marker with the given class and dimensions. If `width`
is null, the DOM element will get no width style.
*/
constructor(className: string, left: number, top: number, width: number | null, height: number);
draw(): HTMLDivElement;
update(elt: HTMLElement, prev: RectangleMarker): boolean;
private adjust;
eq(p: RectangleMarker): boolean;
/**
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: EditorView, className: string, range: SelectionRange): readonly RectangleMarker[];
}
interface LayerConfig {
/**
Determines whether this layer is shown above or below the text.
*/
above: boolean;
/**
When given, this class is added to the DOM element that will
wrap the markers.
*/
class?: string;
/**
Called on every view update. Returning true triggers a marker
update (a call to `markers` and drawing of those markers).
*/
update(update: ViewUpdate, layer: HTMLElement): boolean;
/**
Build a set of markers for this layer, and measure their
dimensions.
*/
markers(view: EditorView): readonly LayerMarker[];
/**
If given, this is called when the layer is created.
*/
mount?(layer: HTMLElement, view: EditorView): void;
/**
If given, called when the layer is removed from the editor or
the entire editor is destroyed.
*/
destroy?(layer: HTMLElement, view: EditorView): void;
}
/**
Define a layer.
*/
declare function layer(config: LayerConfig): Extension;
/**
Helper class used to make it easier to maintain decorations on
visible code that matches a given regular expression. To be used
@ -1357,7 +1462,7 @@ represent a matching configuration.
*/
declare class MatchDecorator {
private regexp;
private getDeco;
private addMatch;
private boundary;
private maxLength;
/**
@ -1374,7 +1479,18 @@ declare class MatchDecorator {
The decoration to apply to matches, either directly or as a
function of the match.
*/
decoration: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration);
decoration?: Decoration | ((match: RegExpExecArray, view: EditorView, pos: number) => Decoration | null);
/**
Customize the way decorations are added for matches. This
function, when given, will be called for matches and should
call `add` to create decorations for them. Note that the
decorations should appear *in* the given range, and the
function should have no side effects beyond calling `add`.
The `decoration` option is ignored when `decorate` is
provided.
*/
decorate?: (add: (from: number, to: number, decoration: Decoration) => void, from: number, to: number, match: RegExpExecArray, view: EditorView) => void;
/**
By default, changed lines are re-matched entirely. You can
provide a boundary expression, which should match single
@ -1463,12 +1579,7 @@ declare function tooltips(config?: {
for showing tooltips. You can provide a function here that
returns an alternative rectangle.
*/
tooltipSpace?: (view: EditorView) => {
top: number;
left: number;
bottom: number;
right: number;
};
tooltipSpace?: (view: EditorView) => Rect;
}): Extension;
/**
Describes a tooltip. Values of this type, when provided through
@ -1551,9 +1662,22 @@ interface TooltipView {
*/
update?(update: ViewUpdate): void;
/**
Called when the tooltip has been (re)positioned.
Called when the tooltip is removed from the editor or the editor
is destroyed.
*/
positioned?(): void;
destroy?(): void;
/**
Called when the tooltip has been (re)positioned. The argument is
the [space](https://codemirror.net/6/docs/ref/#view.tooltips^config.tooltipSpace) available to the
tooltip.
*/
positioned?(space: Rect): void;
/**
By default, the library will restrict the size of tooltips so
that they don't stick out of the available space. Set this to
false to disable that.
*/
resize?: boolean;
}
/**
Facet to which an extension can add a value to show a tooltip.
@ -1790,4 +1914,17 @@ line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
*/
declare function highlightActiveLineGutter(): Extension;
export { BidiSpan, BlockInfo, BlockType, Command, DOMEventHandlers, DOMEventMap, Decoration, DecorationSet, Direction, EditorView, EditorViewConfig, GutterMarker, KeyBinding, MatchDecorator, MouseSelectionStyle, Panel, PanelConstructor, PluginSpec, PluginValue, Rect, Tooltip, TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, hoverTooltip, keymap, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
/**
Returns an extension that highlights whitespace, adding a
`cm-highlightSpace` class to stretches of spaces, and a
`cm-highlightTab` class to individual tab characters. By default,
the former are shown as faint dots, and the latter as arrows.
*/
declare function highlightWhitespace(): Extension;
/**
Returns an extension that adds a `cm-trailingSpace` class to all
trailing whitespace.
*/
declare function highlightTrailingWhitespace(): Extension;
export { BidiSpan, BlockInfo, BlockType, Command, DOMEventHandlers, DOMEventMap, Decoration, DecorationSet, Direction, EditorView, EditorViewConfig, GutterMarker, KeyBinding, LayerMarker, MatchDecorator, MouseSelectionStyle, Panel, PanelConstructor, PluginSpec, PluginValue, Rect, RectangleMarker, Tooltip, TooltipView, ViewPlugin, ViewUpdate, WidgetType, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/view",
"version": "6.0.0",
"version": "6.8.1",
"description": "DOM view component for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",
@ -12,7 +12,7 @@
],
"author": {
"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"email": "marijn@haverbeke.berlin",
"url": "http://marijnhaverbeke.nl"
},
"type": "module",
@ -26,7 +26,7 @@
"sideEffects": false,
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/state": "^6.1.4",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
},