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