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/CHANGELOG.md +9 -0
- package/README.md +66 -5
- package/dist/index.cjs +65 -1758
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +65 -1758
- package/example.html +32 -9
- package/package.json +4 -2
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)
|
|
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-
|
|
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
|
-
|
|
1916
|
-
|
|
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;
|