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",