symfony-expression-editor 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,1760 +4,12 @@ var view = require('@codemirror/view');
4
4
  var state = require('@codemirror/state');
5
5
  var language = require('@codemirror/language');
6
6
  var commands = require('@codemirror/commands');
7
+ var search = require('@codemirror/search');
7
8
  var autocomplete = require('@codemirror/autocomplete');
9
+ var lint = require('@codemirror/lint');
8
10
  var codemirrorLangEl = require('@valtzu/codemirror-lang-el');
9
11
  var highlight = require('@lezer/highlight');
10
12
 
11
- function crelt() {
12
- var elt = arguments[0];
13
- if (typeof elt == "string") elt = document.createElement(elt);
14
- var i = 1, next = arguments[1];
15
- if (next && typeof next == "object" && next.nodeType == null && !Array.isArray(next)) {
16
- for (var name in next) if (Object.prototype.hasOwnProperty.call(next, name)) {
17
- var value = next[name];
18
- if (typeof value == "string") elt.setAttribute(name, value);
19
- else if (value != null) elt[name] = value;
20
- }
21
- i++;
22
- }
23
- for (; i < arguments.length; i++) add(elt, arguments[i]);
24
- return elt;
25
- }
26
- function add(elt, child) {
27
- if (typeof child == "string") {
28
- elt.appendChild(document.createTextNode(child));
29
- } else if (child == null) ; else if (child.nodeType != null) {
30
- elt.appendChild(child);
31
- } else if (Array.isArray(child)) {
32
- for (var i = 0; i < child.length; i++) add(elt, child[i]);
33
- } else {
34
- throw new RangeError("Unsupported child node: " + child);
35
- }
36
- }
37
-
38
- const basicNormalize = typeof String.prototype.normalize == "function" ? (x) => x.normalize("NFKD") : (x) => x;
39
- class SearchCursor {
40
- /**
41
- Create a text cursor. The query is the search string, `from` to
42
- `to` provides the region to search.
43
-
44
- When `normalize` is given, it will be called, on both the query
45
- string and the content it is matched against, before comparing.
46
- You can, for example, create a case-insensitive search by
47
- passing `s => s.toLowerCase()`.
48
-
49
- Text is always normalized with
50
- [`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
51
- (when supported).
52
- */
53
- constructor(text, query, from = 0, to = text.length, normalize, test) {
54
- this.test = test;
55
- this.value = { from: 0, to: 0 };
56
- this.done = false;
57
- this.matches = [];
58
- this.buffer = "";
59
- this.bufferPos = 0;
60
- this.iter = text.iterRange(from, to);
61
- this.bufferStart = from;
62
- this.normalize = normalize ? (x) => normalize(basicNormalize(x)) : basicNormalize;
63
- this.query = this.normalize(query);
64
- }
65
- peek() {
66
- if (this.bufferPos == this.buffer.length) {
67
- this.bufferStart += this.buffer.length;
68
- this.iter.next();
69
- if (this.iter.done)
70
- return -1;
71
- this.bufferPos = 0;
72
- this.buffer = this.iter.value;
73
- }
74
- return state.codePointAt(this.buffer, this.bufferPos);
75
- }
76
- /**
77
- Look for the next match. Updates the iterator's
78
- [`value`](https://codemirror.net/6/docs/ref/#search.SearchCursor.value) and
79
- [`done`](https://codemirror.net/6/docs/ref/#search.SearchCursor.done) properties. Should be called
80
- at least once before using the cursor.
81
- */
82
- next() {
83
- while (this.matches.length)
84
- this.matches.pop();
85
- return this.nextOverlapping();
86
- }
87
- /**
88
- The `next` method will ignore matches that partially overlap a
89
- previous match. This method behaves like `next`, but includes
90
- such matches.
91
- */
92
- nextOverlapping() {
93
- for (; ; ) {
94
- let next = this.peek();
95
- if (next < 0) {
96
- this.done = true;
97
- return this;
98
- }
99
- let str = state.fromCodePoint(next), start = this.bufferStart + this.bufferPos;
100
- this.bufferPos += state.codePointSize(next);
101
- let norm = this.normalize(str);
102
- if (norm.length)
103
- for (let i = 0, pos = start; ; i++) {
104
- let code = norm.charCodeAt(i);
105
- let match = this.match(code, pos, this.bufferPos + this.bufferStart);
106
- if (i == norm.length - 1) {
107
- if (match) {
108
- this.value = match;
109
- return this;
110
- }
111
- break;
112
- }
113
- if (pos == start && i < str.length && str.charCodeAt(i) == code)
114
- pos++;
115
- }
116
- }
117
- }
118
- match(code, pos, end) {
119
- let match = null;
120
- for (let i = 0; i < this.matches.length; i += 2) {
121
- let index = this.matches[i], keep = false;
122
- if (this.query.charCodeAt(index) == code) {
123
- if (index == this.query.length - 1) {
124
- match = { from: this.matches[i + 1], to: end };
125
- } else {
126
- this.matches[i]++;
127
- keep = true;
128
- }
129
- }
130
- if (!keep) {
131
- this.matches.splice(i, 2);
132
- i -= 2;
133
- }
134
- }
135
- if (this.query.charCodeAt(0) == code) {
136
- if (this.query.length == 1)
137
- match = { from: pos, to: end };
138
- else
139
- this.matches.push(1, pos);
140
- }
141
- if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferStart))
142
- match = null;
143
- return match;
144
- }
145
- }
146
- if (typeof Symbol != "undefined")
147
- SearchCursor.prototype[Symbol.iterator] = function() {
148
- return this;
149
- };
150
- const empty = { from: -1, to: -1, match: /* @__PURE__ */ /.*/.exec("") };
151
- const baseFlags = "gm" + (/x/.unicode == null ? "" : "u");
152
- class RegExpCursor {
153
- /**
154
- Create a cursor that will search the given range in the given
155
- document. `query` should be the raw pattern (as you'd pass it to
156
- `new RegExp`).
157
- */
158
- constructor(text, query, options, from = 0, to = text.length) {
159
- this.text = text;
160
- this.to = to;
161
- this.curLine = "";
162
- this.done = false;
163
- this.value = empty;
164
- if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
165
- return new MultilineRegExpCursor(text, query, options, from, to);
166
- this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
167
- this.test = options === null || options === void 0 ? void 0 : options.test;
168
- this.iter = text.iter();
169
- let startLine = text.lineAt(from);
170
- this.curLineStart = startLine.from;
171
- this.matchPos = toCharEnd(text, from);
172
- this.getLine(this.curLineStart);
173
- }
174
- getLine(skip) {
175
- this.iter.next(skip);
176
- if (this.iter.lineBreak) {
177
- this.curLine = "";
178
- } else {
179
- this.curLine = this.iter.value;
180
- if (this.curLineStart + this.curLine.length > this.to)
181
- this.curLine = this.curLine.slice(0, this.to - this.curLineStart);
182
- this.iter.next();
183
- }
184
- }
185
- nextLine() {
186
- this.curLineStart = this.curLineStart + this.curLine.length + 1;
187
- if (this.curLineStart > this.to)
188
- this.curLine = "";
189
- else
190
- this.getLine(0);
191
- }
192
- /**
193
- Move to the next match, if there is one.
194
- */
195
- next() {
196
- for (let off = this.matchPos - this.curLineStart; ; ) {
197
- this.re.lastIndex = off;
198
- let match = this.matchPos <= this.to && this.re.exec(this.curLine);
199
- if (match) {
200
- let from = this.curLineStart + match.index, to = from + match[0].length;
201
- this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
202
- if (from == this.curLineStart + this.curLine.length)
203
- this.nextLine();
204
- if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
205
- this.value = { from, to, match };
206
- return this;
207
- }
208
- off = this.matchPos - this.curLineStart;
209
- } else if (this.curLineStart + this.curLine.length < this.to) {
210
- this.nextLine();
211
- off = 0;
212
- } else {
213
- this.done = true;
214
- return this;
215
- }
216
- }
217
- }
218
- }
219
- const flattened = /* @__PURE__ */ new WeakMap();
220
- class FlattenedDoc {
221
- constructor(from, text) {
222
- this.from = from;
223
- this.text = text;
224
- }
225
- get to() {
226
- return this.from + this.text.length;
227
- }
228
- static get(doc, from, to) {
229
- let cached = flattened.get(doc);
230
- if (!cached || cached.from >= to || cached.to <= from) {
231
- let flat = new FlattenedDoc(from, doc.sliceString(from, to));
232
- flattened.set(doc, flat);
233
- return flat;
234
- }
235
- if (cached.from == from && cached.to == to)
236
- return cached;
237
- let { text, from: cachedFrom } = cached;
238
- if (cachedFrom > from) {
239
- text = doc.sliceString(from, cachedFrom) + text;
240
- cachedFrom = from;
241
- }
242
- if (cached.to < to)
243
- text += doc.sliceString(cached.to, to);
244
- flattened.set(doc, new FlattenedDoc(cachedFrom, text));
245
- return new FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
246
- }
247
- }
248
- class MultilineRegExpCursor {
249
- constructor(text, query, options, from, to) {
250
- this.text = text;
251
- this.to = to;
252
- this.done = false;
253
- this.value = empty;
254
- this.matchPos = toCharEnd(text, from);
255
- this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
256
- this.test = options === null || options === void 0 ? void 0 : options.test;
257
- this.flat = FlattenedDoc.get(text, from, this.chunkEnd(
258
- from + 5e3
259
- /* Chunk.Base */
260
- ));
261
- }
262
- chunkEnd(pos) {
263
- return pos >= this.to ? this.to : this.text.lineAt(pos).to;
264
- }
265
- next() {
266
- for (; ; ) {
267
- let off = this.re.lastIndex = this.matchPos - this.flat.from;
268
- let match = this.re.exec(this.flat.text);
269
- if (match && !match[0] && match.index == off) {
270
- this.re.lastIndex = off + 1;
271
- match = this.re.exec(this.flat.text);
272
- }
273
- if (match) {
274
- let from = this.flat.from + match.index, to = from + match[0].length;
275
- if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) && (!this.test || this.test(from, to, match))) {
276
- this.value = { from, to, match };
277
- this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
278
- return this;
279
- }
280
- }
281
- if (this.flat.to == this.to) {
282
- this.done = true;
283
- return this;
284
- }
285
- this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
286
- }
287
- }
288
- }
289
- if (typeof Symbol != "undefined") {
290
- RegExpCursor.prototype[Symbol.iterator] = MultilineRegExpCursor.prototype[Symbol.iterator] = function() {
291
- return this;
292
- };
293
- }
294
- function validRegExp(source) {
295
- try {
296
- new RegExp(source, baseFlags);
297
- return true;
298
- } catch (_a) {
299
- return false;
300
- }
301
- }
302
- function toCharEnd(text, pos) {
303
- if (pos >= text.length)
304
- return pos;
305
- let line = text.lineAt(pos), next;
306
- while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 56320 && next < 57344)
307
- pos++;
308
- return pos;
309
- }
310
- function createLineDialog(view$1) {
311
- let line = String(view$1.state.doc.lineAt(view$1.state.selection.main.head).number);
312
- let input = crelt("input", { class: "cm-textfield", name: "line", value: line });
313
- let dom = crelt("form", {
314
- class: "cm-gotoLine",
315
- onkeydown: (event) => {
316
- if (event.keyCode == 27) {
317
- event.preventDefault();
318
- view$1.dispatch({ effects: dialogEffect.of(false) });
319
- view$1.focus();
320
- } else if (event.keyCode == 13) {
321
- event.preventDefault();
322
- go();
323
- }
324
- },
325
- onsubmit: (event) => {
326
- event.preventDefault();
327
- go();
328
- }
329
- }, crelt("label", view$1.state.phrase("Go to line"), ": ", input), " ", crelt("button", { class: "cm-button", type: "submit" }, view$1.state.phrase("go")), crelt("button", {
330
- name: "close",
331
- onclick: () => {
332
- view$1.dispatch({ effects: dialogEffect.of(false) });
333
- view$1.focus();
334
- },
335
- "aria-label": view$1.state.phrase("close"),
336
- type: "button"
337
- }, ["\xD7"]));
338
- function go() {
339
- let match = /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(input.value);
340
- if (!match)
341
- return;
342
- let { state: state$1 } = view$1, startLine = state$1.doc.lineAt(state$1.selection.main.head);
343
- let [, sign, ln, cl, percent] = match;
344
- let col = cl ? +cl.slice(1) : 0;
345
- let line2 = ln ? +ln : startLine.number;
346
- if (ln && percent) {
347
- let pc = line2 / 100;
348
- if (sign)
349
- pc = pc * (sign == "-" ? -1 : 1) + startLine.number / state$1.doc.lines;
350
- line2 = Math.round(state$1.doc.lines * pc);
351
- } else if (ln && sign) {
352
- line2 = line2 * (sign == "-" ? -1 : 1) + startLine.number;
353
- }
354
- let docLine = state$1.doc.line(Math.max(1, Math.min(state$1.doc.lines, line2)));
355
- let selection = state.EditorSelection.cursor(docLine.from + Math.max(0, Math.min(col, docLine.length)));
356
- view$1.dispatch({
357
- effects: [dialogEffect.of(false), view.EditorView.scrollIntoView(selection.from, { y: "center" })],
358
- selection
359
- });
360
- view$1.focus();
361
- }
362
- return { dom };
363
- }
364
- const dialogEffect = /* @__PURE__ */ state.StateEffect.define();
365
- const dialogField = /* @__PURE__ */ state.StateField.define({
366
- create() {
367
- return true;
368
- },
369
- update(value, tr) {
370
- for (let e of tr.effects)
371
- if (e.is(dialogEffect))
372
- value = e.value;
373
- return value;
374
- },
375
- provide: (f) => view.showPanel.from(f, (val) => val ? createLineDialog : null)
376
- });
377
- const gotoLine = (view$1) => {
378
- let panel = view.getPanel(view$1, createLineDialog);
379
- if (!panel) {
380
- let effects = [dialogEffect.of(true)];
381
- if (view$1.state.field(dialogField, false) == null)
382
- effects.push(state.StateEffect.appendConfig.of([dialogField, baseTheme$1]));
383
- view$1.dispatch({ effects });
384
- panel = view.getPanel(view$1, createLineDialog);
385
- }
386
- if (panel)
387
- panel.dom.querySelector("input").select();
388
- return true;
389
- };
390
- const baseTheme$1 = /* @__PURE__ */ view.EditorView.baseTheme({
391
- ".cm-panel.cm-gotoLine": {
392
- padding: "2px 6px 4px",
393
- position: "relative",
394
- "& label": { fontSize: "80%" },
395
- "& [name=close]": {
396
- position: "absolute",
397
- top: "0",
398
- bottom: "0",
399
- right: "4px",
400
- backgroundColor: "inherit",
401
- border: "none",
402
- font: "inherit",
403
- padding: "0"
404
- }
405
- }
406
- });
407
- const defaultHighlightOptions = {
408
- highlightWordAroundCursor: false,
409
- minSelectionLength: 1,
410
- maxMatches: 100,
411
- wholeWords: false
412
- };
413
- const highlightConfig = /* @__PURE__ */ state.Facet.define({
414
- combine(options) {
415
- return state.combineConfig(options, defaultHighlightOptions, {
416
- highlightWordAroundCursor: (a, b) => a || b,
417
- minSelectionLength: Math.min,
418
- maxMatches: Math.min
419
- });
420
- }
421
- });
422
- function highlightSelectionMatches(options) {
423
- let ext = [defaultTheme, matchHighlighter];
424
- return ext;
425
- }
426
- const matchDeco = /* @__PURE__ */ view.Decoration.mark({ class: "cm-selectionMatch" });
427
- const mainMatchDeco = /* @__PURE__ */ view.Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" });
428
- function insideWordBoundaries(check, state$1, from, to) {
429
- return (from == 0 || check(state$1.sliceDoc(from - 1, from)) != state.CharCategory.Word) && (to == state$1.doc.length || check(state$1.sliceDoc(to, to + 1)) != state.CharCategory.Word);
430
- }
431
- function insideWord(check, state$1, from, to) {
432
- return check(state$1.sliceDoc(from, from + 1)) == state.CharCategory.Word && check(state$1.sliceDoc(to - 1, to)) == state.CharCategory.Word;
433
- }
434
- const matchHighlighter = /* @__PURE__ */ view.ViewPlugin.fromClass(class {
435
- constructor(view) {
436
- this.decorations = this.getDeco(view);
437
- }
438
- update(update) {
439
- if (update.selectionSet || update.docChanged || update.viewportChanged)
440
- this.decorations = this.getDeco(update.view);
441
- }
442
- getDeco(view$1) {
443
- let conf = view$1.state.facet(highlightConfig);
444
- let { state } = view$1, sel = state.selection;
445
- if (sel.ranges.length > 1)
446
- return view.Decoration.none;
447
- let range = sel.main, query, check = null;
448
- if (range.empty) {
449
- if (!conf.highlightWordAroundCursor)
450
- return view.Decoration.none;
451
- let word = state.wordAt(range.head);
452
- if (!word)
453
- return view.Decoration.none;
454
- check = state.charCategorizer(range.head);
455
- query = state.sliceDoc(word.from, word.to);
456
- } else {
457
- let len = range.to - range.from;
458
- if (len < conf.minSelectionLength || len > 200)
459
- return view.Decoration.none;
460
- if (conf.wholeWords) {
461
- query = state.sliceDoc(range.from, range.to);
462
- check = state.charCategorizer(range.head);
463
- if (!(insideWordBoundaries(check, state, range.from, range.to) && insideWord(check, state, range.from, range.to)))
464
- return view.Decoration.none;
465
- } else {
466
- query = state.sliceDoc(range.from, range.to);
467
- if (!query)
468
- return view.Decoration.none;
469
- }
470
- }
471
- let deco = [];
472
- for (let part of view$1.visibleRanges) {
473
- let cursor = new SearchCursor(state.doc, query, part.from, part.to);
474
- while (!cursor.next().done) {
475
- let { from, to } = cursor.value;
476
- if (!check || insideWordBoundaries(check, state, from, to)) {
477
- if (range.empty && from <= range.from && to >= range.to)
478
- deco.push(mainMatchDeco.range(from, to));
479
- else if (from >= range.to || to <= range.from)
480
- deco.push(matchDeco.range(from, to));
481
- if (deco.length > conf.maxMatches)
482
- return view.Decoration.none;
483
- }
484
- }
485
- }
486
- return view.Decoration.set(deco);
487
- }
488
- }, {
489
- decorations: (v) => v.decorations
490
- });
491
- const defaultTheme = /* @__PURE__ */ view.EditorView.baseTheme({
492
- ".cm-selectionMatch": { backgroundColor: "#99ff7780" },
493
- ".cm-searchMatch .cm-selectionMatch": { backgroundColor: "transparent" }
494
- });
495
- const selectWord = ({ state: state$1, dispatch }) => {
496
- let { selection } = state$1;
497
- let newSel = state.EditorSelection.create(selection.ranges.map((range) => state$1.wordAt(range.head) || state.EditorSelection.cursor(range.head)), selection.mainIndex);
498
- if (newSel.eq(selection))
499
- return false;
500
- dispatch(state$1.update({ selection: newSel }));
501
- return true;
502
- };
503
- function findNextOccurrence(state, query) {
504
- let { main, ranges } = state.selection;
505
- let word = state.wordAt(main.head), fullWord = word && word.from == main.from && word.to == main.to;
506
- for (let cycled = false, cursor = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to); ; ) {
507
- cursor.next();
508
- if (cursor.done) {
509
- if (cycled)
510
- return null;
511
- cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1));
512
- cycled = true;
513
- } else {
514
- if (cycled && ranges.some((r) => r.from == cursor.value.from))
515
- continue;
516
- if (fullWord) {
517
- let word2 = state.wordAt(cursor.value.from);
518
- if (!word2 || word2.from != cursor.value.from || word2.to != cursor.value.to)
519
- continue;
520
- }
521
- return cursor.value;
522
- }
523
- }
524
- }
525
- const selectNextOccurrence = ({ state: state$1, dispatch }) => {
526
- let { ranges } = state$1.selection;
527
- if (ranges.some((sel) => sel.from === sel.to))
528
- return selectWord({ state: state$1, dispatch });
529
- let searchedText = state$1.sliceDoc(ranges[0].from, ranges[0].to);
530
- if (state$1.selection.ranges.some((r) => state$1.sliceDoc(r.from, r.to) != searchedText))
531
- return false;
532
- let range = findNextOccurrence(state$1, searchedText);
533
- if (!range)
534
- return false;
535
- dispatch(state$1.update({
536
- selection: state$1.selection.addRange(state.EditorSelection.range(range.from, range.to), false),
537
- effects: view.EditorView.scrollIntoView(range.to)
538
- }));
539
- return true;
540
- };
541
- const searchConfigFacet = /* @__PURE__ */ state.Facet.define({
542
- combine(configs) {
543
- return state.combineConfig(configs, {
544
- top: false,
545
- caseSensitive: false,
546
- literal: false,
547
- regexp: false,
548
- wholeWord: false,
549
- createPanel: (view) => new SearchPanel(view),
550
- scrollToMatch: (range) => view.EditorView.scrollIntoView(range)
551
- });
552
- }
553
- });
554
- class SearchQuery {
555
- /**
556
- Create a query object.
557
- */
558
- constructor(config) {
559
- this.search = config.search;
560
- this.caseSensitive = !!config.caseSensitive;
561
- this.literal = !!config.literal;
562
- this.regexp = !!config.regexp;
563
- this.replace = config.replace || "";
564
- this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
565
- this.unquoted = this.unquote(this.search);
566
- this.wholeWord = !!config.wholeWord;
567
- }
568
- /**
569
- @internal
570
- */
571
- unquote(text) {
572
- return this.literal ? text : text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? " " : "\\");
573
- }
574
- /**
575
- Compare this query to another query.
576
- */
577
- eq(other) {
578
- return this.search == other.search && this.replace == other.replace && this.caseSensitive == other.caseSensitive && this.regexp == other.regexp && this.wholeWord == other.wholeWord;
579
- }
580
- /**
581
- @internal
582
- */
583
- create() {
584
- return this.regexp ? new RegExpQuery(this) : new StringQuery(this);
585
- }
586
- /**
587
- Get a search cursor for this query, searching through the given
588
- range in the given state.
589
- */
590
- getCursor(state$1, from = 0, to) {
591
- let st = state$1.doc ? state$1 : state.EditorState.create({ doc: state$1 });
592
- if (to == null)
593
- to = st.doc.length;
594
- return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
595
- }
596
- }
597
- class QueryType {
598
- constructor(spec) {
599
- this.spec = spec;
600
- }
601
- }
602
- function stringCursor(spec, state, from, to) {
603
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? void 0 : (x) => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : void 0);
604
- }
605
- function stringWordTest(doc, categorizer) {
606
- return (from, to, buf, bufPos) => {
607
- if (bufPos > from || bufPos + buf.length < to) {
608
- bufPos = Math.max(0, from - 2);
609
- buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
610
- }
611
- return (categorizer(charBefore(buf, from - bufPos)) != state.CharCategory.Word || categorizer(charAfter(buf, from - bufPos)) != state.CharCategory.Word) && (categorizer(charAfter(buf, to - bufPos)) != state.CharCategory.Word || categorizer(charBefore(buf, to - bufPos)) != state.CharCategory.Word);
612
- };
613
- }
614
- class StringQuery extends QueryType {
615
- constructor(spec) {
616
- super(spec);
617
- }
618
- nextMatch(state, curFrom, curTo) {
619
- let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
620
- if (cursor.done) {
621
- let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);
622
- cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();
623
- }
624
- return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;
625
- }
626
- // Searching in reverse is, rather than implementing an inverted search
627
- // cursor, done by scanning chunk after chunk forward.
628
- prevMatchInRange(state, from, to) {
629
- for (let pos = to; ; ) {
630
- let start = Math.max(from, pos - 1e4 - this.spec.unquoted.length);
631
- let cursor = stringCursor(this.spec, state, start, pos), range = null;
632
- while (!cursor.nextOverlapping().done)
633
- range = cursor.value;
634
- if (range)
635
- return range;
636
- if (start == from)
637
- return null;
638
- pos -= 1e4;
639
- }
640
- }
641
- prevMatch(state, curFrom, curTo) {
642
- let found = this.prevMatchInRange(state, 0, curFrom);
643
- if (!found)
644
- found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);
645
- return found && (found.from != curFrom || found.to != curTo) ? found : null;
646
- }
647
- getReplacement(_result) {
648
- return this.spec.unquote(this.spec.replace);
649
- }
650
- matchAll(state, limit) {
651
- let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
652
- while (!cursor.next().done) {
653
- if (ranges.length >= limit)
654
- return null;
655
- ranges.push(cursor.value);
656
- }
657
- return ranges;
658
- }
659
- highlight(state, from, to, add) {
660
- let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
661
- while (!cursor.next().done)
662
- add(cursor.value.from, cursor.value.to);
663
- }
664
- }
665
- function regexpCursor(spec, state, from, to) {
666
- return new RegExpCursor(state.doc, spec.search, {
667
- ignoreCase: !spec.caseSensitive,
668
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : void 0
669
- }, from, to);
670
- }
671
- function charBefore(str, index) {
672
- return str.slice(state.findClusterBreak(str, index, false), index);
673
- }
674
- function charAfter(str, index) {
675
- return str.slice(index, state.findClusterBreak(str, index));
676
- }
677
- function regexpWordTest(categorizer) {
678
- return (_from, _to, match) => !match[0].length || (categorizer(charBefore(match.input, match.index)) != state.CharCategory.Word || categorizer(charAfter(match.input, match.index)) != state.CharCategory.Word) && (categorizer(charAfter(match.input, match.index + match[0].length)) != state.CharCategory.Word || categorizer(charBefore(match.input, match.index + match[0].length)) != state.CharCategory.Word);
679
- }
680
- class RegExpQuery extends QueryType {
681
- nextMatch(state, curFrom, curTo) {
682
- let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
683
- if (cursor.done)
684
- cursor = regexpCursor(this.spec, state, 0, curFrom).next();
685
- return cursor.done ? null : cursor.value;
686
- }
687
- prevMatchInRange(state, from, to) {
688
- for (let size = 1; ; size++) {
689
- let start = Math.max(
690
- from,
691
- to - size * 1e4
692
- /* FindPrev.ChunkSize */
693
- );
694
- let cursor = regexpCursor(this.spec, state, start, to), range = null;
695
- while (!cursor.next().done)
696
- range = cursor.value;
697
- if (range && (start == from || range.from > start + 10))
698
- return range;
699
- if (start == from)
700
- return null;
701
- }
702
- }
703
- prevMatch(state, curFrom, curTo) {
704
- return this.prevMatchInRange(state, 0, curFrom) || this.prevMatchInRange(state, curTo, state.doc.length);
705
- }
706
- getReplacement(result) {
707
- return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
708
- if (i == "&")
709
- return result.match[0];
710
- if (i == "$")
711
- return "$";
712
- for (let l = i.length; l > 0; l--) {
713
- let n = +i.slice(0, l);
714
- if (n > 0 && n < result.match.length)
715
- return result.match[n] + i.slice(l);
716
- }
717
- return m;
718
- });
719
- }
720
- matchAll(state, limit) {
721
- let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
722
- while (!cursor.next().done) {
723
- if (ranges.length >= limit)
724
- return null;
725
- ranges.push(cursor.value);
726
- }
727
- return ranges;
728
- }
729
- highlight(state, from, to, add) {
730
- let cursor = regexpCursor(this.spec, state, Math.max(
731
- 0,
732
- from - 250
733
- /* RegExp.HighlightMargin */
734
- ), Math.min(to + 250, state.doc.length));
735
- while (!cursor.next().done)
736
- add(cursor.value.from, cursor.value.to);
737
- }
738
- }
739
- const setSearchQuery = /* @__PURE__ */ state.StateEffect.define();
740
- const togglePanel$1 = /* @__PURE__ */ state.StateEffect.define();
741
- const searchState = /* @__PURE__ */ state.StateField.define({
742
- create(state) {
743
- return new SearchState(defaultQuery(state).create(), null);
744
- },
745
- update(value, tr) {
746
- for (let effect of tr.effects) {
747
- if (effect.is(setSearchQuery))
748
- value = new SearchState(effect.value.create(), value.panel);
749
- else if (effect.is(togglePanel$1))
750
- value = new SearchState(value.query, effect.value ? createSearchPanel : null);
751
- }
752
- return value;
753
- },
754
- provide: (f) => view.showPanel.from(f, (val) => val.panel)
755
- });
756
- class SearchState {
757
- constructor(query, panel) {
758
- this.query = query;
759
- this.panel = panel;
760
- }
761
- }
762
- const matchMark = /* @__PURE__ */ view.Decoration.mark({ class: "cm-searchMatch" }), selectedMatchMark = /* @__PURE__ */ view.Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" });
763
- const searchHighlighter = /* @__PURE__ */ view.ViewPlugin.fromClass(class {
764
- constructor(view) {
765
- this.view = view;
766
- this.decorations = this.highlight(view.state.field(searchState));
767
- }
768
- update(update) {
769
- let state = update.state.field(searchState);
770
- if (state != update.startState.field(searchState) || update.docChanged || update.selectionSet || update.viewportChanged)
771
- this.decorations = this.highlight(state);
772
- }
773
- highlight({ query, panel }) {
774
- if (!panel || !query.spec.valid)
775
- return view.Decoration.none;
776
- let { view: view$1 } = this;
777
- let builder = new state.RangeSetBuilder();
778
- for (let i = 0, ranges = view$1.visibleRanges, l = ranges.length; i < l; i++) {
779
- let { from, to } = ranges[i];
780
- while (i < l - 1 && to > ranges[i + 1].from - 2 * 250)
781
- to = ranges[++i].to;
782
- query.highlight(view$1.state, from, to, (from2, to2) => {
783
- let selected = view$1.state.selection.ranges.some((r) => r.from == from2 && r.to == to2);
784
- builder.add(from2, to2, selected ? selectedMatchMark : matchMark);
785
- });
786
- }
787
- return builder.finish();
788
- }
789
- }, {
790
- decorations: (v) => v.decorations
791
- });
792
- function searchCommand(f) {
793
- return (view) => {
794
- let state = view.state.field(searchState, false);
795
- return state && state.query.spec.valid ? f(view, state) : openSearchPanel(view);
796
- };
797
- }
798
- const findNext = /* @__PURE__ */ searchCommand((view, { query }) => {
799
- let { to } = view.state.selection.main;
800
- let next = query.nextMatch(view.state, to, to);
801
- if (!next)
802
- return false;
803
- let selection = state.EditorSelection.single(next.from, next.to);
804
- let config = view.state.facet(searchConfigFacet);
805
- view.dispatch({
806
- selection,
807
- effects: [announceMatch(view, next), config.scrollToMatch(selection.main, view)],
808
- userEvent: "select.search"
809
- });
810
- selectSearchInput(view);
811
- return true;
812
- });
813
- const findPrevious = /* @__PURE__ */ searchCommand((view, { query }) => {
814
- let { state: state$1 } = view, { from } = state$1.selection.main;
815
- let prev = query.prevMatch(state$1, from, from);
816
- if (!prev)
817
- return false;
818
- let selection = state.EditorSelection.single(prev.from, prev.to);
819
- let config = view.state.facet(searchConfigFacet);
820
- view.dispatch({
821
- selection,
822
- effects: [announceMatch(view, prev), config.scrollToMatch(selection.main, view)],
823
- userEvent: "select.search"
824
- });
825
- selectSearchInput(view);
826
- return true;
827
- });
828
- const selectMatches = /* @__PURE__ */ searchCommand((view, { query }) => {
829
- let ranges = query.matchAll(view.state, 1e3);
830
- if (!ranges || !ranges.length)
831
- return false;
832
- view.dispatch({
833
- selection: state.EditorSelection.create(ranges.map((r) => state.EditorSelection.range(r.from, r.to))),
834
- userEvent: "select.search.matches"
835
- });
836
- return true;
837
- });
838
- const selectSelectionMatches = ({ state: state$1, dispatch }) => {
839
- let sel = state$1.selection;
840
- if (sel.ranges.length > 1 || sel.main.empty)
841
- return false;
842
- let { from, to } = sel.main;
843
- let ranges = [], main = 0;
844
- for (let cur = new SearchCursor(state$1.doc, state$1.sliceDoc(from, to)); !cur.next().done; ) {
845
- if (ranges.length > 1e3)
846
- return false;
847
- if (cur.value.from == from)
848
- main = ranges.length;
849
- ranges.push(state.EditorSelection.range(cur.value.from, cur.value.to));
850
- }
851
- dispatch(state$1.update({
852
- selection: state.EditorSelection.create(ranges, main),
853
- userEvent: "select.search.matches"
854
- }));
855
- return true;
856
- };
857
- const replaceNext = /* @__PURE__ */ searchCommand((view$1, { query }) => {
858
- let { state: state$1 } = view$1, { from, to } = state$1.selection.main;
859
- if (state$1.readOnly)
860
- return false;
861
- let match = query.nextMatch(state$1, from, from);
862
- if (!match)
863
- return false;
864
- let next = match;
865
- let changes = [], selection, replacement;
866
- let effects = [];
867
- if (next.from == from && next.to == to) {
868
- replacement = state$1.toText(query.getReplacement(next));
869
- changes.push({ from: next.from, to: next.to, insert: replacement });
870
- next = query.nextMatch(state$1, next.from, next.to);
871
- effects.push(view.EditorView.announce.of(state$1.phrase("replaced match on line $", state$1.doc.lineAt(from).number) + "."));
872
- }
873
- let changeSet = view$1.state.changes(changes);
874
- if (next) {
875
- selection = state.EditorSelection.single(next.from, next.to).map(changeSet);
876
- effects.push(announceMatch(view$1, next));
877
- effects.push(state$1.facet(searchConfigFacet).scrollToMatch(selection.main, view$1));
878
- }
879
- view$1.dispatch({
880
- changes: changeSet,
881
- selection,
882
- effects,
883
- userEvent: "input.replace"
884
- });
885
- return true;
886
- });
887
- const replaceAll = /* @__PURE__ */ searchCommand((view$1, { query }) => {
888
- if (view$1.state.readOnly)
889
- return false;
890
- let changes = query.matchAll(view$1.state, 1e9).map((match) => {
891
- let { from, to } = match;
892
- return { from, to, insert: query.getReplacement(match) };
893
- });
894
- if (!changes.length)
895
- return false;
896
- let announceText = view$1.state.phrase("replaced $ matches", changes.length) + ".";
897
- view$1.dispatch({
898
- changes,
899
- effects: view.EditorView.announce.of(announceText),
900
- userEvent: "input.replace.all"
901
- });
902
- return true;
903
- });
904
- function createSearchPanel(view) {
905
- return view.state.facet(searchConfigFacet).createPanel(view);
906
- }
907
- function defaultQuery(state, fallback) {
908
- var _a, _b, _c, _d, _e;
909
- let sel = state.selection.main;
910
- let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
911
- if (fallback && !selText)
912
- return fallback;
913
- let config = state.facet(searchConfigFacet);
914
- return new SearchQuery({
915
- search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
916
- caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
917
- literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
918
- regexp: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.regexp) !== null && _d !== void 0 ? _d : config.regexp,
919
- wholeWord: (_e = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _e !== void 0 ? _e : config.wholeWord
920
- });
921
- }
922
- function getSearchInput(view$1) {
923
- let panel = view.getPanel(view$1, createSearchPanel);
924
- return panel && panel.dom.querySelector("[main-field]");
925
- }
926
- function selectSearchInput(view) {
927
- let input = getSearchInput(view);
928
- if (input && input == view.root.activeElement)
929
- input.select();
930
- }
931
- const openSearchPanel = (view) => {
932
- let state$1 = view.state.field(searchState, false);
933
- if (state$1 && state$1.panel) {
934
- let searchInput = getSearchInput(view);
935
- if (searchInput && searchInput != view.root.activeElement) {
936
- let query = defaultQuery(view.state, state$1.query.spec);
937
- if (query.valid)
938
- view.dispatch({ effects: setSearchQuery.of(query) });
939
- searchInput.focus();
940
- searchInput.select();
941
- }
942
- } else {
943
- view.dispatch({ effects: [
944
- togglePanel$1.of(true),
945
- state$1 ? setSearchQuery.of(defaultQuery(view.state, state$1.query.spec)) : state.StateEffect.appendConfig.of(searchExtensions)
946
- ] });
947
- }
948
- return true;
949
- };
950
- const closeSearchPanel = (view$1) => {
951
- let state = view$1.state.field(searchState, false);
952
- if (!state || !state.panel)
953
- return false;
954
- let panel = view.getPanel(view$1, createSearchPanel);
955
- if (panel && panel.dom.contains(view$1.root.activeElement))
956
- view$1.focus();
957
- view$1.dispatch({ effects: togglePanel$1.of(false) });
958
- return true;
959
- };
960
- const searchKeymap = [
961
- { key: "Mod-f", run: openSearchPanel, scope: "editor search-panel" },
962
- { key: "F3", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
963
- { key: "Mod-g", run: findNext, shift: findPrevious, scope: "editor search-panel", preventDefault: true },
964
- { key: "Escape", run: closeSearchPanel, scope: "editor search-panel" },
965
- { key: "Mod-Shift-l", run: selectSelectionMatches },
966
- { key: "Mod-Alt-g", run: gotoLine },
967
- { key: "Mod-d", run: selectNextOccurrence, preventDefault: true }
968
- ];
969
- class SearchPanel {
970
- constructor(view) {
971
- this.view = view;
972
- let query = this.query = view.state.field(searchState).query.spec;
973
- this.commit = this.commit.bind(this);
974
- this.searchField = crelt("input", {
975
- value: query.search,
976
- placeholder: phrase(view, "Find"),
977
- "aria-label": phrase(view, "Find"),
978
- class: "cm-textfield",
979
- name: "search",
980
- form: "",
981
- "main-field": "true",
982
- onchange: this.commit,
983
- onkeyup: this.commit
984
- });
985
- this.replaceField = crelt("input", {
986
- value: query.replace,
987
- placeholder: phrase(view, "Replace"),
988
- "aria-label": phrase(view, "Replace"),
989
- class: "cm-textfield",
990
- name: "replace",
991
- form: "",
992
- onchange: this.commit,
993
- onkeyup: this.commit
994
- });
995
- this.caseField = crelt("input", {
996
- type: "checkbox",
997
- name: "case",
998
- form: "",
999
- checked: query.caseSensitive,
1000
- onchange: this.commit
1001
- });
1002
- this.reField = crelt("input", {
1003
- type: "checkbox",
1004
- name: "re",
1005
- form: "",
1006
- checked: query.regexp,
1007
- onchange: this.commit
1008
- });
1009
- this.wordField = crelt("input", {
1010
- type: "checkbox",
1011
- name: "word",
1012
- form: "",
1013
- checked: query.wholeWord,
1014
- onchange: this.commit
1015
- });
1016
- function button(name, onclick, content) {
1017
- return crelt("button", { class: "cm-button", name, onclick, type: "button" }, content);
1018
- }
1019
- this.dom = crelt("div", { onkeydown: (e) => this.keydown(e), class: "cm-search" }, [
1020
- this.searchField,
1021
- button("next", () => findNext(view), [phrase(view, "next")]),
1022
- button("prev", () => findPrevious(view), [phrase(view, "previous")]),
1023
- button("select", () => selectMatches(view), [phrase(view, "all")]),
1024
- crelt("label", null, [this.caseField, phrase(view, "match case")]),
1025
- crelt("label", null, [this.reField, phrase(view, "regexp")]),
1026
- crelt("label", null, [this.wordField, phrase(view, "by word")]),
1027
- ...view.state.readOnly ? [] : [
1028
- crelt("br"),
1029
- this.replaceField,
1030
- button("replace", () => replaceNext(view), [phrase(view, "replace")]),
1031
- button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
1032
- ],
1033
- crelt("button", {
1034
- name: "close",
1035
- onclick: () => closeSearchPanel(view),
1036
- "aria-label": phrase(view, "close"),
1037
- type: "button"
1038
- }, ["\xD7"])
1039
- ]);
1040
- }
1041
- commit() {
1042
- let query = new SearchQuery({
1043
- search: this.searchField.value,
1044
- caseSensitive: this.caseField.checked,
1045
- regexp: this.reField.checked,
1046
- wholeWord: this.wordField.checked,
1047
- replace: this.replaceField.value
1048
- });
1049
- if (!query.eq(this.query)) {
1050
- this.query = query;
1051
- this.view.dispatch({ effects: setSearchQuery.of(query) });
1052
- }
1053
- }
1054
- keydown(e) {
1055
- if (view.runScopeHandlers(this.view, e, "search-panel")) {
1056
- e.preventDefault();
1057
- } else if (e.keyCode == 13 && e.target == this.searchField) {
1058
- e.preventDefault();
1059
- (e.shiftKey ? findPrevious : findNext)(this.view);
1060
- } else if (e.keyCode == 13 && e.target == this.replaceField) {
1061
- e.preventDefault();
1062
- replaceNext(this.view);
1063
- }
1064
- }
1065
- update(update) {
1066
- for (let tr of update.transactions)
1067
- for (let effect of tr.effects) {
1068
- if (effect.is(setSearchQuery) && !effect.value.eq(this.query))
1069
- this.setQuery(effect.value);
1070
- }
1071
- }
1072
- setQuery(query) {
1073
- this.query = query;
1074
- this.searchField.value = query.search;
1075
- this.replaceField.value = query.replace;
1076
- this.caseField.checked = query.caseSensitive;
1077
- this.reField.checked = query.regexp;
1078
- this.wordField.checked = query.wholeWord;
1079
- }
1080
- mount() {
1081
- this.searchField.select();
1082
- }
1083
- get pos() {
1084
- return 80;
1085
- }
1086
- get top() {
1087
- return this.view.state.facet(searchConfigFacet).top;
1088
- }
1089
- }
1090
- function phrase(view, phrase2) {
1091
- return view.state.phrase(phrase2);
1092
- }
1093
- const AnnounceMargin = 30;
1094
- const Break = /[\s\.,:;?!]/;
1095
- function announceMatch(view$1, { from, to }) {
1096
- let line = view$1.state.doc.lineAt(from), lineEnd = view$1.state.doc.lineAt(to).to;
1097
- let start = Math.max(line.from, from - AnnounceMargin), end = Math.min(lineEnd, to + AnnounceMargin);
1098
- let text = view$1.state.sliceDoc(start, end);
1099
- if (start != line.from) {
1100
- for (let i = 0; i < AnnounceMargin; i++)
1101
- if (!Break.test(text[i + 1]) && Break.test(text[i])) {
1102
- text = text.slice(i);
1103
- break;
1104
- }
1105
- }
1106
- if (end != lineEnd) {
1107
- for (let i = text.length - 1; i > text.length - AnnounceMargin; i--)
1108
- if (!Break.test(text[i - 1]) && Break.test(text[i])) {
1109
- text = text.slice(0, i);
1110
- break;
1111
- }
1112
- }
1113
- return view.EditorView.announce.of(`${view$1.state.phrase("current match")}. ${text} ${view$1.state.phrase("on line")} ${line.number}.`);
1114
- }
1115
- const baseTheme$2 = /* @__PURE__ */ view.EditorView.baseTheme({
1116
- ".cm-panel.cm-search": {
1117
- padding: "2px 6px 4px",
1118
- position: "relative",
1119
- "& [name=close]": {
1120
- position: "absolute",
1121
- top: "0",
1122
- right: "4px",
1123
- backgroundColor: "inherit",
1124
- border: "none",
1125
- font: "inherit",
1126
- padding: 0,
1127
- margin: 0
1128
- },
1129
- "& input, & button, & label": {
1130
- margin: ".2em .6em .2em 0"
1131
- },
1132
- "& input[type=checkbox]": {
1133
- marginRight: ".2em"
1134
- },
1135
- "& label": {
1136
- fontSize: "80%",
1137
- whiteSpace: "pre"
1138
- }
1139
- },
1140
- "&light .cm-searchMatch": { backgroundColor: "#ffff0054" },
1141
- "&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" },
1142
- "&light .cm-searchMatch-selected": { backgroundColor: "#ff6a0054" },
1143
- "&dark .cm-searchMatch-selected": { backgroundColor: "#ff00ff8a" }
1144
- });
1145
- const searchExtensions = [
1146
- searchState,
1147
- /* @__PURE__ */ state.Prec.low(searchHighlighter),
1148
- baseTheme$2
1149
- ];
1150
-
1151
- var __defProp = Object.defineProperty;
1152
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
1153
- var __hasOwnProp = Object.prototype.hasOwnProperty;
1154
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
1155
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1156
- var __spreadValues = (a, b) => {
1157
- for (var prop in b || (b = {}))
1158
- if (__hasOwnProp.call(b, prop))
1159
- __defNormalProp(a, prop, b[prop]);
1160
- if (__getOwnPropSymbols)
1161
- for (var prop of __getOwnPropSymbols(b)) {
1162
- if (__propIsEnum.call(b, prop))
1163
- __defNormalProp(a, prop, b[prop]);
1164
- }
1165
- return a;
1166
- };
1167
- class SelectedDiagnostic {
1168
- constructor(from, to, diagnostic) {
1169
- this.from = from;
1170
- this.to = to;
1171
- this.diagnostic = diagnostic;
1172
- }
1173
- }
1174
- class LintState {
1175
- constructor(diagnostics, panel, selected) {
1176
- this.diagnostics = diagnostics;
1177
- this.panel = panel;
1178
- this.selected = selected;
1179
- }
1180
- static init(diagnostics, panel, state$1) {
1181
- let diagnosticFilter = state$1.facet(lintConfig).markerFilter;
1182
- if (diagnosticFilter)
1183
- diagnostics = diagnosticFilter(diagnostics, state$1);
1184
- let sorted = diagnostics.slice().sort((a, b) => a.from - b.from || a.to - b.to);
1185
- let deco = new state.RangeSetBuilder(), active = [], pos = 0;
1186
- let scan = state$1.doc.iter(), scanPos = 0, docLen = state$1.doc.length;
1187
- for (let i = 0; ; ) {
1188
- let next = i == sorted.length ? null : sorted[i];
1189
- if (!next && !active.length)
1190
- break;
1191
- let from, to;
1192
- if (active.length) {
1193
- from = pos;
1194
- to = active.reduce((p, d) => Math.min(p, d.to), next && next.from > from ? next.from : 1e8);
1195
- } else {
1196
- from = next.from;
1197
- if (from > docLen)
1198
- break;
1199
- to = next.to;
1200
- active.push(next);
1201
- i++;
1202
- }
1203
- while (i < sorted.length) {
1204
- let next2 = sorted[i];
1205
- if (next2.from == from && (next2.to > next2.from || next2.to == from)) {
1206
- active.push(next2);
1207
- i++;
1208
- to = Math.min(next2.to, to);
1209
- } else {
1210
- to = Math.min(next2.from, to);
1211
- break;
1212
- }
1213
- }
1214
- to = Math.min(to, docLen);
1215
- let widget = false;
1216
- if (active.some((d) => d.from == from && (d.to == to || to == docLen))) {
1217
- widget = from == to;
1218
- if (!widget && to - from < 10) {
1219
- let behind = from - (scanPos + scan.value.length);
1220
- if (behind > 0) {
1221
- scan.next(behind);
1222
- scanPos = from;
1223
- }
1224
- for (let check = from; ; ) {
1225
- if (check >= to) {
1226
- widget = true;
1227
- break;
1228
- }
1229
- if (!scan.lineBreak && scanPos + scan.value.length > check)
1230
- break;
1231
- check = scanPos + scan.value.length;
1232
- scanPos += scan.value.length;
1233
- scan.next();
1234
- }
1235
- }
1236
- }
1237
- let sev = maxSeverity(active);
1238
- if (widget) {
1239
- deco.add(from, from, view.Decoration.widget({
1240
- widget: new DiagnosticWidget(sev),
1241
- diagnostics: active.slice()
1242
- }));
1243
- } else {
1244
- let markClass = active.reduce((c, d) => d.markClass ? c + " " + d.markClass : c, "");
1245
- deco.add(from, to, view.Decoration.mark({
1246
- class: "cm-lintRange cm-lintRange-" + sev + markClass,
1247
- diagnostics: active.slice(),
1248
- inclusiveEnd: active.some((a) => a.to > to)
1249
- }));
1250
- }
1251
- pos = to;
1252
- if (pos == docLen)
1253
- break;
1254
- for (let i2 = 0; i2 < active.length; i2++)
1255
- if (active[i2].to <= pos)
1256
- active.splice(i2--, 1);
1257
- }
1258
- let set = deco.finish();
1259
- return new LintState(set, panel, findDiagnostic(set));
1260
- }
1261
- }
1262
- function findDiagnostic(diagnostics, diagnostic = null, after = 0) {
1263
- let found = null;
1264
- diagnostics.between(after, 1e9, (from, to, { spec }) => {
1265
- if (diagnostic && spec.diagnostics.indexOf(diagnostic) < 0)
1266
- return;
1267
- if (!found)
1268
- found = new SelectedDiagnostic(from, to, diagnostic || spec.diagnostics[0]);
1269
- else if (spec.diagnostics.indexOf(found.diagnostic) < 0)
1270
- return false;
1271
- else
1272
- found = new SelectedDiagnostic(found.from, to, found.diagnostic);
1273
- });
1274
- return found;
1275
- }
1276
- function hideTooltip(tr, tooltip) {
1277
- let from = tooltip.pos, to = tooltip.end || from;
1278
- let result = tr.state.facet(lintConfig).hideOn(tr, from, to);
1279
- if (result != null)
1280
- return result;
1281
- let line = tr.startState.doc.lineAt(tooltip.pos);
1282
- return !!(tr.effects.some((e) => e.is(setDiagnosticsEffect)) || tr.changes.touchesRange(line.from, Math.max(line.to, to)));
1283
- }
1284
- function maybeEnableLint(state$1, effects) {
1285
- return state$1.field(lintState, false) ? effects : effects.concat(state.StateEffect.appendConfig.of(lintExtensions));
1286
- }
1287
- const setDiagnosticsEffect = /* @__PURE__ */ state.StateEffect.define();
1288
- const togglePanel = /* @__PURE__ */ state.StateEffect.define();
1289
- const movePanelSelection = /* @__PURE__ */ state.StateEffect.define();
1290
- const lintState = /* @__PURE__ */ state.StateField.define({
1291
- create() {
1292
- return new LintState(view.Decoration.none, null, null);
1293
- },
1294
- update(value, tr) {
1295
- if (tr.docChanged && value.diagnostics.size) {
1296
- let mapped = value.diagnostics.map(tr.changes), selected = null, panel = value.panel;
1297
- if (value.selected) {
1298
- let selPos = tr.changes.mapPos(value.selected.from, 1);
1299
- selected = findDiagnostic(mapped, value.selected.diagnostic, selPos) || findDiagnostic(mapped, null, selPos);
1300
- }
1301
- if (!mapped.size && panel && tr.state.facet(lintConfig).autoPanel)
1302
- panel = null;
1303
- value = new LintState(mapped, panel, selected);
1304
- }
1305
- for (let effect of tr.effects) {
1306
- if (effect.is(setDiagnosticsEffect)) {
1307
- let panel = !tr.state.facet(lintConfig).autoPanel ? value.panel : effect.value.length ? LintPanel.open : null;
1308
- value = LintState.init(effect.value, panel, tr.state);
1309
- } else if (effect.is(togglePanel)) {
1310
- value = new LintState(value.diagnostics, effect.value ? LintPanel.open : null, value.selected);
1311
- } else if (effect.is(movePanelSelection)) {
1312
- value = new LintState(value.diagnostics, value.panel, effect.value);
1313
- }
1314
- }
1315
- return value;
1316
- },
1317
- provide: (f) => [
1318
- view.showPanel.from(f, (val) => val.panel),
1319
- view.EditorView.decorations.from(f, (s) => s.diagnostics)
1320
- ]
1321
- });
1322
- const activeMark = /* @__PURE__ */ view.Decoration.mark({ class: "cm-lintRange cm-lintRange-active" });
1323
- function lintTooltip(view, pos, side) {
1324
- let { diagnostics } = view.state.field(lintState);
1325
- let found, start = -1, end = -1;
1326
- diagnostics.between(pos - (side < 0 ? 1 : 0), pos + (side > 0 ? 1 : 0), (from, to, { spec }) => {
1327
- if (pos >= from && pos <= to && (from == to || (pos > from || side > 0) && (pos < to || side < 0))) {
1328
- found = spec.diagnostics;
1329
- start = from;
1330
- end = to;
1331
- return false;
1332
- }
1333
- });
1334
- let diagnosticFilter = view.state.facet(lintConfig).tooltipFilter;
1335
- if (found && diagnosticFilter)
1336
- found = diagnosticFilter(found, view.state);
1337
- if (!found)
1338
- return null;
1339
- return {
1340
- pos: start,
1341
- end,
1342
- above: view.state.doc.lineAt(start).to < end,
1343
- create() {
1344
- return { dom: diagnosticsTooltip(view, found) };
1345
- }
1346
- };
1347
- }
1348
- function diagnosticsTooltip(view, diagnostics) {
1349
- return crelt("ul", { class: "cm-tooltip-lint" }, diagnostics.map((d) => renderDiagnostic(view, d, false)));
1350
- }
1351
- const openLintPanel = (view$1) => {
1352
- let field = view$1.state.field(lintState, false);
1353
- if (!field || !field.panel)
1354
- view$1.dispatch({ effects: maybeEnableLint(view$1.state, [togglePanel.of(true)]) });
1355
- let panel = view.getPanel(view$1, LintPanel.open);
1356
- if (panel)
1357
- panel.dom.querySelector(".cm-panel-lint ul").focus();
1358
- return true;
1359
- };
1360
- const closeLintPanel = (view) => {
1361
- let field = view.state.field(lintState, false);
1362
- if (!field || !field.panel)
1363
- return false;
1364
- view.dispatch({ effects: togglePanel.of(false) });
1365
- return true;
1366
- };
1367
- const nextDiagnostic = (view) => {
1368
- let field = view.state.field(lintState, false);
1369
- if (!field)
1370
- return false;
1371
- let sel = view.state.selection.main, next = field.diagnostics.iter(sel.to + 1);
1372
- if (!next.value) {
1373
- next = field.diagnostics.iter(0);
1374
- if (!next.value || next.from == sel.from && next.to == sel.to)
1375
- return false;
1376
- }
1377
- view.dispatch({ selection: { anchor: next.from, head: next.to }, scrollIntoView: true });
1378
- return true;
1379
- };
1380
- const lintKeymap = [
1381
- { key: "Mod-Shift-m", run: openLintPanel, preventDefault: true },
1382
- { key: "F8", run: nextDiagnostic }
1383
- ];
1384
- const lintConfig = /* @__PURE__ */ state.Facet.define({
1385
- combine(input) {
1386
- return __spreadValues({
1387
- sources: input.map((i) => i.source).filter((x) => x != null)
1388
- }, state.combineConfig(input.map((i) => i.config), {
1389
- delay: 750,
1390
- markerFilter: null,
1391
- tooltipFilter: null,
1392
- needsRefresh: null,
1393
- hideOn: () => null
1394
- }, {
1395
- delay: Math.max,
1396
- markerFilter: combineFilter,
1397
- tooltipFilter: combineFilter,
1398
- needsRefresh: (a, b) => !a ? b : !b ? a : (u) => a(u) || b(u),
1399
- hideOn: (a, b) => !a ? b : !b ? a : (t, x, y) => a(t, x, y) || b(t, x, y),
1400
- autoPanel: (a, b) => a || b
1401
- }));
1402
- }
1403
- });
1404
- function combineFilter(a, b) {
1405
- return !a ? b : !b ? a : (d, s) => b(a(d, s), s);
1406
- }
1407
- function assignKeys(actions) {
1408
- let assigned = [];
1409
- if (actions)
1410
- actions: for (let { name } of actions) {
1411
- for (let i = 0; i < name.length; i++) {
1412
- let ch = name[i];
1413
- if (/[a-zA-Z]/.test(ch) && !assigned.some((c) => c.toLowerCase() == ch.toLowerCase())) {
1414
- assigned.push(ch);
1415
- continue actions;
1416
- }
1417
- }
1418
- assigned.push("");
1419
- }
1420
- return assigned;
1421
- }
1422
- function renderDiagnostic(view, diagnostic, inPanel) {
1423
- var _a;
1424
- let keys = inPanel ? assignKeys(diagnostic.actions) : [];
1425
- return crelt("li", { class: "cm-diagnostic cm-diagnostic-" + diagnostic.severity }, crelt("span", { class: "cm-diagnosticText" }, diagnostic.renderMessage ? diagnostic.renderMessage(view) : diagnostic.message), (_a = diagnostic.actions) === null || _a === void 0 ? void 0 : _a.map((action, i) => {
1426
- let fired = false, click = (e) => {
1427
- e.preventDefault();
1428
- if (fired)
1429
- return;
1430
- fired = true;
1431
- let found = findDiagnostic(view.state.field(lintState).diagnostics, diagnostic);
1432
- if (found)
1433
- action.apply(view, found.from, found.to);
1434
- };
1435
- let { name } = action, keyIndex = keys[i] ? name.indexOf(keys[i]) : -1;
1436
- let nameElt = keyIndex < 0 ? name : [
1437
- name.slice(0, keyIndex),
1438
- crelt("u", name.slice(keyIndex, keyIndex + 1)),
1439
- name.slice(keyIndex + 1)
1440
- ];
1441
- let markClass = action.markClass ? " " + action.markClass : "";
1442
- return crelt("button", {
1443
- type: "button",
1444
- class: "cm-diagnosticAction" + markClass,
1445
- onclick: click,
1446
- onmousedown: click,
1447
- "aria-label": ` Action: ${name}${keyIndex < 0 ? "" : ` (access key "${keys[i]})"`}.`
1448
- }, nameElt);
1449
- }), diagnostic.source && crelt("div", { class: "cm-diagnosticSource" }, diagnostic.source));
1450
- }
1451
- class DiagnosticWidget extends view.WidgetType {
1452
- constructor(sev) {
1453
- super();
1454
- this.sev = sev;
1455
- }
1456
- eq(other) {
1457
- return other.sev == this.sev;
1458
- }
1459
- toDOM() {
1460
- return crelt("span", { class: "cm-lintPoint cm-lintPoint-" + this.sev });
1461
- }
1462
- }
1463
- class PanelItem {
1464
- constructor(view, diagnostic) {
1465
- this.diagnostic = diagnostic;
1466
- this.id = "item_" + Math.floor(Math.random() * 4294967295).toString(16);
1467
- this.dom = renderDiagnostic(view, diagnostic, true);
1468
- this.dom.id = this.id;
1469
- this.dom.setAttribute("role", "option");
1470
- }
1471
- }
1472
- class LintPanel {
1473
- constructor(view) {
1474
- this.view = view;
1475
- this.items = [];
1476
- let onkeydown = (event) => {
1477
- if (event.keyCode == 27) {
1478
- closeLintPanel(this.view);
1479
- this.view.focus();
1480
- } else if (event.keyCode == 38 || event.keyCode == 33) {
1481
- this.moveSelection((this.selectedIndex - 1 + this.items.length) % this.items.length);
1482
- } else if (event.keyCode == 40 || event.keyCode == 34) {
1483
- this.moveSelection((this.selectedIndex + 1) % this.items.length);
1484
- } else if (event.keyCode == 36) {
1485
- this.moveSelection(0);
1486
- } else if (event.keyCode == 35) {
1487
- this.moveSelection(this.items.length - 1);
1488
- } else if (event.keyCode == 13) {
1489
- this.view.focus();
1490
- } else if (event.keyCode >= 65 && event.keyCode <= 90 && this.selectedIndex >= 0) {
1491
- let { diagnostic } = this.items[this.selectedIndex], keys = assignKeys(diagnostic.actions);
1492
- for (let i = 0; i < keys.length; i++)
1493
- if (keys[i].toUpperCase().charCodeAt(0) == event.keyCode) {
1494
- let found = findDiagnostic(this.view.state.field(lintState).diagnostics, diagnostic);
1495
- if (found)
1496
- diagnostic.actions[i].apply(view, found.from, found.to);
1497
- }
1498
- } else {
1499
- return;
1500
- }
1501
- event.preventDefault();
1502
- };
1503
- let onclick = (event) => {
1504
- for (let i = 0; i < this.items.length; i++) {
1505
- if (this.items[i].dom.contains(event.target))
1506
- this.moveSelection(i);
1507
- }
1508
- };
1509
- this.list = crelt("ul", {
1510
- tabIndex: 0,
1511
- role: "listbox",
1512
- "aria-label": this.view.state.phrase("Diagnostics"),
1513
- onkeydown,
1514
- onclick
1515
- });
1516
- this.dom = crelt("div", { class: "cm-panel-lint" }, this.list, crelt("button", {
1517
- type: "button",
1518
- name: "close",
1519
- "aria-label": this.view.state.phrase("close"),
1520
- onclick: () => closeLintPanel(this.view)
1521
- }, "\xD7"));
1522
- this.update();
1523
- }
1524
- get selectedIndex() {
1525
- let selected = this.view.state.field(lintState).selected;
1526
- if (!selected)
1527
- return -1;
1528
- for (let i = 0; i < this.items.length; i++)
1529
- if (this.items[i].diagnostic == selected.diagnostic)
1530
- return i;
1531
- return -1;
1532
- }
1533
- update() {
1534
- let { diagnostics, selected } = this.view.state.field(lintState);
1535
- let i = 0, needsSync = false, newSelectedItem = null;
1536
- let seen = /* @__PURE__ */ new Set();
1537
- diagnostics.between(0, this.view.state.doc.length, (_start, _end, { spec }) => {
1538
- for (let diagnostic of spec.diagnostics) {
1539
- if (seen.has(diagnostic))
1540
- continue;
1541
- seen.add(diagnostic);
1542
- let found = -1, item;
1543
- for (let j = i; j < this.items.length; j++)
1544
- if (this.items[j].diagnostic == diagnostic) {
1545
- found = j;
1546
- break;
1547
- }
1548
- if (found < 0) {
1549
- item = new PanelItem(this.view, diagnostic);
1550
- this.items.splice(i, 0, item);
1551
- needsSync = true;
1552
- } else {
1553
- item = this.items[found];
1554
- if (found > i) {
1555
- this.items.splice(i, found - i);
1556
- needsSync = true;
1557
- }
1558
- }
1559
- if (selected && item.diagnostic == selected.diagnostic) {
1560
- if (!item.dom.hasAttribute("aria-selected")) {
1561
- item.dom.setAttribute("aria-selected", "true");
1562
- newSelectedItem = item;
1563
- }
1564
- } else if (item.dom.hasAttribute("aria-selected")) {
1565
- item.dom.removeAttribute("aria-selected");
1566
- }
1567
- i++;
1568
- }
1569
- });
1570
- while (i < this.items.length && !(this.items.length == 1 && this.items[0].diagnostic.from < 0)) {
1571
- needsSync = true;
1572
- this.items.pop();
1573
- }
1574
- if (this.items.length == 0) {
1575
- this.items.push(new PanelItem(this.view, {
1576
- from: -1,
1577
- to: -1,
1578
- severity: "info",
1579
- message: this.view.state.phrase("No diagnostics")
1580
- }));
1581
- needsSync = true;
1582
- }
1583
- if (newSelectedItem) {
1584
- this.list.setAttribute("aria-activedescendant", newSelectedItem.id);
1585
- this.view.requestMeasure({
1586
- key: this,
1587
- read: () => ({ sel: newSelectedItem.dom.getBoundingClientRect(), panel: this.list.getBoundingClientRect() }),
1588
- write: ({ sel, panel }) => {
1589
- let scaleY = panel.height / this.list.offsetHeight;
1590
- if (sel.top < panel.top)
1591
- this.list.scrollTop -= (panel.top - sel.top) / scaleY;
1592
- else if (sel.bottom > panel.bottom)
1593
- this.list.scrollTop += (sel.bottom - panel.bottom) / scaleY;
1594
- }
1595
- });
1596
- } else if (this.selectedIndex < 0) {
1597
- this.list.removeAttribute("aria-activedescendant");
1598
- }
1599
- if (needsSync)
1600
- this.sync();
1601
- }
1602
- sync() {
1603
- let domPos = this.list.firstChild;
1604
- function rm() {
1605
- let prev = domPos;
1606
- domPos = prev.nextSibling;
1607
- prev.remove();
1608
- }
1609
- for (let item of this.items) {
1610
- if (item.dom.parentNode == this.list) {
1611
- while (domPos != item.dom)
1612
- rm();
1613
- domPos = item.dom.nextSibling;
1614
- } else {
1615
- this.list.insertBefore(item.dom, domPos);
1616
- }
1617
- }
1618
- while (domPos)
1619
- rm();
1620
- }
1621
- moveSelection(selectedIndex) {
1622
- if (this.selectedIndex < 0)
1623
- return;
1624
- let field = this.view.state.field(lintState);
1625
- let selection = findDiagnostic(field.diagnostics, this.items[selectedIndex].diagnostic);
1626
- if (!selection)
1627
- return;
1628
- this.view.dispatch({
1629
- selection: { anchor: selection.from, head: selection.to },
1630
- scrollIntoView: true,
1631
- effects: movePanelSelection.of(selection)
1632
- });
1633
- }
1634
- static open(view) {
1635
- return new LintPanel(view);
1636
- }
1637
- }
1638
- function svg(content, attrs = `viewBox="0 0 40 40"`) {
1639
- return `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${encodeURIComponent(content)}</svg>')`;
1640
- }
1641
- function underline(color) {
1642
- return svg(`<path d="m0 2.5 l2 -1.5 l1 0 l2 1.5 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>`, `width="6" height="3"`);
1643
- }
1644
- const baseTheme = /* @__PURE__ */ view.EditorView.baseTheme({
1645
- ".cm-diagnostic": {
1646
- padding: "3px 6px 3px 8px",
1647
- marginLeft: "-1px",
1648
- display: "block",
1649
- whiteSpace: "pre-wrap"
1650
- },
1651
- ".cm-diagnostic-error": { borderLeft: "5px solid #d11" },
1652
- ".cm-diagnostic-warning": { borderLeft: "5px solid orange" },
1653
- ".cm-diagnostic-info": { borderLeft: "5px solid #999" },
1654
- ".cm-diagnostic-hint": { borderLeft: "5px solid #66d" },
1655
- ".cm-diagnosticAction": {
1656
- font: "inherit",
1657
- border: "none",
1658
- padding: "2px 4px",
1659
- backgroundColor: "#444",
1660
- color: "white",
1661
- borderRadius: "3px",
1662
- marginLeft: "8px",
1663
- cursor: "pointer"
1664
- },
1665
- ".cm-diagnosticSource": {
1666
- fontSize: "70%",
1667
- opacity: 0.7
1668
- },
1669
- ".cm-lintRange": {
1670
- backgroundPosition: "left bottom",
1671
- backgroundRepeat: "repeat-x",
1672
- paddingBottom: "0.7px"
1673
- },
1674
- ".cm-lintRange-error": { backgroundImage: /* @__PURE__ */ underline("#d11") },
1675
- ".cm-lintRange-warning": { backgroundImage: /* @__PURE__ */ underline("orange") },
1676
- ".cm-lintRange-info": { backgroundImage: /* @__PURE__ */ underline("#999") },
1677
- ".cm-lintRange-hint": { backgroundImage: /* @__PURE__ */ underline("#66d") },
1678
- ".cm-lintRange-active": { backgroundColor: "#ffdd9980" },
1679
- ".cm-tooltip-lint": {
1680
- padding: 0,
1681
- margin: 0
1682
- },
1683
- ".cm-lintPoint": {
1684
- position: "relative",
1685
- "&:after": {
1686
- content: '""',
1687
- position: "absolute",
1688
- bottom: 0,
1689
- left: "-2px",
1690
- borderLeft: "3px solid transparent",
1691
- borderRight: "3px solid transparent",
1692
- borderBottom: "4px solid #d11"
1693
- }
1694
- },
1695
- ".cm-lintPoint-warning": {
1696
- "&:after": { borderBottomColor: "orange" }
1697
- },
1698
- ".cm-lintPoint-info": {
1699
- "&:after": { borderBottomColor: "#999" }
1700
- },
1701
- ".cm-lintPoint-hint": {
1702
- "&:after": { borderBottomColor: "#66d" }
1703
- },
1704
- ".cm-panel.cm-panel-lint": {
1705
- position: "relative",
1706
- "& ul": {
1707
- maxHeight: "100px",
1708
- overflowY: "auto",
1709
- "& [aria-selected]": {
1710
- backgroundColor: "#ddd",
1711
- "& u": { textDecoration: "underline" }
1712
- },
1713
- "&:focus [aria-selected]": {
1714
- background_fallback: "#bdf",
1715
- backgroundColor: "Highlight",
1716
- color_fallback: "white",
1717
- color: "HighlightText"
1718
- },
1719
- "& u": { textDecoration: "none" },
1720
- padding: 0,
1721
- margin: 0
1722
- },
1723
- "& [name=close]": {
1724
- position: "absolute",
1725
- top: "0",
1726
- right: "2px",
1727
- background: "inherit",
1728
- border: "none",
1729
- font: "inherit",
1730
- padding: 0,
1731
- margin: 0
1732
- }
1733
- }
1734
- });
1735
- function severityWeight(sev) {
1736
- return sev == "error" ? 4 : sev == "warning" ? 3 : sev == "info" ? 2 : 1;
1737
- }
1738
- function maxSeverity(diagnostics) {
1739
- let sev = "hint", weight = 1;
1740
- for (let d of diagnostics) {
1741
- let w = severityWeight(d.severity);
1742
- if (w > weight) {
1743
- weight = w;
1744
- sev = d.severity;
1745
- }
1746
- }
1747
- return sev;
1748
- }
1749
- const lintExtensions = [
1750
- lintState,
1751
- /* @__PURE__ */ view.EditorView.decorations.compute([lintState], (state) => {
1752
- let { selected, panel } = state.field(lintState);
1753
- return !selected || !panel || selected.from == selected.to ? view.Decoration.none : view.Decoration.set([
1754
- activeMark.range(selected.from, selected.to)
1755
- ]);
1756
- }),
1757
- /* @__PURE__ */ view.hoverTooltip(lintTooltip, { hideOn: hideTooltip }),
1758
- baseTheme
1759
- ];
1760
-
1761
13
  const basicSetup = ({ useLineNumbers }) => [
1762
14
  useLineNumbers ? [view.lineNumbers()] : [],
1763
15
  view.highlightActiveLineGutter(),
@@ -1775,15 +27,15 @@ const basicSetup = ({ useLineNumbers }) => [
1775
27
  view.rectangularSelection(),
1776
28
  view.crosshairCursor(),
1777
29
  view.highlightActiveLine(),
1778
- highlightSelectionMatches(),
30
+ search.highlightSelectionMatches(),
1779
31
  view.keymap.of([
1780
32
  ...autocomplete.closeBracketsKeymap,
1781
33
  ...commands.defaultKeymap,
1782
- ...searchKeymap,
34
+ ...search.searchKeymap,
1783
35
  ...commands.historyKeymap,
1784
36
  ...language.foldKeymap,
1785
37
  ...autocomplete.completionKeymap,
1786
- ...lintKeymap
38
+ ...lint.lintKeymap
1787
39
  ])
1788
40
  ];
1789
41
 
@@ -1791,7 +43,7 @@ const bootstrap = view.EditorView.theme({
1791
43
  "&": {
1792
44
  backgroundColor: "var(--bs-body-bg)",
1793
45
  color: "var(--bs-body-color)",
1794
- border: "var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important",
46
+ border: "var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)",
1795
47
  "--bs-border-width": "1px",
1796
48
  borderRadius: "var(--bs-border-radius)",
1797
49
  fontSize: "0.8125rem !important",
@@ -1799,6 +51,19 @@ const bootstrap = view.EditorView.theme({
1799
51
  transition: "border-color .15s ease-in-out, box-shadow .15s ease-in-out",
1800
52
  resize: "vertical"
1801
53
  },
54
+ ":host(.is-invalid) &": {
55
+ borderColor: "var(--bs-form-invalid-border-color)",
56
+ paddingRight: "calc(1.5em + .75rem)",
57
+ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e")`,
58
+ backgroundRepeat: "no-repeat",
59
+ backgroundSize: "calc(.75em + .375rem) calc(.75em + .375rem)",
60
+ backgroundPosition: "top calc(.375em + .1875rem) right calc(.375em + .1875rem)"
61
+ },
62
+ ":host(.form-control-plaintext) &:has(.cm-content[contenteditable=false])": {
63
+ "--bs-body-bg": "transparent",
64
+ "--bs-border-color": "transparent",
65
+ "resize": "none"
66
+ },
1802
67
  ".cm-line": {
1803
68
  padding: "0 0.75rem"
1804
69
  },
@@ -1806,7 +71,7 @@ const bootstrap = view.EditorView.theme({
1806
71
  padding: "0.375rem 0",
1807
72
  animation: "fade-to-colors 0.5s ease-out 0s forwards"
1808
73
  },
1809
- "&:not(.cm-focused) .cm-selectionBackground, &:not(.cm-focused) .cm-activeLine, &:not(.cm-focused) .cm-activeLineGutter": {
74
+ "&:not(.cm-focused) .cm-activeLine, &:not(.cm-focused) .cm-activeLineGutter": {
1810
75
  backgroundColor: "transparent"
1811
76
  },
1812
77
  "&.cm-focused": {
@@ -1908,21 +173,44 @@ const setTheme = (themeContainer, editor) => editor.dispatch({ effects: themes.g
1908
173
  const themes = /* @__PURE__ */ new WeakMap();
1909
174
  const editors = /* @__PURE__ */ new WeakMap();
1910
175
  class ExpressionEditor extends HTMLTextAreaElement {
176
+ get editableExtension() {
177
+ return [
178
+ view.EditorView.editable.of(!this.readOnly && !this.disabled),
179
+ state.EditorState.readOnly.of(this.readOnly || this.disabled)
180
+ ];
181
+ }
1911
182
  constructor() {
1912
183
  super();
1913
184
  this.theme = new state.Compartment();
1914
185
  this.instanceStyles = new state.Compartment();
1915
- this.dom = document.createElement("div");
1916
- const shadow = this.dom.attachShadow({ mode: "closed" });
186
+ const readonly = new state.Compartment();
187
+ new MutationObserver(() => this.editorView.dispatch({ effects: readonly.reconfigure(this.editableExtension) })).observe(this, { attributes: true, attributeFilter: ["disabled", "readonly"] });
188
+ this.dom = document.createElement("expression-editor-container");
189
+ this.dom.className = this.className;
190
+ this.dom.style.display = "contents";
191
+ const shadow = this.dom.attachShadow({ mode: "closed", delegatesFocus: true });
1917
192
  this.editorView = new view.EditorView({
1918
193
  extensions: [
194
+ readonly.of(this.editableExtension),
1919
195
  this.instanceStyles.of(view.EditorView.theme({})),
1920
196
  basicSetup({
1921
197
  useLineNumbers: JSON.parse(this.dataset.lineNumbers || "false")
1922
198
  }),
1923
199
  this.theme.of(light),
1924
200
  view.keymap.of([...commands.defaultKeymap, { key: "Tab", run: autocomplete.acceptCompletion }]),
1925
- codemirrorLangEl.expressionlanguage(JSON.parse(this.dataset.config || "{}")),
201
+ codemirrorLangEl.expressionlanguage(JSON.parse(this.dataset.config || "{}"), [
202
+ view.EditorView.updateListener.of((e) => {
203
+ if (!this.dom.internals_) {
204
+ return;
205
+ }
206
+ const diagnosticEffects = e.transactions.flatMap((x) => x.effects.filter((v) => v.is(lint.setDiagnosticsEffect)));
207
+ if (!diagnosticEffects.length) {
208
+ return;
209
+ }
210
+ const hasErrors = diagnosticEffects.some((v) => v.value.some((x) => x.severity == "error"));
211
+ this.dom.internals_.setValidity({ badInput: hasErrors }, hasErrors ? "Invalid expression" : null, this.dom);
212
+ })
213
+ ]),
1926
214
  view.EditorView.updateListener.of((e) => {
1927
215
  if (e.docChanged) {
1928
216
  this.value = e.state.doc.toString();
@@ -1954,8 +242,27 @@ class ExpressionEditor extends HTMLTextAreaElement {
1954
242
  });
1955
243
  this.replaceWith(this.dom);
1956
244
  this.dom.appendChild(this);
245
+ this.dom.addEventListener("focus", () => {
246
+ let selected = false;
247
+ lint.forEachDiagnostic(this.editorView.state, (d, from, to) => {
248
+ if (selected || d.severity !== "error") {
249
+ return;
250
+ }
251
+ this.editorView.dispatch({ selection: state.EditorSelection.create([state.EditorSelection.range(from, to)]) });
252
+ this.editorView.focus();
253
+ selected = true;
254
+ });
255
+ });
256
+ }
257
+ }
258
+ class ExpressionEditorContainer extends HTMLElement {
259
+ constructor() {
260
+ super();
261
+ this.internals_ = this.attachInternals();
1957
262
  }
1958
263
  }
264
+ ExpressionEditorContainer.formAssociated = true;
1959
265
  customElements.define("expression-editor", ExpressionEditor, { extends: "textarea" });
266
+ customElements.define("expression-editor-container", ExpressionEditorContainer);
1960
267
 
1961
268
  exports.ExpressionEditor = ExpressionEditor;