Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
2import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
3import { syntaxTree, indentUnit } from '@codemirror/language';
4
5/**
6An instance of this is passed to completion source functions.
7*/
8class CompletionContext {
9 /**
10 Create a new completion context. (Mostly useful for testing
11 completion sources—in the editor, the extension will create
12 these for you.)
13 */
14 constructor(
15 /**
16 The editor state that the completion happens in.
17 */
18 state,
19 /**
20 The position at which the completion is happening.
21 */
22 pos,
23 /**
24 Indicates whether completion was activated explicitly, or
25 implicitly by typing. The usual way to respond to this is to
26 only return completions when either there is part of a
27 completable entity before the cursor, or `explicit` is true.
28 */
29 explicit,
30 /**
31 The editor view. May be undefined if the context was created
32 in a situation where there is no such view available, such as
33 in synchronous updates via
34 [`CompletionResult.update`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.update)
35 or when called by test code.
36 */
37 view) {
38 this.state = state;
39 this.pos = pos;
40 this.explicit = explicit;
41 this.view = view;
42 /**
43 @internal
44 */
45 this.abortListeners = [];
46 /**
47 @internal
48 */
49 this.abortOnDocChange = false;
50 }
51 /**
52 Get the extent, content, and (if there is a token) type of the
53 token before `this.pos`.
54 */
55 tokenBefore(types) {
56 let token = syntaxTree(this.state).resolveInner(this.pos, -1);
57 while (token && types.indexOf(token.name) < 0)
58 token = token.parent;
59 return token ? { from: token.from, to: this.pos,
60 text: this.state.sliceDoc(token.from, this.pos),
61 type: token.type } : null;
62 }
63 /**
64 Get the match of the given expression directly before the
65 cursor.
66 */
67 matchBefore(expr) {
68 let line = this.state.doc.lineAt(this.pos);
69 let start = Math.max(line.from, this.pos - 250);
70 let str = line.text.slice(start - line.from, this.pos - line.from);
71 let found = str.search(ensureAnchor(expr, false));
72 return found < 0 ? null : { from: start + found, to: this.pos, text: str.slice(found) };
73 }
74 /**
75 Yields true when the query has been aborted. Can be useful in
76 asynchronous queries to avoid doing work that will be ignored.
77 */
78 get aborted() { return this.abortListeners == null; }
79 /**
80 Allows you to register abort handlers, which will be called when
81 the query is
82 [aborted](https://codemirror.net/6/docs/ref/#autocomplete.CompletionContext.aborted).
83
84 By default, running queries will not be aborted for regular
85 typing or backspacing, on the assumption that they are likely to
86 return a result with a
87 [`validFor`](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult.validFor) field that
88 allows the result to be used after all. Passing `onDocChange:
89 true` will cause this query to be aborted for any document
90 change.
91 */
92 addEventListener(type, listener, options) {
93 if (type == "abort" && this.abortListeners) {
94 this.abortListeners.push(listener);
95 if (options && options.onDocChange)
96 this.abortOnDocChange = true;
97 }
98 }
99}
100function toSet(chars) {
101 let flat = Object.keys(chars).join("");
102 let words = /\w/.test(flat);
103 if (words)
104 flat = flat.replace(/\w/g, "");
105 return `[${words ? "\\w" : ""}${flat.replace(/[^\w\s]/g, "\\$&")}]`;
106}
107function prefixMatch(options) {
108 let first = Object.create(null), rest = Object.create(null);
109 for (let { label } of options) {
110 first[label[0]] = true;
111 for (let i = 1; i < label.length; i++)
112 rest[label[i]] = true;
113 }
114 let source = toSet(first) + toSet(rest) + "*$";
115 return [new RegExp("^" + source), new RegExp(source)];
116}
117/**
118Given a a fixed array of options, return an autocompleter that
119completes them.
120*/
121function completeFromList(list) {
122 let options = list.map(o => typeof o == "string" ? { label: o } : o);
123 let [validFor, match] = options.every(o => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options);
124 return (context) => {
125 let token = context.matchBefore(match);
126 return token || context.explicit ? { from: token ? token.from : context.pos, options, validFor } : null;
127 };
128}
129/**
130Wrap the given completion source so that it will only fire when the
131cursor is in a syntax node with one of the given names.
132*/
133function ifIn(nodes, source) {
134 return (context) => {
135 for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
136 if (nodes.indexOf(pos.name) > -1)
137 return source(context);
138 if (pos.type.isTop)
139 break;
140 }
141 return null;
142 };
143}
144/**
145Wrap the given completion source so that it will not fire when the
146cursor is in a syntax node with one of the given names.
147*/
148function ifNotIn(nodes, source) {
149 return (context) => {
150 for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) {
151 if (nodes.indexOf(pos.name) > -1)
152 return null;
153 if (pos.type.isTop)
154 break;
155 }
156 return source(context);
157 };
158}
159class Option {
160 constructor(completion, source, match, score) {
161 this.completion = completion;
162 this.source = source;
163 this.match = match;
164 this.score = score;
165 }
166}
167function cur(state) { return state.selection.main.from; }
168// Make sure the given regexp has a $ at its end and, if `start` is
169// true, a ^ at its start.
170function ensureAnchor(expr, start) {
171 var _a;
172 let { source } = expr;
173 let addStart = start && source[0] != "^", addEnd = source[source.length - 1] != "$";
174 if (!addStart && !addEnd)
175 return expr;
176 return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : (expr.ignoreCase ? "i" : ""));
177}
178/**
179This annotation is added to transactions that are produced by
180picking a completion.
181*/
182const pickedCompletion = /*@__PURE__*/Annotation.define();
183/**
184Helper function that returns a transaction spec which inserts a
185completion's text in the main selection range, and any other
186selection range that has the same text in front of it.
187*/
188function insertCompletionText(state, text, from, to) {
189 let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
190 return {
191 ...state.changeByRange(range => {
192 if (range != main && from != to &&
193 state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
194 return { range };
195 let lines = state.toText(text);
196 return {
197 changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
198 range: EditorSelection.cursor(range.from + fromOff + lines.length)
199 };
200 }),
201 scrollIntoView: true,
202 userEvent: "input.complete"
203 };
204}
205const SourceCache = /*@__PURE__*/new WeakMap();
206function asSource(source) {
207 if (!Array.isArray(source))
208 return source;
209 let known = SourceCache.get(source);
210 if (!known)
211 SourceCache.set(source, known = completeFromList(source));
212 return known;
213}
214const startCompletionEffect = /*@__PURE__*/StateEffect.define();
215const closeCompletionEffect = /*@__PURE__*/StateEffect.define();
216
217// A pattern matcher for fuzzy completion matching. Create an instance
218// once for a pattern, and then use that to match any number of
219// completions.
220class FuzzyMatcher {
221 constructor(pattern) {
222 this.pattern = pattern;
223 this.chars = [];
224 this.folded = [];
225 // Buffers reused by calls to `match` to track matched character
226 // positions.
227 this.any = [];
228 this.precise = [];
229 this.byWord = [];
230 this.score = 0;
231 this.matched = [];
232 for (let p = 0; p < pattern.length;) {
233 let char = codePointAt(pattern, p), size = codePointSize(char);
234 this.chars.push(char);
235 let part = pattern.slice(p, p + size), upper = part.toUpperCase();
236 this.folded.push(codePointAt(upper == part ? part.toLowerCase() : upper, 0));
237 p += size;
238 }
239 this.astral = pattern.length != this.chars.length;
240 }
241 ret(score, matched) {
242 this.score = score;
243 this.matched = matched;
244 return this;
245 }
246 // Matches a given word (completion) against the pattern (input).
247 // Will return a boolean indicating whether there was a match and,
248 // on success, set `this.score` to the score, `this.matched` to an
249 // array of `from, to` pairs indicating the matched parts of `word`.
250 //
251 // The score is a number that is more negative the worse the match
252 // is. See `Penalty` above.
253 match(word) {
254 if (this.pattern.length == 0)
255 return this.ret(-100 /* Penalty.NotFull */, []);
256 if (word.length < this.pattern.length)
257 return null;
258 let { chars, folded, any, precise, byWord } = this;
259 // For single-character queries, only match when they occur right
260 // at the start
261 if (chars.length == 1) {
262 let first = codePointAt(word, 0), firstSize = codePointSize(first);
263 let score = firstSize == word.length ? 0 : -100 /* Penalty.NotFull */;
264 if (first == chars[0]) ;
265 else if (first == folded[0])
266 score += -200 /* Penalty.CaseFold */;
267 else
268 return null;
269 return this.ret(score, [0, firstSize]);
270 }
271 let direct = word.indexOf(this.pattern);
272 if (direct == 0)
273 return this.ret(word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */, [0, this.pattern.length]);
274 let len = chars.length, anyTo = 0;
275 if (direct < 0) {
276 for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len;) {
277 let next = codePointAt(word, i);
278 if (next == chars[anyTo] || next == folded[anyTo])
279 any[anyTo++] = i;
280 i += codePointSize(next);
281 }
282 // No match, exit immediately
283 if (anyTo < len)
284 return null;
285 }
286 // This tracks the extent of the precise (non-folded, not
287 // necessarily adjacent) match
288 let preciseTo = 0;
289 // Tracks whether there is a match that hits only characters that
290 // appear to be starting words. `byWordFolded` is set to true when
291 // a case folded character is encountered in such a match
292 let byWordTo = 0, byWordFolded = false;
293 // If we've found a partial adjacent match, these track its state
294 let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1;
295 let hasLower = /[a-z]/.test(word), wordAdjacent = true;
296 // Go over the option's text, scanning for the various kinds of matches
297 for (let i = 0, e = Math.min(word.length, 200), prevType = 0 /* Tp.NonWord */; i < e && byWordTo < len;) {
298 let next = codePointAt(word, i);
299 if (direct < 0) {
300 if (preciseTo < len && next == chars[preciseTo])
301 precise[preciseTo++] = i;
302 if (adjacentTo < len) {
303 if (next == chars[adjacentTo] || next == folded[adjacentTo]) {
304 if (adjacentTo == 0)
305 adjacentStart = i;
306 adjacentEnd = i + 1;
307 adjacentTo++;
308 }
309 else {
310 adjacentTo = 0;
311 }
312 }
313 }
314 let ch, type = next < 0xff
315 ? (next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 /* Tp.Lower */ : next >= 65 && next <= 90 ? 1 /* Tp.Upper */ : 0 /* Tp.NonWord */)
316 : ((ch = fromCodePoint(next)) != ch.toLowerCase() ? 1 /* Tp.Upper */ : ch != ch.toUpperCase() ? 2 /* Tp.Lower */ : 0 /* Tp.NonWord */);
317 if (!i || type == 1 /* Tp.Upper */ && hasLower || prevType == 0 /* Tp.NonWord */ && type != 0 /* Tp.NonWord */) {
318 if (chars[byWordTo] == next || (folded[byWordTo] == next && (byWordFolded = true)))
319 byWord[byWordTo++] = i;
320 else if (byWord.length)
321 wordAdjacent = false;
322 }
323 prevType = type;
324 i += codePointSize(next);
325 }
326 if (byWordTo == len && byWord[0] == 0 && wordAdjacent)
327 return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0), byWord, word);
328 if (adjacentTo == len && adjacentStart == 0)
329 return this.ret(-200 /* Penalty.CaseFold */ - word.length + (adjacentEnd == word.length ? 0 : -100 /* Penalty.NotFull */), [0, adjacentEnd]);
330 if (direct > -1)
331 return this.ret(-700 /* Penalty.NotStart */ - word.length, [direct, direct + this.pattern.length]);
332 if (adjacentTo == len)
333 return this.ret(-200 /* Penalty.CaseFold */ + -700 /* Penalty.NotStart */ - word.length, [adjacentStart, adjacentEnd]);
334 if (byWordTo == len)
335 return this.result(-100 /* Penalty.ByWord */ + (byWordFolded ? -200 /* Penalty.CaseFold */ : 0) + -700 /* Penalty.NotStart */ +
336 (wordAdjacent ? 0 : -1100 /* Penalty.Gap */), byWord, word);
337 return chars.length == 2 ? null
338 : this.result((any[0] ? -700 /* Penalty.NotStart */ : 0) + -200 /* Penalty.CaseFold */ + -1100 /* Penalty.Gap */, any, word);
339 }
340 result(score, positions, word) {
341 let result = [], i = 0;
342 for (let pos of positions) {
343 let to = pos + (this.astral ? codePointSize(codePointAt(word, pos)) : 1);
344 if (i && result[i - 1] == pos)
345 result[i - 1] = to;
346 else {
347 result[i++] = pos;
348 result[i++] = to;
349 }
350 }
351 return this.ret(score - word.length, result);
352 }
353}
354class StrictMatcher {
355 constructor(pattern) {
356 this.pattern = pattern;
357 this.matched = [];
358 this.score = 0;
359 this.folded = pattern.toLowerCase();
360 }
361 match(word) {
362 if (word.length < this.pattern.length)
363 return null;
364 let start = word.slice(0, this.pattern.length);
365 let match = start == this.pattern ? 0 : start.toLowerCase() == this.folded ? -200 /* Penalty.CaseFold */ : null;
366 if (match == null)
367 return null;
368 this.matched = [0, start.length];
369 this.score = match + (word.length == this.pattern.length ? 0 : -100 /* Penalty.NotFull */);
370 return this;
371 }
372}
373
374const completionConfig = /*@__PURE__*/Facet.define({
375 combine(configs) {
376 return combineConfig(configs, {
377 activateOnTyping: true,
378 activateOnCompletion: () => false,
379 activateOnTypingDelay: 100,
380 selectOnOpen: true,
381 override: null,
382 closeOnBlur: true,
383 maxRenderedOptions: 100,
384 defaultKeymap: true,
385 tooltipClass: () => "",
386 optionClass: () => "",
387 aboveCursor: false,
388 icons: true,
389 addToOptions: [],
390 positionInfo: defaultPositionInfo,
391 filterStrict: false,
392 compareCompletions: (a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label),
393 interactionDelay: 75,
394 updateSyncTime: 100
395 }, {
396 defaultKeymap: (a, b) => a && b,
397 closeOnBlur: (a, b) => a && b,
398 icons: (a, b) => a && b,
399 tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
400 optionClass: (a, b) => c => joinClass(a(c), b(c)),
401 addToOptions: (a, b) => a.concat(b),
402 filterStrict: (a, b) => a || b,
403 });
404 }
405});
406function joinClass(a, b) {
407 return a ? b ? a + " " + b : a : b;
408}
409function defaultPositionInfo(view, list, option, info, space, tooltip) {
410 let rtl = view.textDirection == Direction.RTL, left = rtl, narrow = false;
411 let side = "top", offset, maxWidth;
412 let spaceLeft = list.left - space.left, spaceRight = space.right - list.right;
413 let infoWidth = info.right - info.left, infoHeight = info.bottom - info.top;
414 if (left && spaceLeft < Math.min(infoWidth, spaceRight))
415 left = false;
416 else if (!left && spaceRight < Math.min(infoWidth, spaceLeft))
417 left = true;
418 if (infoWidth <= (left ? spaceLeft : spaceRight)) {
419 offset = Math.max(space.top, Math.min(option.top, space.bottom - infoHeight)) - list.top;
420 maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight);
421 }
422 else {
423 narrow = true;
424 maxWidth = Math.min(400 /* Info.Width */, (rtl ? list.right : space.right - list.left) - 30 /* Info.Margin */);
425 let spaceBelow = space.bottom - list.bottom;
426 if (spaceBelow >= infoHeight || spaceBelow > list.top) { // Below the completion
427 offset = option.bottom - list.top;
428 }
429 else { // Above it
430 side = "bottom";
431 offset = list.bottom - option.top;
432 }
433 }
434 let scaleY = (list.bottom - list.top) / tooltip.offsetHeight;
435 let scaleX = (list.right - list.left) / tooltip.offsetWidth;
436 return {
437 style: `${side}: ${offset / scaleY}px; max-width: ${maxWidth / scaleX}px`,
438 class: "cm-completionInfo-" + (narrow ? (rtl ? "left-narrow" : "right-narrow") : left ? "left" : "right")
439 };
440}
441
442const setSelectedEffect = /*@__PURE__*/StateEffect.define();
443function optionContent(config) {
444 let content = config.addToOptions.slice();
445 if (config.icons)
446 content.push({
447 render(completion) {
448 let icon = document.createElement("div");
449 icon.classList.add("cm-completionIcon");
450 if (completion.type)
451 icon.classList.add(...completion.type.split(/\s+/g).map(cls => "cm-completionIcon-" + cls));
452 icon.setAttribute("aria-hidden", "true");
453 return icon;
454 },
455 position: 20
456 });
457 content.push({
458 render(completion, _s, _v, match) {
459 let labelElt = document.createElement("span");
460 labelElt.className = "cm-completionLabel";
461 let label = completion.displayLabel || completion.label, off = 0;
462 for (let j = 0; j < match.length;) {
463 let from = match[j++], to = match[j++];
464 if (from > off)
465 labelElt.appendChild(document.createTextNode(label.slice(off, from)));
466 let span = labelElt.appendChild(document.createElement("span"));
467 span.appendChild(document.createTextNode(label.slice(from, to)));
468 span.className = "cm-completionMatchedText";
469 off = to;
470 }
471 if (off < label.length)
472 labelElt.appendChild(document.createTextNode(label.slice(off)));
473 return labelElt;
474 },
475 position: 50
476 }, {
477 render(completion) {
478 if (!completion.detail)
479 return null;
480 let detailElt = document.createElement("span");
481 detailElt.className = "cm-completionDetail";
482 detailElt.textContent = completion.detail;
483 return detailElt;
484 },
485 position: 80
486 });
487 return content.sort((a, b) => a.position - b.position).map(a => a.render);
488}
489function rangeAroundSelected(total, selected, max) {
490 if (total <= max)
491 return { from: 0, to: total };
492 if (selected < 0)
493 selected = 0;
494 if (selected <= (total >> 1)) {
495 let off = Math.floor(selected / max);
496 return { from: off * max, to: (off + 1) * max };
497 }
498 let off = Math.floor((total - selected) / max);
499 return { from: total - (off + 1) * max, to: total - off * max };
500}
501class CompletionTooltip {
502 constructor(view, stateField, applyCompletion) {
503 this.view = view;
504 this.stateField = stateField;
505 this.applyCompletion = applyCompletion;
506 this.info = null;
507 this.infoDestroy = null;
508 this.placeInfoReq = {
509 read: () => this.measureInfo(),
510 write: (pos) => this.placeInfo(pos),
511 key: this
512 };
513 this.space = null;
514 this.currentClass = "";
515 let cState = view.state.field(stateField);
516 let { options, selected } = cState.open;
517 let config = view.state.facet(completionConfig);
518 this.optionContent = optionContent(config);
519 this.optionClass = config.optionClass;
520 this.tooltipClass = config.tooltipClass;
521 this.range = rangeAroundSelected(options.length, selected, config.maxRenderedOptions);
522 this.dom = document.createElement("div");
523 this.dom.className = "cm-tooltip-autocomplete";
524 this.updateTooltipClass(view.state);
525 this.dom.addEventListener("mousedown", (e) => {
526 let { options } = view.state.field(stateField).open;
527 for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) {
528 if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) {
529 this.applyCompletion(view, options[+match[1]]);
530 e.preventDefault();
531 return;
532 }
533 }
534 if (e.target == this.list) {
535 let move = this.list.classList.contains("cm-completionListIncompleteTop") &&
536 e.clientY < this.list.firstChild.getBoundingClientRect().top ? this.range.from - 1 :
537 this.list.classList.contains("cm-completionListIncompleteBottom") &&
538 e.clientY > this.list.lastChild.getBoundingClientRect().bottom ? this.range.to : null;
539 if (move != null) {
540 view.dispatch({ effects: setSelectedEffect.of(move) });
541 e.preventDefault();
542 }
543 }
544 });
545 this.dom.addEventListener("focusout", (e) => {
546 let state = view.state.field(this.stateField, false);
547 if (state && state.tooltip && view.state.facet(completionConfig).closeOnBlur &&
548 e.relatedTarget != view.contentDOM)
549 view.dispatch({ effects: closeCompletionEffect.of(null) });
550 });
551 this.showOptions(options, cState.id);
552 }
553 mount() { this.updateSel(); }
554 showOptions(options, id) {
555 if (this.list)
556 this.list.remove();
557 this.list = this.dom.appendChild(this.createListBox(options, id, this.range));
558 this.list.addEventListener("scroll", () => {
559 if (this.info)
560 this.view.requestMeasure(this.placeInfoReq);
561 });
562 }
563 update(update) {
564 var _a;
565 let cState = update.state.field(this.stateField);
566 let prevState = update.startState.field(this.stateField);
567 this.updateTooltipClass(update.state);
568 if (cState != prevState) {
569 let { options, selected, disabled } = cState.open;
570 if (!prevState.open || prevState.open.options != options) {
571 this.range = rangeAroundSelected(options.length, selected, update.state.facet(completionConfig).maxRenderedOptions);
572 this.showOptions(options, cState.id);
573 }
574 this.updateSel();
575 if (disabled != ((_a = prevState.open) === null || _a === void 0 ? void 0 : _a.disabled))
576 this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!disabled);
577 }
578 }
579 updateTooltipClass(state) {
580 let cls = this.tooltipClass(state);
581 if (cls != this.currentClass) {
582 for (let c of this.currentClass.split(" "))
583 if (c)
584 this.dom.classList.remove(c);
585 for (let c of cls.split(" "))
586 if (c)
587 this.dom.classList.add(c);
588 this.currentClass = cls;
589 }
590 }
591 positioned(space) {
592 this.space = space;
593 if (this.info)
594 this.view.requestMeasure(this.placeInfoReq);
595 }
596 updateSel() {
597 let cState = this.view.state.field(this.stateField), open = cState.open;
598 if (open.selected > -1 && open.selected < this.range.from || open.selected >= this.range.to) {
599 this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions);
600 this.showOptions(open.options, cState.id);
601 }
602 let newSel = this.updateSelectedOption(open.selected);
603 if (newSel) {
604 this.destroyInfo();
605 let { completion } = open.options[open.selected];
606 let { info } = completion;
607 if (!info)
608 return;
609 let infoResult = typeof info === "string" ? document.createTextNode(info) : info(completion);
610 if (!infoResult)
611 return;
612 if ("then" in infoResult) {
613 infoResult.then(obj => {
614 if (obj && this.view.state.field(this.stateField, false) == cState)
615 this.addInfoPane(obj, completion);
616 }).catch(e => logException(this.view.state, e, "completion info"));
617 }
618 else {
619 this.addInfoPane(infoResult, completion);
620 newSel.setAttribute("aria-describedby", this.info.id);
621 }
622 }
623 }
624 addInfoPane(content, completion) {
625 this.destroyInfo();
626 let wrap = this.info = document.createElement("div");
627 wrap.className = "cm-tooltip cm-completionInfo";
628 wrap.id = "cm-completionInfo-" + Math.floor(Math.random() * 0xffff).toString(16);
629 if (content.nodeType != null) {
630 wrap.appendChild(content);
631 this.infoDestroy = null;
632 }
633 else {
634 let { dom, destroy } = content;
635 wrap.appendChild(dom);
636 this.infoDestroy = destroy || null;
637 }
638 this.dom.appendChild(wrap);
639 this.view.requestMeasure(this.placeInfoReq);
640 }
641 updateSelectedOption(selected) {
642 let set = null;
643 for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) {
644 if (opt.nodeName != "LI" || !opt.id) {
645 i--; // A section header
646 }
647 else if (i == selected) {
648 if (!opt.hasAttribute("aria-selected")) {
649 opt.setAttribute("aria-selected", "true");
650 set = opt;
651 }
652 }
653 else {
654 if (opt.hasAttribute("aria-selected")) {
655 opt.removeAttribute("aria-selected");
656 opt.removeAttribute("aria-describedby");
657 }
658 }
659 }
660 if (set)
661 scrollIntoView(this.list, set);
662 return set;
663 }
664 measureInfo() {
665 let sel = this.dom.querySelector("[aria-selected]");
666 if (!sel || !this.info)
667 return null;
668 let listRect = this.dom.getBoundingClientRect();
669 let infoRect = this.info.getBoundingClientRect();
670 let selRect = sel.getBoundingClientRect();
671 let space = this.space;
672 if (!space) {
673 let docElt = this.dom.ownerDocument.documentElement;
674 space = { left: 0, top: 0, right: docElt.clientWidth, bottom: docElt.clientHeight };
675 }
676 if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
677 selRect.bottom < Math.max(space.top, listRect.top) + 10)
678 return null;
679 return this.view.state.facet(completionConfig).positionInfo(this.view, listRect, selRect, infoRect, space, this.dom);
680 }
681 placeInfo(pos) {
682 if (this.info) {
683 if (pos) {
684 if (pos.style)
685 this.info.style.cssText = pos.style;
686 this.info.className = "cm-tooltip cm-completionInfo " + (pos.class || "");
687 }
688 else {
689 this.info.style.cssText = "top: -1e6px";
690 }
691 }
692 }
693 createListBox(options, id, range) {
694 const ul = document.createElement("ul");
695 ul.id = id;
696 ul.setAttribute("role", "listbox");
697 ul.setAttribute("aria-expanded", "true");
698 ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
699 ul.addEventListener("mousedown", e => {
700 // Prevent focus change when clicking the scrollbar
701 if (e.target == ul)
702 e.preventDefault();
703 });
704 let curSection = null;
705 for (let i = range.from; i < range.to; i++) {
706 let { completion, match } = options[i], { section } = completion;
707 if (section) {
708 let name = typeof section == "string" ? section : section.name;
709 if (name != curSection && (i > range.from || range.from == 0)) {
710 curSection = name;
711 if (typeof section != "string" && section.header) {
712 ul.appendChild(section.header(section));
713 }
714 else {
715 let header = ul.appendChild(document.createElement("completion-section"));
716 header.textContent = name;
717 }
718 }
719 }
720 const li = ul.appendChild(document.createElement("li"));
721 li.id = id + "-" + i;
722 li.setAttribute("role", "option");
723 let cls = this.optionClass(completion);
724 if (cls)
725 li.className = cls;
726 for (let source of this.optionContent) {
727 let node = source(completion, this.view.state, this.view, match);
728 if (node)
729 li.appendChild(node);
730 }
731 }
732 if (range.from)
733 ul.classList.add("cm-completionListIncompleteTop");
734 if (range.to < options.length)
735 ul.classList.add("cm-completionListIncompleteBottom");
736 return ul;
737 }
738 destroyInfo() {
739 if (this.info) {
740 if (this.infoDestroy)
741 this.infoDestroy();
742 this.info.remove();
743 this.info = null;
744 }
745 }
746 destroy() {
747 this.destroyInfo();
748 }
749}
750function completionTooltip(stateField, applyCompletion) {
751 return (view) => new CompletionTooltip(view, stateField, applyCompletion);
752}
753function scrollIntoView(container, element) {
754 let parent = container.getBoundingClientRect();
755 let self = element.getBoundingClientRect();
756 let scaleY = parent.height / container.offsetHeight;
757 if (self.top < parent.top)
758 container.scrollTop -= (parent.top - self.top) / scaleY;
759 else if (self.bottom > parent.bottom)
760 container.scrollTop += (self.bottom - parent.bottom) / scaleY;
761}
762
763// Used to pick a preferred option when two options with the same
764// label occur in the result.
765function score(option) {
766 return (option.boost || 0) * 100 + (option.apply ? 10 : 0) + (option.info ? 5 : 0) +
767 (option.type ? 1 : 0);
768}
769function sortOptions(active, state) {
770 let options = [];
771 let sections = null, dynamicSectionScore = null;
772 let addOption = (option) => {
773 options.push(option);
774 let { section } = option.completion;
775 if (section) {
776 if (!sections)
777 sections = [];
778 let name = typeof section == "string" ? section : section.name;
779 if (!sections.some(s => s.name == name))
780 sections.push(typeof section == "string" ? { name } : section);
781 }
782 };
783 let conf = state.facet(completionConfig);
784 for (let a of active)
785 if (a.hasResult()) {
786 let getMatch = a.result.getMatch;
787 if (a.result.filter === false) {
788 for (let option of a.result.options) {
789 addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
790 }
791 }
792 else {
793 let pattern = state.sliceDoc(a.from, a.to), match;
794 let matcher = conf.filterStrict ? new StrictMatcher(pattern) : new FuzzyMatcher(pattern);
795 for (let option of a.result.options)
796 if (match = matcher.match(option.label)) {
797 let matched = !option.displayLabel ? match.matched : getMatch ? getMatch(option, match.matched) : [];
798 let score = match.score + (option.boost || 0);
799 addOption(new Option(option, a.source, matched, score));
800 if (typeof option.section == "object" && option.section.rank === "dynamic") {
801 let { name } = option.section;
802 if (!dynamicSectionScore)
803 dynamicSectionScore = Object.create(null);
804 dynamicSectionScore[name] = Math.max(score, dynamicSectionScore[name] || -1e9);
805 }
806 }
807 }
808 }
809 if (sections) {
810 let sectionOrder = Object.create(null), pos = 0;
811 let cmp = (a, b) => {
812 return (a.rank === "dynamic" && b.rank === "dynamic" ? dynamicSectionScore[b.name] - dynamicSectionScore[a.name] : 0) ||
813 (typeof a.rank == "number" ? a.rank : 1e9) - (typeof b.rank == "number" ? b.rank : 1e9) ||
814 (a.name < b.name ? -1 : 1);
815 };
816 for (let s of sections.sort(cmp)) {
817 pos -= 1e5;
818 sectionOrder[s.name] = pos;
819 }
820 for (let option of options) {
821 let { section } = option.completion;
822 if (section)
823 option.score += sectionOrder[typeof section == "string" ? section : section.name];
824 }
825 }
826 let result = [], prev = null;
827 let compare = conf.compareCompletions;
828 for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
829 let cur = opt.completion;
830 if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
831 (prev.type != null && cur.type != null && prev.type != cur.type) ||
832 prev.apply != cur.apply || prev.boost != cur.boost)
833 result.push(opt);
834 else if (score(opt.completion) > score(prev))
835 result[result.length - 1] = opt;
836 prev = opt.completion;
837 }
838 return result;
839}
840class CompletionDialog {
841 constructor(options, attrs, tooltip, timestamp, selected, disabled) {
842 this.options = options;
843 this.attrs = attrs;
844 this.tooltip = tooltip;
845 this.timestamp = timestamp;
846 this.selected = selected;
847 this.disabled = disabled;
848 }
849 setSelected(selected, id) {
850 return selected == this.selected || selected >= this.options.length ? this
851 : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
852 }
853 static build(active, state, id, prev, conf, didSetActive) {
854 if (prev && !didSetActive && active.some(s => s.isPending))
855 return prev.setDisabled();
856 let options = sortOptions(active, state);
857 if (!options.length)
858 return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
859 let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
860 if (prev && prev.selected != selected && prev.selected != -1) {
861 let selectedValue = prev.options[prev.selected].completion;
862 for (let i = 0; i < options.length; i++)
863 if (options[i].completion == selectedValue) {
864 selected = i;
865 break;
866 }
867 }
868 return new CompletionDialog(options, makeAttrs(id, selected), {
869 pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
870 create: createTooltip,
871 above: conf.aboveCursor,
872 }, prev ? prev.timestamp : Date.now(), selected, false);
873 }
874 map(changes) {
875 return new CompletionDialog(this.options, this.attrs, { ...this.tooltip, pos: changes.mapPos(this.tooltip.pos) }, this.timestamp, this.selected, this.disabled);
876 }
877 setDisabled() {
878 return new CompletionDialog(this.options, this.attrs, this.tooltip, this.timestamp, this.selected, true);
879 }
880}
881class CompletionState {
882 constructor(active, id, open) {
883 this.active = active;
884 this.id = id;
885 this.open = open;
886 }
887 static start() {
888 return new CompletionState(none, "cm-ac-" + Math.floor(Math.random() * 2e6).toString(36), null);
889 }
890 update(tr) {
891 let { state } = tr, conf = state.facet(completionConfig);
892 let sources = conf.override ||
893 state.languageDataAt("autocomplete", cur(state)).map(asSource);
894 let active = sources.map(source => {
895 let value = this.active.find(s => s.source == source) ||
896 new ActiveSource(source, this.active.some(a => a.state != 0 /* State.Inactive */) ? 1 /* State.Pending */ : 0 /* State.Inactive */);
897 return value.update(tr, conf);
898 });
899 if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
900 active = this.active;
901 let open = this.open, didSet = tr.effects.some(e => e.is(setActiveEffect));
902 if (open && tr.docChanged)
903 open = open.map(tr.changes);
904 if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
905 !sameResults(active, this.active) || didSet)
906 open = CompletionDialog.build(active, state, this.id, open, conf, didSet);
907 else if (open && open.disabled && !active.some(a => a.isPending))
908 open = null;
909 if (!open && active.every(a => !a.isPending) && active.some(a => a.hasResult()))
910 active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
911 for (let effect of tr.effects)
912 if (effect.is(setSelectedEffect))
913 open = open && open.setSelected(effect.value, this.id);
914 return active == this.active && open == this.open ? this : new CompletionState(active, this.id, open);
915 }
916 get tooltip() { return this.open ? this.open.tooltip : null; }
917 get attrs() { return this.open ? this.open.attrs : this.active.length ? baseAttrs : noAttrs; }
918}
919function sameResults(a, b) {
920 if (a == b)
921 return true;
922 for (let iA = 0, iB = 0;;) {
923 while (iA < a.length && !a[iA].hasResult())
924 iA++;
925 while (iB < b.length && !b[iB].hasResult())
926 iB++;
927 let endA = iA == a.length, endB = iB == b.length;
928 if (endA || endB)
929 return endA == endB;
930 if (a[iA++].result != b[iB++].result)
931 return false;
932 }
933}
934const baseAttrs = {
935 "aria-autocomplete": "list"
936};
937const noAttrs = {};
938function makeAttrs(id, selected) {
939 let result = {
940 "aria-autocomplete": "list",
941 "aria-haspopup": "listbox",
942 "aria-controls": id
943 };
944 if (selected > -1)
945 result["aria-activedescendant"] = id + "-" + selected;
946 return result;
947}
948const none = [];
949function getUpdateType(tr, conf) {
950 if (tr.isUserEvent("input.complete")) {
951 let completion = tr.annotation(pickedCompletion);
952 if (completion && conf.activateOnCompletion(completion))
953 return 4 /* UpdateType.Activate */ | 8 /* UpdateType.Reset */;
954 }
955 let typing = tr.isUserEvent("input.type");
956 return typing && conf.activateOnTyping ? 4 /* UpdateType.Activate */ | 1 /* UpdateType.Typing */
957 : typing ? 1 /* UpdateType.Typing */
958 : tr.isUserEvent("delete.backward") ? 2 /* UpdateType.Backspacing */
959 : tr.selection ? 8 /* UpdateType.Reset */
960 : tr.docChanged ? 16 /* UpdateType.ResetIfTouching */ : 0 /* UpdateType.None */;
961}
962class ActiveSource {
963 constructor(source, state, explicit = false) {
964 this.source = source;
965 this.state = state;
966 this.explicit = explicit;
967 }
968 hasResult() { return false; }
969 get isPending() { return this.state == 1 /* State.Pending */; }
970 update(tr, conf) {
971 let type = getUpdateType(tr, conf), value = this;
972 if ((type & 8 /* UpdateType.Reset */) || (type & 16 /* UpdateType.ResetIfTouching */) && this.touches(tr))
973 value = new ActiveSource(value.source, 0 /* State.Inactive */);
974 if ((type & 4 /* UpdateType.Activate */) && value.state == 0 /* State.Inactive */)
975 value = new ActiveSource(this.source, 1 /* State.Pending */);
976 value = value.updateFor(tr, type);
977 for (let effect of tr.effects) {
978 if (effect.is(startCompletionEffect))
979 value = new ActiveSource(value.source, 1 /* State.Pending */, effect.value);
980 else if (effect.is(closeCompletionEffect))
981 value = new ActiveSource(value.source, 0 /* State.Inactive */);
982 else if (effect.is(setActiveEffect))
983 for (let active of effect.value)
984 if (active.source == value.source)
985 value = active;
986 }
987 return value;
988 }
989 updateFor(tr, type) { return this.map(tr.changes); }
990 map(changes) { return this; }
991 touches(tr) {
992 return tr.changes.touchesRange(cur(tr.state));
993 }
994}
995class ActiveResult extends ActiveSource {
996 constructor(source, explicit, limit, result, from, to) {
997 super(source, 3 /* State.Result */, explicit);
998 this.limit = limit;
999 this.result = result;
1000 this.from = from;
1001 this.to = to;
1002 }
1003 hasResult() { return true; }
1004 updateFor(tr, type) {
1005 var _a;
1006 if (!(type & 3 /* UpdateType.SimpleInteraction */))
1007 return this.map(tr.changes);
1008 let result = this.result;
1009 if (result.map && !tr.changes.empty)
1010 result = result.map(result, tr.changes);
1011 let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1);
1012 let pos = cur(tr.state);
1013 if (pos > to || !result ||
1014 (type & 2 /* UpdateType.Backspacing */) && (cur(tr.startState) == this.from || pos < this.limit))
1015 return new ActiveSource(this.source, type & 4 /* UpdateType.Activate */ ? 1 /* State.Pending */ : 0 /* State.Inactive */);
1016 let limit = tr.changes.mapPos(this.limit);
1017 if (checkValid(result.validFor, tr.state, from, to))
1018 return new ActiveResult(this.source, this.explicit, limit, result, from, to);
1019 if (result.update &&
1020 (result = result.update(result, from, to, new CompletionContext(tr.state, pos, false))))
1021 return new ActiveResult(this.source, this.explicit, limit, result, result.from, (_a = result.to) !== null && _a !== void 0 ? _a : cur(tr.state));
1022 return new ActiveSource(this.source, 1 /* State.Pending */, this.explicit);
1023 }
1024 map(mapping) {
1025 if (mapping.empty)
1026 return this;
1027 let result = this.result.map ? this.result.map(this.result, mapping) : this.result;
1028 if (!result)
1029 return new ActiveSource(this.source, 0 /* State.Inactive */);
1030 return new ActiveResult(this.source, this.explicit, mapping.mapPos(this.limit), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1));
1031 }
1032 touches(tr) {
1033 return tr.changes.touchesRange(this.from, this.to);
1034 }
1035}
1036function checkValid(validFor, state, from, to) {
1037 if (!validFor)
1038 return false;
1039 let text = state.sliceDoc(from, to);
1040 return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text);
1041}
1042const setActiveEffect = /*@__PURE__*/StateEffect.define({
1043 map(sources, mapping) { return sources.map(s => s.map(mapping)); }
1044});
1045const completionState = /*@__PURE__*/StateField.define({
1046 create() { return CompletionState.start(); },
1047 update(value, tr) { return value.update(tr); },
1048 provide: f => [
1049 showTooltip.from(f, val => val.tooltip),
1050 EditorView.contentAttributes.from(f, state => state.attrs)
1051 ]
1052});
1053function applyCompletion(view, option) {
1054 const apply = option.completion.apply || option.completion.label;
1055 let result = view.state.field(completionState).active.find(a => a.source == option.source);
1056 if (!(result instanceof ActiveResult))
1057 return false;
1058 if (typeof apply == "string")
1059 view.dispatch({
1060 ...insertCompletionText(view.state, apply, result.from, result.to),
1061 annotations: pickedCompletion.of(option.completion)
1062 });
1063 else
1064 apply(view, option.completion, result.from, result.to);
1065 return true;
1066}
1067const createTooltip = /*@__PURE__*/completionTooltip(completionState, applyCompletion);
1068
1069/**
1070Returns a command that moves the completion selection forward or
1071backward by the given amount.
1072*/
1073function moveCompletionSelection(forward, by = "option") {
1074 return (view) => {
1075 let cState = view.state.field(completionState, false);
1076 if (!cState || !cState.open || cState.open.disabled ||
1077 Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
1078 return false;
1079 let step = 1, tooltip;
1080 if (by == "page" && (tooltip = getTooltip(view, cState.open.tooltip)))
1081 step = Math.max(2, Math.floor(tooltip.dom.offsetHeight /
1082 tooltip.dom.querySelector("li").offsetHeight) - 1);
1083 let { length } = cState.open.options;
1084 let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1;
1085 if (selected < 0)
1086 selected = by == "page" ? 0 : length - 1;
1087 else if (selected >= length)
1088 selected = by == "page" ? length - 1 : 0;
1089 view.dispatch({ effects: setSelectedEffect.of(selected) });
1090 return true;
1091 };
1092}
1093/**
1094Accept the current completion.
1095*/
1096const acceptCompletion = (view) => {
1097 let cState = view.state.field(completionState, false);
1098 if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 || cState.open.disabled ||
1099 Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
1100 return false;
1101 return applyCompletion(view, cState.open.options[cState.open.selected]);
1102};
1103/**
1104Explicitly start autocompletion.
1105*/
1106const startCompletion = (view) => {
1107 let cState = view.state.field(completionState, false);
1108 if (!cState)
1109 return false;
1110 view.dispatch({ effects: startCompletionEffect.of(true) });
1111 return true;
1112};
1113/**
1114Close the currently active completion.
1115*/
1116const closeCompletion = (view) => {
1117 let cState = view.state.field(completionState, false);
1118 if (!cState || !cState.active.some(a => a.state != 0 /* State.Inactive */))
1119 return false;
1120 view.dispatch({ effects: closeCompletionEffect.of(null) });
1121 return true;
1122};
1123class RunningQuery {
1124 constructor(active, context) {
1125 this.active = active;
1126 this.context = context;
1127 this.time = Date.now();
1128 this.updates = [];
1129 // Note that 'undefined' means 'not done yet', whereas 'null' means
1130 // 'query returned null'.
1131 this.done = undefined;
1132 }
1133}
1134const MaxUpdateCount = 50, MinAbortTime = 1000;
1135const completionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
1136 constructor(view) {
1137 this.view = view;
1138 this.debounceUpdate = -1;
1139 this.running = [];
1140 this.debounceAccept = -1;
1141 this.pendingStart = false;
1142 this.composing = 0 /* CompositionState.None */;
1143 for (let active of view.state.field(completionState).active)
1144 if (active.isPending)
1145 this.startQuery(active);
1146 }
1147 update(update) {
1148 let cState = update.state.field(completionState);
1149 let conf = update.state.facet(completionConfig);
1150 if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState)
1151 return;
1152 let doesReset = update.transactions.some(tr => {
1153 let type = getUpdateType(tr, conf);
1154 return (type & 8 /* UpdateType.Reset */) || (tr.selection || tr.docChanged) && !(type & 3 /* UpdateType.SimpleInteraction */);
1155 });
1156 for (let i = 0; i < this.running.length; i++) {
1157 let query = this.running[i];
1158 if (doesReset ||
1159 query.context.abortOnDocChange && update.docChanged ||
1160 query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) {
1161 for (let handler of query.context.abortListeners) {
1162 try {
1163 handler();
1164 }
1165 catch (e) {
1166 logException(this.view.state, e);
1167 }
1168 }
1169 query.context.abortListeners = null;
1170 this.running.splice(i--, 1);
1171 }
1172 else {
1173 query.updates.push(...update.transactions);
1174 }
1175 }
1176 if (this.debounceUpdate > -1)
1177 clearTimeout(this.debounceUpdate);
1178 if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect))))
1179 this.pendingStart = true;
1180 let delay = this.pendingStart ? 50 : conf.activateOnTypingDelay;
1181 this.debounceUpdate = cState.active.some(a => a.isPending && !this.running.some(q => q.active.source == a.source))
1182 ? setTimeout(() => this.startUpdate(), delay) : -1;
1183 if (this.composing != 0 /* CompositionState.None */)
1184 for (let tr of update.transactions) {
1185 if (tr.isUserEvent("input.type"))
1186 this.composing = 2 /* CompositionState.Changed */;
1187 else if (this.composing == 2 /* CompositionState.Changed */ && tr.selection)
1188 this.composing = 3 /* CompositionState.ChangedAndMoved */;
1189 }
1190 }
1191 startUpdate() {
1192 this.debounceUpdate = -1;
1193 this.pendingStart = false;
1194 let { state } = this.view, cState = state.field(completionState);
1195 for (let active of cState.active) {
1196 if (active.isPending && !this.running.some(r => r.active.source == active.source))
1197 this.startQuery(active);
1198 }
1199 if (this.running.length && cState.open && cState.open.disabled)
1200 this.debounceAccept = setTimeout(() => this.accept(), this.view.state.facet(completionConfig).updateSyncTime);
1201 }
1202 startQuery(active) {
1203 let { state } = this.view, pos = cur(state);
1204 let context = new CompletionContext(state, pos, active.explicit, this.view);
1205 let pending = new RunningQuery(active, context);
1206 this.running.push(pending);
1207 Promise.resolve(active.source(context)).then(result => {
1208 if (!pending.context.aborted) {
1209 pending.done = result || null;
1210 this.scheduleAccept();
1211 }
1212 }, err => {
1213 this.view.dispatch({ effects: closeCompletionEffect.of(null) });
1214 logException(this.view.state, err);
1215 });
1216 }
1217 scheduleAccept() {
1218 if (this.running.every(q => q.done !== undefined))
1219 this.accept();
1220 else if (this.debounceAccept < 0)
1221 this.debounceAccept = setTimeout(() => this.accept(), this.view.state.facet(completionConfig).updateSyncTime);
1222 }
1223 // For each finished query in this.running, try to create a result
1224 // or, if appropriate, restart the query.
1225 accept() {
1226 var _a;
1227 if (this.debounceAccept > -1)
1228 clearTimeout(this.debounceAccept);
1229 this.debounceAccept = -1;
1230 let updated = [];
1231 let conf = this.view.state.facet(completionConfig), cState = this.view.state.field(completionState);
1232 for (let i = 0; i < this.running.length; i++) {
1233 let query = this.running[i];
1234 if (query.done === undefined)
1235 continue;
1236 this.running.splice(i--, 1);
1237 if (query.done) {
1238 let pos = cur(query.updates.length ? query.updates[0].startState : this.view.state);
1239 let limit = Math.min(pos, query.done.from + (query.active.explicit ? 0 : 1));
1240 let active = new ActiveResult(query.active.source, query.active.explicit, limit, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : pos);
1241 // Replay the transactions that happened since the start of
1242 // the request and see if that preserves the result
1243 for (let tr of query.updates)
1244 active = active.update(tr, conf);
1245 if (active.hasResult()) {
1246 updated.push(active);
1247 continue;
1248 }
1249 }
1250 let current = cState.active.find(a => a.source == query.active.source);
1251 if (current && current.isPending) {
1252 if (query.done == null) {
1253 // Explicitly failed. Should clear the pending status if it
1254 // hasn't been re-set in the meantime.
1255 let active = new ActiveSource(query.active.source, 0 /* State.Inactive */);
1256 for (let tr of query.updates)
1257 active = active.update(tr, conf);
1258 if (!active.isPending)
1259 updated.push(active);
1260 }
1261 else {
1262 // Cleared by subsequent transactions. Restart.
1263 this.startQuery(current);
1264 }
1265 }
1266 }
1267 if (updated.length || cState.open && cState.open.disabled)
1268 this.view.dispatch({ effects: setActiveEffect.of(updated) });
1269 }
1270}, {
1271 eventHandlers: {
1272 blur(event) {
1273 let state = this.view.state.field(completionState, false);
1274 if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
1275 let dialog = state.open && getTooltip(this.view, state.open.tooltip);
1276 if (!dialog || !dialog.dom.contains(event.relatedTarget))
1277 setTimeout(() => this.view.dispatch({ effects: closeCompletionEffect.of(null) }), 10);
1278 }
1279 },
1280 compositionstart() {
1281 this.composing = 1 /* CompositionState.Started */;
1282 },
1283 compositionend() {
1284 if (this.composing == 3 /* CompositionState.ChangedAndMoved */) {
1285 // Safari fires compositionend events synchronously, possibly
1286 // from inside an update, so dispatch asynchronously to avoid reentrancy
1287 setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20);
1288 }
1289 this.composing = 0 /* CompositionState.None */;
1290 }
1291 }
1292});
1293const windows = typeof navigator == "object" && /*@__PURE__*//Win/.test(navigator.platform);
1294const commitCharacters = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.domEventHandlers({
1295 keydown(event, view) {
1296 let field = view.state.field(completionState, false);
1297 if (!field || !field.open || field.open.disabled || field.open.selected < 0 ||
1298 event.key.length > 1 || event.ctrlKey && !(windows && event.altKey) || event.metaKey)
1299 return false;
1300 let option = field.open.options[field.open.selected];
1301 let result = field.active.find(a => a.source == option.source);
1302 let commitChars = option.completion.commitCharacters || result.result.commitCharacters;
1303 if (commitChars && commitChars.indexOf(event.key) > -1)
1304 applyCompletion(view, option);
1305 return false;
1306 }
1307}));
1308
1309const baseTheme = /*@__PURE__*/EditorView.baseTheme({
1310 ".cm-tooltip.cm-tooltip-autocomplete": {
1311 "& > ul": {
1312 fontFamily: "monospace",
1313 whiteSpace: "nowrap",
1314 overflow: "hidden auto",
1315 maxWidth_fallback: "700px",
1316 maxWidth: "min(700px, 95vw)",
1317 minWidth: "250px",
1318 maxHeight: "10em",
1319 height: "100%",
1320 listStyle: "none",
1321 margin: 0,
1322 padding: 0,
1323 "& > li, & > completion-section": {
1324 padding: "1px 3px",
1325 lineHeight: 1.2
1326 },
1327 "& > li": {
1328 overflowX: "hidden",
1329 textOverflow: "ellipsis",
1330 cursor: "pointer"
1331 },
1332 "& > completion-section": {
1333 display: "list-item",
1334 borderBottom: "1px solid silver",
1335 paddingLeft: "0.5em",
1336 opacity: 0.7
1337 }
1338 }
1339 },
1340 "&light .cm-tooltip-autocomplete ul li[aria-selected]": {
1341 background: "#17c",
1342 color: "white",
1343 },
1344 "&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1345 background: "#777",
1346 },
1347 "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
1348 background: "#347",
1349 color: "white",
1350 },
1351 "&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
1352 background: "#444",
1353 },
1354 ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
1355 content: '"···"',
1356 opacity: 0.5,
1357 display: "block",
1358 textAlign: "center"
1359 },
1360 ".cm-tooltip.cm-completionInfo": {
1361 position: "absolute",
1362 padding: "3px 9px",
1363 width: "max-content",
1364 maxWidth: `${400 /* Info.Width */}px`,
1365 boxSizing: "border-box",
1366 whiteSpace: "pre-line"
1367 },
1368 ".cm-completionInfo.cm-completionInfo-left": { right: "100%" },
1369 ".cm-completionInfo.cm-completionInfo-right": { left: "100%" },
1370 ".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${30 /* Info.Margin */}px` },
1371 ".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${30 /* Info.Margin */}px` },
1372 "&light .cm-snippetField": { backgroundColor: "#00000022" },
1373 "&dark .cm-snippetField": { backgroundColor: "#ffffff22" },
1374 ".cm-snippetFieldPosition": {
1375 verticalAlign: "text-top",
1376 width: 0,
1377 height: "1.15em",
1378 display: "inline-block",
1379 margin: "0 -0.7px -.7em",
1380 borderLeft: "1.4px dotted #888"
1381 },
1382 ".cm-completionMatchedText": {
1383 textDecoration: "underline"
1384 },
1385 ".cm-completionDetail": {
1386 marginLeft: "0.5em",
1387 fontStyle: "italic"
1388 },
1389 ".cm-completionIcon": {
1390 fontSize: "90%",
1391 width: ".8em",
1392 display: "inline-block",
1393 textAlign: "center",
1394 paddingRight: ".6em",
1395 opacity: "0.6",
1396 boxSizing: "content-box"
1397 },
1398 ".cm-completionIcon-function, .cm-completionIcon-method": {
1399 "&:after": { content: "'ƒ'" }
1400 },
1401 ".cm-completionIcon-class": {
1402 "&:after": { content: "'○'" }
1403 },
1404 ".cm-completionIcon-interface": {
1405 "&:after": { content: "'◌'" }
1406 },
1407 ".cm-completionIcon-variable": {
1408 "&:after": { content: "'𝑥'" }
1409 },
1410 ".cm-completionIcon-constant": {
1411 "&:after": { content: "'𝐶'" }
1412 },
1413 ".cm-completionIcon-type": {
1414 "&:after": { content: "'𝑡'" }
1415 },
1416 ".cm-completionIcon-enum": {
1417 "&:after": { content: "'∪'" }
1418 },
1419 ".cm-completionIcon-property": {
1420 "&:after": { content: "'□'" }
1421 },
1422 ".cm-completionIcon-keyword": {
1423 "&:after": { content: "'🔑\uFE0E'" } // Disable emoji rendering
1424 },
1425 ".cm-completionIcon-namespace": {
1426 "&:after": { content: "'▢'" }
1427 },
1428 ".cm-completionIcon-text": {
1429 "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" }
1430 }
1431});
1432
1433class FieldPos {
1434 constructor(field, line, from, to) {
1435 this.field = field;
1436 this.line = line;
1437 this.from = from;
1438 this.to = to;
1439 }
1440}
1441class FieldRange {
1442 constructor(field, from, to) {
1443 this.field = field;
1444 this.from = from;
1445 this.to = to;
1446 }
1447 map(changes) {
1448 let from = changes.mapPos(this.from, -1, MapMode.TrackDel);
1449 let to = changes.mapPos(this.to, 1, MapMode.TrackDel);
1450 return from == null || to == null ? null : new FieldRange(this.field, from, to);
1451 }
1452}
1453class Snippet {
1454 constructor(lines, fieldPositions) {
1455 this.lines = lines;
1456 this.fieldPositions = fieldPositions;
1457 }
1458 instantiate(state, pos) {
1459 let text = [], lineStart = [pos];
1460 let lineObj = state.doc.lineAt(pos), baseIndent = /^\s*/.exec(lineObj.text)[0];
1461 for (let line of this.lines) {
1462 if (text.length) {
1463 let indent = baseIndent, tabs = /^\t*/.exec(line)[0].length;
1464 for (let i = 0; i < tabs; i++)
1465 indent += state.facet(indentUnit);
1466 lineStart.push(pos + indent.length - tabs);
1467 line = indent + line.slice(tabs);
1468 }
1469 text.push(line);
1470 pos += line.length + 1;
1471 }
1472 let ranges = this.fieldPositions.map(pos => new FieldRange(pos.field, lineStart[pos.line] + pos.from, lineStart[pos.line] + pos.to));
1473 return { text, ranges };
1474 }
1475 static parse(template) {
1476 let fields = [];
1477 let lines = [], positions = [], m;
1478 for (let line of template.split(/\r\n?|\n/)) {
1479 while (m = /[#$]\{(?:(\d+)(?::([^{}]*))?|((?:\\[{}]|[^{}])*))\}/.exec(line)) {
1480 let seq = m[1] ? +m[1] : null, rawName = m[2] || m[3] || "", found = -1;
1481 let name = rawName.replace(/\\[{}]/g, m => m[1]);
1482 for (let i = 0; i < fields.length; i++) {
1483 if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false)
1484 found = i;
1485 }
1486 if (found < 0) {
1487 let i = 0;
1488 while (i < fields.length && (seq == null || (fields[i].seq != null && fields[i].seq < seq)))
1489 i++;
1490 fields.splice(i, 0, { seq, name });
1491 found = i;
1492 for (let pos of positions)
1493 if (pos.field >= found)
1494 pos.field++;
1495 }
1496 for (let pos of positions)
1497 if (pos.line == lines.length && pos.from > m.index) {
1498 let snip = m[2] ? 3 + (m[1] || "").length : 2;
1499 pos.from -= snip;
1500 pos.to -= snip;
1501 }
1502 positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length));
1503 line = line.slice(0, m.index) + rawName + line.slice(m.index + m[0].length);
1504 }
1505 line = line.replace(/\\([{}])/g, (_, brace, index) => {
1506 for (let pos of positions)
1507 if (pos.line == lines.length && pos.from > index) {
1508 pos.from--;
1509 pos.to--;
1510 }
1511 return brace;
1512 });
1513 lines.push(line);
1514 }
1515 return new Snippet(lines, positions);
1516 }
1517}
1518let fieldMarker = /*@__PURE__*/Decoration.widget({ widget: /*@__PURE__*/new class extends WidgetType {
1519 toDOM() {
1520 let span = document.createElement("span");
1521 span.className = "cm-snippetFieldPosition";
1522 return span;
1523 }
1524 ignoreEvent() { return false; }
1525 } });
1526let fieldRange = /*@__PURE__*/Decoration.mark({ class: "cm-snippetField" });
1527class ActiveSnippet {
1528 constructor(ranges, active) {
1529 this.ranges = ranges;
1530 this.active = active;
1531 this.deco = Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)), true);
1532 }
1533 map(changes) {
1534 let ranges = [];
1535 for (let r of this.ranges) {
1536 let mapped = r.map(changes);
1537 if (!mapped)
1538 return null;
1539 ranges.push(mapped);
1540 }
1541 return new ActiveSnippet(ranges, this.active);
1542 }
1543 selectionInsideField(sel) {
1544 return sel.ranges.every(range => this.ranges.some(r => r.field == this.active && r.from <= range.from && r.to >= range.to));
1545 }
1546}
1547const setActive = /*@__PURE__*/StateEffect.define({
1548 map(value, changes) { return value && value.map(changes); }
1549});
1550const moveToField = /*@__PURE__*/StateEffect.define();
1551const snippetState = /*@__PURE__*/StateField.define({
1552 create() { return null; },
1553 update(value, tr) {
1554 for (let effect of tr.effects) {
1555 if (effect.is(setActive))
1556 return effect.value;
1557 if (effect.is(moveToField) && value)
1558 return new ActiveSnippet(value.ranges, effect.value);
1559 }
1560 if (value && tr.docChanged)
1561 value = value.map(tr.changes);
1562 if (value && tr.selection && !value.selectionInsideField(tr.selection))
1563 value = null;
1564 return value;
1565 },
1566 provide: f => EditorView.decorations.from(f, val => val ? val.deco : Decoration.none)
1567});
1568function fieldSelection(ranges, field) {
1569 return EditorSelection.create(ranges.filter(r => r.field == field).map(r => EditorSelection.range(r.from, r.to)));
1570}
1571/**
1572Convert a snippet template to a function that can
1573[apply](https://codemirror.net/6/docs/ref/#autocomplete.Completion.apply) it. Snippets are written
1574using syntax like this:
1575
1576 "for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}"
1577
1578Each `${}` placeholder (you may also use `#{}`) indicates a field
1579that the user can fill in. Its name, if any, will be the default
1580content for the field.
1581
1582When the snippet is activated by calling the returned function,
1583the code is inserted at the given position. Newlines in the
1584template are indented by the indentation of the start line, plus
1585one [indent unit](https://codemirror.net/6/docs/ref/#language.indentUnit) per tab character after
1586the newline.
1587
1588On activation, (all instances of) the first field are selected.
1589The user can move between fields with Tab and Shift-Tab as long as
1590the fields are active. Moving to the last field or moving the
1591cursor out of the current field deactivates the fields.
1592
1593The order of fields defaults to textual order, but you can add
1594numbers to placeholders (`${1}` or `${1:defaultText}`) to provide
1595a custom order.
1596
1597To include a literal `{` or `}` in your template, put a backslash
1598in front of it. This will be removed and the brace will not be
1599interpreted as indicating a placeholder.
1600*/
1601function snippet(template) {
1602 let snippet = Snippet.parse(template);
1603 return (editor, completion, from, to) => {
1604 let { text, ranges } = snippet.instantiate(editor.state, from);
1605 let { main } = editor.state.selection;
1606 let spec = {
1607 changes: { from, to: to == main.from ? main.to : to, insert: Text.of(text) },
1608 scrollIntoView: true,
1609 annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
1610 };
1611 if (ranges.length)
1612 spec.selection = fieldSelection(ranges, 0);
1613 if (ranges.some(r => r.field > 0)) {
1614 let active = new ActiveSnippet(ranges, 0);
1615 let effects = spec.effects = [setActive.of(active)];
1616 if (editor.state.field(snippetState, false) === undefined)
1617 effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
1618 }
1619 editor.dispatch(editor.state.update(spec));
1620 };
1621}
1622function moveField(dir) {
1623 return ({ state, dispatch }) => {
1624 let active = state.field(snippetState, false);
1625 if (!active || dir < 0 && active.active == 0)
1626 return false;
1627 let next = active.active + dir, last = dir > 0 && !active.ranges.some(r => r.field == next + dir);
1628 dispatch(state.update({
1629 selection: fieldSelection(active.ranges, next),
1630 effects: setActive.of(last ? null : new ActiveSnippet(active.ranges, next)),
1631 scrollIntoView: true
1632 }));
1633 return true;
1634 };
1635}
1636/**
1637A command that clears the active snippet, if any.
1638*/
1639const clearSnippet = ({ state, dispatch }) => {
1640 let active = state.field(snippetState, false);
1641 if (!active)
1642 return false;
1643 dispatch(state.update({ effects: setActive.of(null) }));
1644 return true;
1645};
1646/**
1647Move to the next snippet field, if available.
1648*/
1649const nextSnippetField = /*@__PURE__*/moveField(1);
1650/**
1651Move to the previous snippet field, if available.
1652*/
1653const prevSnippetField = /*@__PURE__*/moveField(-1);
1654/**
1655Check if there is an active snippet with a next field for
1656`nextSnippetField` to move to.
1657*/
1658function hasNextSnippetField(state) {
1659 let active = state.field(snippetState, false);
1660 return !!(active && active.ranges.some(r => r.field == active.active + 1));
1661}
1662/**
1663Returns true if there is an active snippet and a previous field
1664for `prevSnippetField` to move to.
1665*/
1666function hasPrevSnippetField(state) {
1667 let active = state.field(snippetState, false);
1668 return !!(active && active.active > 0);
1669}
1670const defaultSnippetKeymap = [
1671 { key: "Tab", run: nextSnippetField, shift: prevSnippetField },
1672 { key: "Escape", run: clearSnippet }
1673];
1674/**
1675A facet that can be used to configure the key bindings used by
1676snippets. The default binds Tab to
1677[`nextSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.nextSnippetField), Shift-Tab to
1678[`prevSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.prevSnippetField), and Escape
1679to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet).
1680*/
1681const snippetKeymap = /*@__PURE__*/Facet.define({
1682 combine(maps) { return maps.length ? maps[0] : defaultSnippetKeymap; }
1683});
1684const addSnippetKeymap = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.compute([snippetKeymap], state => state.facet(snippetKeymap)));
1685/**
1686Create a completion from a snippet. Returns an object with the
1687properties from `completion`, plus an `apply` function that
1688applies the snippet.
1689*/
1690function snippetCompletion(template, completion) {
1691 return { ...completion, apply: snippet(template) };
1692}
1693const snippetPointerHandler = /*@__PURE__*/EditorView.domEventHandlers({
1694 mousedown(event, view) {
1695 let active = view.state.field(snippetState, false), pos;
1696 if (!active || (pos = view.posAtCoords({ x: event.clientX, y: event.clientY })) == null)
1697 return false;
1698 let match = active.ranges.find(r => r.from <= pos && r.to >= pos);
1699 if (!match || match.field == active.active)
1700 return false;
1701 view.dispatch({
1702 selection: fieldSelection(active.ranges, match.field),
1703 effects: setActive.of(active.ranges.some(r => r.field > match.field)
1704 ? new ActiveSnippet(active.ranges, match.field) : null),
1705 scrollIntoView: true
1706 });
1707 return true;
1708 }
1709});
1710
1711function wordRE(wordChars) {
1712 let escaped = wordChars.replace(/[\]\-\\]/g, "\\$&");
1713 try {
1714 return new RegExp(`[\\p{Alphabetic}\\p{Number}_${escaped}]+`, "ug");
1715 }
1716 catch (_a) {
1717 return new RegExp(`[\w${escaped}]`, "g");
1718 }
1719}
1720function mapRE(re, f) {
1721 return new RegExp(f(re.source), re.unicode ? "u" : "");
1722}
1723const wordCaches = /*@__PURE__*/Object.create(null);
1724function wordCache(wordChars) {
1725 return wordCaches[wordChars] || (wordCaches[wordChars] = new WeakMap);
1726}
1727function storeWords(doc, wordRE, result, seen, ignoreAt) {
1728 for (let lines = doc.iterLines(), pos = 0; !lines.next().done;) {
1729 let { value } = lines, m;
1730 wordRE.lastIndex = 0;
1731 while (m = wordRE.exec(value)) {
1732 if (!seen[m[0]] && pos + m.index != ignoreAt) {
1733 result.push({ type: "text", label: m[0] });
1734 seen[m[0]] = true;
1735 if (result.length >= 2000 /* C.MaxList */)
1736 return;
1737 }
1738 }
1739 pos += value.length + 1;
1740 }
1741}
1742function collectWords(doc, cache, wordRE, to, ignoreAt) {
1743 let big = doc.length >= 1000 /* C.MinCacheLen */;
1744 let cached = big && cache.get(doc);
1745 if (cached)
1746 return cached;
1747 let result = [], seen = Object.create(null);
1748 if (doc.children) {
1749 let pos = 0;
1750 for (let ch of doc.children) {
1751 if (ch.length >= 1000 /* C.MinCacheLen */) {
1752 for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) {
1753 if (!seen[c.label]) {
1754 seen[c.label] = true;
1755 result.push(c);
1756 }
1757 }
1758 }
1759 else {
1760 storeWords(ch, wordRE, result, seen, ignoreAt - pos);
1761 }
1762 pos += ch.length + 1;
1763 }
1764 }
1765 else {
1766 storeWords(doc, wordRE, result, seen, ignoreAt);
1767 }
1768 if (big && result.length < 2000 /* C.MaxList */)
1769 cache.set(doc, result);
1770 return result;
1771}
1772/**
1773A completion source that will scan the document for words (using a
1774[character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and
1775return those as completions.
1776*/
1777const completeAnyWord = context => {
1778 var _a;
1779 let wordChars = (_a = context.state.languageDataAt("wordChars", context.pos)[0]) !== null && _a !== void 0 ? _a : "";
1780 let re = wordRE(wordChars);
1781 let token = context.matchBefore(mapRE(re, s => s + "$"));
1782 if (!token && !context.explicit)
1783 return null;
1784 let from = token ? token.from : context.pos;
1785 let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* C.Range */, from);
1786 return { from, options, validFor: mapRE(re, s => "^" + s) };
1787};
1788
1789const defaults = {
1790 brackets: ["(", "[", "{", "'", '"'],
1791 before: ")]}:;>",
1792 stringPrefixes: []
1793};
1794const closeBracketEffect = /*@__PURE__*/StateEffect.define({
1795 map(value, mapping) {
1796 let mapped = mapping.mapPos(value, -1, MapMode.TrackAfter);
1797 return mapped == null ? undefined : mapped;
1798 }
1799});
1800const closedBracket = /*@__PURE__*/new class extends RangeValue {
1801};
1802closedBracket.startSide = 1;
1803closedBracket.endSide = -1;
1804const bracketState = /*@__PURE__*/StateField.define({
1805 create() { return RangeSet.empty; },
1806 update(value, tr) {
1807 value = value.map(tr.changes);
1808 if (tr.selection) {
1809 let line = tr.state.doc.lineAt(tr.selection.main.head);
1810 value = value.update({ filter: from => from >= line.from && from <= line.to });
1811 }
1812 for (let effect of tr.effects)
1813 if (effect.is(closeBracketEffect))
1814 value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] });
1815 return value;
1816 }
1817});
1818/**
1819Extension to enable bracket-closing behavior. When a closeable
1820bracket is typed, its closing bracket is immediately inserted
1821after the cursor. When closing a bracket directly in front of a
1822closing bracket inserted by the extension, the cursor moves over
1823that bracket.
1824*/
1825function closeBrackets() {
1826 return [inputHandler, bracketState];
1827}
1828const definedClosing = "()[]{}<>«»»«[]{}";
1829function closing(ch) {
1830 for (let i = 0; i < definedClosing.length; i += 2)
1831 if (definedClosing.charCodeAt(i) == ch)
1832 return definedClosing.charAt(i + 1);
1833 return fromCodePoint(ch < 128 ? ch : ch + 1);
1834}
1835function config(state, pos) {
1836 return state.languageDataAt("closeBrackets", pos)[0] || defaults;
1837}
1838const android = typeof navigator == "object" && /*@__PURE__*//Android\b/.test(navigator.userAgent);
1839const inputHandler = /*@__PURE__*/EditorView.inputHandler.of((view, from, to, insert) => {
1840 if ((android ? view.composing : view.compositionStarted) || view.state.readOnly)
1841 return false;
1842 let sel = view.state.selection.main;
1843 if (insert.length > 2 || insert.length == 2 && codePointSize(codePointAt(insert, 0)) == 1 ||
1844 from != sel.from || to != sel.to)
1845 return false;
1846 let tr = insertBracket(view.state, insert);
1847 if (!tr)
1848 return false;
1849 view.dispatch(tr);
1850 return true;
1851});
1852/**
1853Command that implements deleting a pair of matching brackets when
1854the cursor is between them.
1855*/
1856const deleteBracketPair = ({ state, dispatch }) => {
1857 if (state.readOnly)
1858 return false;
1859 let conf = config(state, state.selection.main.head);
1860 let tokens = conf.brackets || defaults.brackets;
1861 let dont = null, changes = state.changeByRange(range => {
1862 if (range.empty) {
1863 let before = prevChar(state.doc, range.head);
1864 for (let token of tokens) {
1865 if (token == before && nextChar(state.doc, range.head) == closing(codePointAt(token, 0)))
1866 return { changes: { from: range.head - token.length, to: range.head + token.length },
1867 range: EditorSelection.cursor(range.head - token.length) };
1868 }
1869 }
1870 return { range: dont = range };
1871 });
1872 if (!dont)
1873 dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete.backward" }));
1874 return !dont;
1875};
1876/**
1877Close-brackets related key bindings. Binds Backspace to
1878[`deleteBracketPair`](https://codemirror.net/6/docs/ref/#autocomplete.deleteBracketPair).
1879*/
1880const closeBracketsKeymap = [
1881 { key: "Backspace", run: deleteBracketPair }
1882];
1883/**
1884Implements the extension's behavior on text insertion. If the
1885given string counts as a bracket in the language around the
1886selection, and replacing the selection with it requires custom
1887behavior (inserting a closing version or skipping past a
1888previously-closed bracket), this function returns a transaction
1889representing that custom behavior. (You only need this if you want
1890to programmatically insert brackets—the
1891[`closeBrackets`](https://codemirror.net/6/docs/ref/#autocomplete.closeBrackets) extension will
1892take care of running this for user input.)
1893*/
1894function insertBracket(state, bracket) {
1895 let conf = config(state, state.selection.main.head);
1896 let tokens = conf.brackets || defaults.brackets;
1897 for (let tok of tokens) {
1898 let closed = closing(codePointAt(tok, 0));
1899 if (bracket == tok)
1900 return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
1901 : handleOpen(state, tok, closed, conf.before || defaults.before);
1902 if (bracket == closed && closedBracketAt(state, state.selection.main.from))
1903 return handleClose(state, tok, closed);
1904 }
1905 return null;
1906}
1907function closedBracketAt(state, pos) {
1908 let found = false;
1909 state.field(bracketState).between(0, state.doc.length, from => {
1910 if (from == pos)
1911 found = true;
1912 });
1913 return found;
1914}
1915function nextChar(doc, pos) {
1916 let next = doc.sliceString(pos, pos + 2);
1917 return next.slice(0, codePointSize(codePointAt(next, 0)));
1918}
1919function prevChar(doc, pos) {
1920 let prev = doc.sliceString(pos - 2, pos);
1921 return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
1922}
1923function handleOpen(state, open, close, closeBefore) {
1924 let dont = null, changes = state.changeByRange(range => {
1925 if (!range.empty)
1926 return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
1927 effects: closeBracketEffect.of(range.to + open.length),
1928 range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
1929 let next = nextChar(state.doc, range.head);
1930 if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
1931 return { changes: { insert: open + close, from: range.head },
1932 effects: closeBracketEffect.of(range.head + open.length),
1933 range: EditorSelection.cursor(range.head + open.length) };
1934 return { range: dont = range };
1935 });
1936 return dont ? null : state.update(changes, {
1937 scrollIntoView: true,
1938 userEvent: "input.type"
1939 });
1940}
1941function handleClose(state, _open, close) {
1942 let dont = null, changes = state.changeByRange(range => {
1943 if (range.empty && nextChar(state.doc, range.head) == close)
1944 return { changes: { from: range.head, to: range.head + close.length, insert: close },
1945 range: EditorSelection.cursor(range.head + close.length) };
1946 return dont = { range };
1947 });
1948 return dont ? null : state.update(changes, {
1949 scrollIntoView: true,
1950 userEvent: "input.type"
1951 });
1952}
1953// Handles cases where the open and close token are the same, and
1954// possibly triple quotes (as in `"""abc"""`-style quoting).
1955function handleSame(state, token, allowTriple, config) {
1956 let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
1957 let dont = null, changes = state.changeByRange(range => {
1958 if (!range.empty)
1959 return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
1960 effects: closeBracketEffect.of(range.to + token.length),
1961 range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
1962 let pos = range.head, next = nextChar(state.doc, pos), start;
1963 if (next == token) {
1964 if (nodeStart(state, pos)) {
1965 return { changes: { insert: token + token, from: pos },
1966 effects: closeBracketEffect.of(pos + token.length),
1967 range: EditorSelection.cursor(pos + token.length) };
1968 }
1969 else if (closedBracketAt(state, pos)) {
1970 let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token;
1971 let content = isTriple ? token + token + token : token;
1972 return { changes: { from: pos, to: pos + content.length, insert: content },
1973 range: EditorSelection.cursor(pos + content.length) };
1974 }
1975 }
1976 else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
1977 (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
1978 nodeStart(state, start)) {
1979 return { changes: { insert: token + token + token + token, from: pos },
1980 effects: closeBracketEffect.of(pos + token.length),
1981 range: EditorSelection.cursor(pos + token.length) };
1982 }
1983 else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
1984 if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
1985 return { changes: { insert: token + token, from: pos },
1986 effects: closeBracketEffect.of(pos + token.length),
1987 range: EditorSelection.cursor(pos + token.length) };
1988 }
1989 return { range: dont = range };
1990 });
1991 return dont ? null : state.update(changes, {
1992 scrollIntoView: true,
1993 userEvent: "input.type"
1994 });
1995}
1996function nodeStart(state, pos) {
1997 let tree = syntaxTree(state).resolveInner(pos + 1);
1998 return tree.parent && tree.from == pos;
1999}
2000function probablyInString(state, pos, quoteToken, prefixes) {
2001 let node = syntaxTree(state).resolveInner(pos, -1);
2002 let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0);
2003 for (let i = 0; i < 5; i++) {
2004 let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix));
2005 let quotePos = start.indexOf(quoteToken);
2006 if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) {
2007 let first = node.firstChild;
2008 while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) {
2009 if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken)
2010 return false;
2011 first = first.firstChild;
2012 }
2013 return true;
2014 }
2015 let parent = node.to == pos && node.parent;
2016 if (!parent)
2017 break;
2018 node = parent;
2019 }
2020 return false;
2021}
2022function canStartStringAt(state, pos, prefixes) {
2023 let charCat = state.charCategorizer(pos);
2024 if (charCat(state.sliceDoc(pos - 1, pos)) != CharCategory.Word)
2025 return pos;
2026 for (let prefix of prefixes) {
2027 let start = pos - prefix.length;
2028 if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != CharCategory.Word)
2029 return start;
2030 }
2031 return -1;
2032}
2033
2034/**
2035Returns an extension that enables autocompletion.
2036*/
2037function autocompletion(config = {}) {
2038 return [
2039 commitCharacters,
2040 completionState,
2041 completionConfig.of(config),
2042 completionPlugin,
2043 completionKeymapExt,
2044 baseTheme
2045 ];
2046}
2047/**
2048Basic keybindings for autocompletion.
2049
2050 - Ctrl-Space (and Alt-\` or Alt-i on macOS): [`startCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.startCompletion)
2051 - Escape: [`closeCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.closeCompletion)
2052 - ArrowDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true)`
2053 - ArrowUp: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(false)`
2054 - PageDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true, "page")`
2055 - PageUp: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(false, "page")`
2056 - Enter: [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion)
2057*/
2058const completionKeymap = [
2059 { key: "Ctrl-Space", run: startCompletion },
2060 { mac: "Alt-`", run: startCompletion },
2061 { mac: "Alt-i", run: startCompletion },
2062 { key: "Escape", run: closeCompletion },
2063 { key: "ArrowDown", run: /*@__PURE__*/moveCompletionSelection(true) },
2064 { key: "ArrowUp", run: /*@__PURE__*/moveCompletionSelection(false) },
2065 { key: "PageDown", run: /*@__PURE__*/moveCompletionSelection(true, "page") },
2066 { key: "PageUp", run: /*@__PURE__*/moveCompletionSelection(false, "page") },
2067 { key: "Enter", run: acceptCompletion }
2068];
2069const completionKeymapExt = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : []));
2070/**
2071Get the current completion status. When completions are available,
2072this will return `"active"`. When completions are pending (in the
2073process of being queried), this returns `"pending"`. Otherwise, it
2074returns `null`.
2075*/
2076function completionStatus(state) {
2077 let cState = state.field(completionState, false);
2078 return cState && cState.active.some(a => a.isPending) ? "pending"
2079 : cState && cState.active.some(a => a.state != 0 /* State.Inactive */) ? "active" : null;
2080}
2081const completionArrayCache = /*@__PURE__*/new WeakMap;
2082/**
2083Returns the available completions as an array.
2084*/
2085function currentCompletions(state) {
2086 var _a;
2087 let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
2088 if (!open || open.disabled)
2089 return [];
2090 let completions = completionArrayCache.get(open.options);
2091 if (!completions)
2092 completionArrayCache.set(open.options, completions = open.options.map(o => o.completion));
2093 return completions;
2094}
2095/**
2096Return the currently selected completion, if any.
2097*/
2098function selectedCompletion(state) {
2099 var _a;
2100 let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
2101 return open && !open.disabled && open.selected >= 0 ? open.options[open.selected].completion : null;
2102}
2103/**
2104Returns the currently selected position in the active completion
2105list, or null if no completions are active.
2106*/
2107function selectedCompletionIndex(state) {
2108 var _a;
2109 let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open;
2110 return open && !open.disabled && open.selected >= 0 ? open.selected : null;
2111}
2112/**
2113Create an effect that can be attached to a transaction to change
2114the currently selected completion.
2115*/
2116function setSelectedCompletion(index) {
2117 return setSelectedEffect.of(index);
2118}
2119
2120export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };