@batchfy/codemirror-minimap 0.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/README.md +49 -0
- package/dist/Config.d.ts +63 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Gutters.d.ts +11 -0
- package/dist/Gutters.d.ts.map +1 -0
- package/dist/LinesState.d.ts +14 -0
- package/dist/LinesState.d.ts.map +1 -0
- package/dist/Overlay.d.ts +2 -0
- package/dist/Overlay.d.ts.map +1 -0
- package/dist/diagnostics.d.ts +29 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1163 -0
- package/dist/index.js.map +1 -0
- package/dist/linebasedstate.d.ts +9 -0
- package/dist/linebasedstate.d.ts.map +1 -0
- package/dist/selections.d.ts +20 -0
- package/dist/selections.d.ts.map +1 -0
- package/dist/text.d.ts +38 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/Config.ts +110 -0
- package/src/Gutters.ts +29 -0
- package/src/LinesState.ts +96 -0
- package/src/Overlay.ts +320 -0
- package/src/diagnostics.ts +171 -0
- package/src/index.ts +368 -0
- package/src/linebasedstate.ts +21 -0
- package/src/selections.ts +212 -0
- package/src/text.ts +465 -0
- package/src/types.ts +7 -0
package/src/text.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { LineBasedState } from "./linebasedstate.js";
|
|
2
|
+
import { Highlighter, highlightTree } from "@lezer/highlight";
|
|
3
|
+
import { ChangedRange, Tree, TreeFragment } from "@lezer/common";
|
|
4
|
+
import { highlightingFor, language } from "@codemirror/language";
|
|
5
|
+
import { EditorView, ViewUpdate } from "@codemirror/view";
|
|
6
|
+
import { DrawContext } from "./types.js";
|
|
7
|
+
import { Config, Options, Scale } from "./Config.js";
|
|
8
|
+
import { LinesState, foldsChanged } from "./LinesState.js";
|
|
9
|
+
import crelt from "crelt";
|
|
10
|
+
import { ChangeSet, EditorState } from "@codemirror/state";
|
|
11
|
+
|
|
12
|
+
const NON_WHITESPACE = /\S+/g;
|
|
13
|
+
|
|
14
|
+
type TagSpan = { text: string; tags: string };
|
|
15
|
+
type FontInfo = { color: string; font: string; lineHeight: number };
|
|
16
|
+
|
|
17
|
+
export class TextState extends LineBasedState<Array<TagSpan>> {
|
|
18
|
+
private _previousTree: Tree | undefined;
|
|
19
|
+
private _displayText: Required<Options>["displayText"] | undefined;
|
|
20
|
+
private _fontInfoMap: Map<string, FontInfo> = new Map();
|
|
21
|
+
private _themeClasses: Set<string> | undefined;
|
|
22
|
+
private _highlightingCallbackId: number | undefined;
|
|
23
|
+
private _charWidthCache: { font: string; charWidth: number } | undefined;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Font metrics are derived from `getComputedStyle`, which is only correct
|
|
27
|
+
* once the fonts they describe have actually loaded. A web font that
|
|
28
|
+
* resolves after the first render would otherwise leave every cached
|
|
29
|
+
* measurement stale for the lifetime of the view.
|
|
30
|
+
*/
|
|
31
|
+
private readonly onFontsLoaded = () => {
|
|
32
|
+
this._fontInfoMap.clear();
|
|
33
|
+
this._charWidthCache = undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
public constructor(view: EditorView) {
|
|
37
|
+
super(view);
|
|
38
|
+
|
|
39
|
+
this._themeClasses = new Set(view.dom.classList.values());
|
|
40
|
+
document.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
41
|
+
|
|
42
|
+
if (view.state.facet(Config).enabled) {
|
|
43
|
+
this.updateImpl(view.state);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public destroy() {
|
|
48
|
+
document.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private shouldUpdate(update: ViewUpdate, themeChanged: boolean) {
|
|
52
|
+
// If the doc changed
|
|
53
|
+
if (update.docChanged) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If configuration settings changed
|
|
58
|
+
if (update.state.facet(Config) !== update.startState.facet(Config)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If the theme changed
|
|
63
|
+
if (themeChanged) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If the folds changed
|
|
68
|
+
if (foldsChanged(update.transactions)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public update(update: ViewUpdate) {
|
|
76
|
+
/**
|
|
77
|
+
* `themeChanged` consumes the pending change: it records the current
|
|
78
|
+
* classes as it compares them, so a second call in the same update
|
|
79
|
+
* always reports "unchanged". Sample it once and pass the result down.
|
|
80
|
+
*/
|
|
81
|
+
const themeChanged = this.themeChanged();
|
|
82
|
+
|
|
83
|
+
if (!this.shouldUpdate(update, themeChanged)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this._highlightingCallbackId) {
|
|
88
|
+
if (typeof window.requestIdleCallback !== "undefined") {
|
|
89
|
+
cancelIdleCallback(this._highlightingCallbackId);
|
|
90
|
+
} else {
|
|
91
|
+
clearTimeout(this._highlightingCallbackId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.updateImpl(update.state, update.changes, themeChanged);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private updateImpl(
|
|
99
|
+
state: EditorState,
|
|
100
|
+
changes?: ChangeSet,
|
|
101
|
+
themeChanged = true,
|
|
102
|
+
) {
|
|
103
|
+
this.map.clear();
|
|
104
|
+
|
|
105
|
+
/* Store display text setting for rendering */
|
|
106
|
+
this._displayText = state.facet(Config).displayText;
|
|
107
|
+
|
|
108
|
+
/* If class list has changed, clear and recalculate the font info map */
|
|
109
|
+
if (themeChanged) {
|
|
110
|
+
this._fontInfoMap.clear();
|
|
111
|
+
this._charWidthCache = undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Incrementally parse the tree based on previous tree + changes */
|
|
115
|
+
let treeFragments: ReadonlyArray<TreeFragment> | undefined = undefined;
|
|
116
|
+
if (this._previousTree && changes) {
|
|
117
|
+
const previousFragments = TreeFragment.addTree(this._previousTree);
|
|
118
|
+
|
|
119
|
+
const changedRanges: Array<ChangedRange> = [];
|
|
120
|
+
changes.iterChangedRanges((fromA, toA, fromB, toB) =>
|
|
121
|
+
changedRanges.push({ fromA, toA, fromB, toB }),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
treeFragments = TreeFragment.applyChanges(
|
|
125
|
+
previousFragments,
|
|
126
|
+
changedRanges,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Parse the document into a lezer tree */
|
|
131
|
+
const docToString = state.doc.toString();
|
|
132
|
+
const parser = state.facet(language)?.parser;
|
|
133
|
+
const tree = parser
|
|
134
|
+
? parser.parse(docToString, treeFragments)
|
|
135
|
+
: undefined;
|
|
136
|
+
this._previousTree = tree;
|
|
137
|
+
|
|
138
|
+
/* Highlight the document, and store the text and tags for each line */
|
|
139
|
+
const highlighter: Highlighter = {
|
|
140
|
+
style: (tags) => highlightingFor(state, tags),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let highlights: Array<{ from: number; to: number; tags: string }> = [];
|
|
144
|
+
|
|
145
|
+
if (tree) {
|
|
146
|
+
/**
|
|
147
|
+
* The viewport renders a few extra lines above and below the editor view. To approximate
|
|
148
|
+
* the lines visible in the minimap, we multiply the lines in the viewport by the scale multipliers.
|
|
149
|
+
*
|
|
150
|
+
* Based on the current scroll position, the minimap may show a larger portion of lines above or
|
|
151
|
+
* below the lines currently in the editor view. On a long document, when the scroll position is
|
|
152
|
+
* near the top of the document, the minimap will show a small number of lines above the lines
|
|
153
|
+
* in the editor view, and a large number of lines below the lines in the editor view.
|
|
154
|
+
*
|
|
155
|
+
* To approximate this ratio, we can use the viewport scroll percentage
|
|
156
|
+
*
|
|
157
|
+
* ┌─────────────────────┐
|
|
158
|
+
* │ │
|
|
159
|
+
* │ Extra viewport │
|
|
160
|
+
* │ buffer │
|
|
161
|
+
* ├─────────────────────┼───────┐
|
|
162
|
+
* │ │Minimap│
|
|
163
|
+
* │ │Gutter │
|
|
164
|
+
* │ ├───────┤
|
|
165
|
+
* │ Editor View │Scaled │
|
|
166
|
+
* │ │View │
|
|
167
|
+
* │ │Overlay│
|
|
168
|
+
* │ ├───────┤
|
|
169
|
+
* │ │ │
|
|
170
|
+
* │ │ │
|
|
171
|
+
* ├─────────────────────┼───────┘
|
|
172
|
+
* │ │
|
|
173
|
+
* │ Extra viewport │
|
|
174
|
+
* │ buffer │
|
|
175
|
+
* └─────────────────────┘
|
|
176
|
+
*
|
|
177
|
+
**/
|
|
178
|
+
|
|
179
|
+
const vpLineTop = state.doc.lineAt(this.view.viewport.from).number;
|
|
180
|
+
const vpLineBottom = state.doc.lineAt(this.view.viewport.to).number;
|
|
181
|
+
const vpLineCount = vpLineBottom - vpLineTop;
|
|
182
|
+
const vpScroll = vpLineTop / (state.doc.lines - vpLineCount);
|
|
183
|
+
|
|
184
|
+
const { SizeRatio, PixelMultiplier } = Scale;
|
|
185
|
+
const mmLineCount = vpLineCount * SizeRatio * PixelMultiplier;
|
|
186
|
+
const mmLineRatio = vpScroll * mmLineCount;
|
|
187
|
+
|
|
188
|
+
const mmLineTop = Math.max(1, Math.floor(vpLineTop - mmLineRatio));
|
|
189
|
+
const mmLineBottom = Math.min(
|
|
190
|
+
vpLineBottom + Math.floor(mmLineCount - mmLineRatio),
|
|
191
|
+
state.doc.lines,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Highlight the in-view lines synchronously
|
|
195
|
+
highlightTree(
|
|
196
|
+
tree,
|
|
197
|
+
highlighter,
|
|
198
|
+
(from, to, tags) => {
|
|
199
|
+
highlights.push({ from, to, tags });
|
|
200
|
+
},
|
|
201
|
+
state.doc.line(mmLineTop).from,
|
|
202
|
+
state.doc.line(mmLineBottom).to,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Update the map
|
|
207
|
+
this.updateMapImpl(state, highlights, docToString);
|
|
208
|
+
|
|
209
|
+
// Highlight the entire tree in an idle callback
|
|
210
|
+
highlights = [];
|
|
211
|
+
const highlightingCallback = () => {
|
|
212
|
+
if (tree) {
|
|
213
|
+
highlightTree(tree, highlighter, (from, to, tags) => {
|
|
214
|
+
highlights.push({ from, to, tags });
|
|
215
|
+
});
|
|
216
|
+
this.updateMapImpl(state, highlights, docToString);
|
|
217
|
+
this._highlightingCallbackId = undefined;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
this._highlightingCallbackId =
|
|
221
|
+
typeof window.requestIdleCallback !== "undefined"
|
|
222
|
+
? requestIdleCallback(highlightingCallback)
|
|
223
|
+
: setTimeout(highlightingCallback);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private updateMapImpl(
|
|
227
|
+
state: EditorState,
|
|
228
|
+
highlights: Array<{ from: number; to: number; tags: string }>,
|
|
229
|
+
docToString: string,
|
|
230
|
+
) {
|
|
231
|
+
this.map.clear();
|
|
232
|
+
|
|
233
|
+
const highlightsIterator = highlights.values();
|
|
234
|
+
let highlightPtr = highlightsIterator.next();
|
|
235
|
+
|
|
236
|
+
for (const [index, line] of state.field(LinesState).entries()) {
|
|
237
|
+
const spans: Array<TagSpan> = [];
|
|
238
|
+
|
|
239
|
+
for (const span of line) {
|
|
240
|
+
// Skip if it's a 0-length span
|
|
241
|
+
if (span.from === span.to) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Append a placeholder for a folded span
|
|
246
|
+
if (span.folded) {
|
|
247
|
+
spans.push({ text: "…", tags: "" });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let position = span.from;
|
|
252
|
+
while (
|
|
253
|
+
!highlightPtr.done &&
|
|
254
|
+
highlightPtr.value.from < span.to
|
|
255
|
+
) {
|
|
256
|
+
const { from, to, tags } = highlightPtr.value;
|
|
257
|
+
|
|
258
|
+
// Iterate until our highlight is over the current span
|
|
259
|
+
if (to < position) {
|
|
260
|
+
highlightPtr = highlightsIterator.next();
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Append unstyled text before the highlight begins
|
|
265
|
+
if (from > position) {
|
|
266
|
+
spans.push({
|
|
267
|
+
text: docToString.slice(position, from),
|
|
268
|
+
tags: "",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// A highlight may start before and extend beyond the current span
|
|
273
|
+
const start = Math.max(from, span.from);
|
|
274
|
+
const end = Math.min(to, span.to);
|
|
275
|
+
|
|
276
|
+
// Append the highlighted text
|
|
277
|
+
spans.push({ text: docToString.slice(start, end), tags });
|
|
278
|
+
position = end;
|
|
279
|
+
|
|
280
|
+
// If the highlight continues beyond this span, break from this loop
|
|
281
|
+
if (to > end) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Otherwise, move to the next highlight
|
|
286
|
+
highlightPtr = highlightsIterator.next();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// If there are remaining spans that did not get highlighted, append them unstyled
|
|
290
|
+
if (position !== span.to) {
|
|
291
|
+
spans.push({
|
|
292
|
+
text: docToString.slice(position, span.to),
|
|
293
|
+
tags: "",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Lines are indexed beginning at 1 instead of 0
|
|
299
|
+
const lineNumber = index + 1;
|
|
300
|
+
this.map.set(lineNumber, spans);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public measure(context: CanvasRenderingContext2D): {
|
|
305
|
+
charWidth: number;
|
|
306
|
+
lineHeight: number;
|
|
307
|
+
} {
|
|
308
|
+
const { color, font, lineHeight } = this.getFontInfo("");
|
|
309
|
+
|
|
310
|
+
// The canvas resets its state whenever it is resized, so these are
|
|
311
|
+
// reapplied on every measure even when the metrics themselves are cached.
|
|
312
|
+
context.textBaseline = "ideographic";
|
|
313
|
+
context.fillStyle = color;
|
|
314
|
+
context.font = font;
|
|
315
|
+
|
|
316
|
+
if (this._charWidthCache?.font !== font) {
|
|
317
|
+
this._charWidthCache = {
|
|
318
|
+
font,
|
|
319
|
+
charWidth: context.measureText("_").width,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { charWidth: this._charWidthCache.charWidth, lineHeight };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public drawLine(ctx: DrawContext, lineNumber: number) {
|
|
327
|
+
const line = this.get(lineNumber);
|
|
328
|
+
if (!line) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let { context, charWidth, lineHeight, offsetX, offsetY } = ctx;
|
|
333
|
+
|
|
334
|
+
let prevInfo: FontInfo | undefined;
|
|
335
|
+
context.textBaseline = "ideographic";
|
|
336
|
+
|
|
337
|
+
for (const span of line) {
|
|
338
|
+
const info = this.getFontInfo(span.tags);
|
|
339
|
+
|
|
340
|
+
if (!prevInfo || prevInfo.color !== info.color) {
|
|
341
|
+
context.fillStyle = info.color;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!prevInfo || prevInfo.font !== info.font) {
|
|
345
|
+
context.font = info.font;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
prevInfo = info;
|
|
349
|
+
|
|
350
|
+
lineHeight = Math.max(lineHeight, info.lineHeight);
|
|
351
|
+
|
|
352
|
+
switch (this._displayText) {
|
|
353
|
+
case "characters": {
|
|
354
|
+
// TODO: `fillText` takes up the majority of profiling time in `render`
|
|
355
|
+
// Try speeding it up with `drawImage`
|
|
356
|
+
// https://stackoverflow.com/questions/8237030/html5-canvas-faster-filltext-vs-drawimage/8237081
|
|
357
|
+
|
|
358
|
+
context.fillText(span.text, offsetX, offsetY + lineHeight);
|
|
359
|
+
offsetX += span.text.length * charWidth;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
case "blocks": {
|
|
364
|
+
// Shared regex, so reset the cursor that a previous span
|
|
365
|
+
// may have left behind after breaking out of the loop.
|
|
366
|
+
NON_WHITESPACE.lastIndex = 0;
|
|
367
|
+
let start: RegExpExecArray | null;
|
|
368
|
+
while ((start = NON_WHITESPACE.exec(span.text)) !== null) {
|
|
369
|
+
const startX = offsetX + start.index * charWidth;
|
|
370
|
+
let width =
|
|
371
|
+
(NON_WHITESPACE.lastIndex - start.index) *
|
|
372
|
+
charWidth;
|
|
373
|
+
|
|
374
|
+
// Reached the edge of the minimap
|
|
375
|
+
if (startX > context.canvas.width) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Limit width to edge of minimap
|
|
380
|
+
if (startX + width > context.canvas.width) {
|
|
381
|
+
width = context.canvas.width - startX;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Scaled 2px buffer between lines
|
|
385
|
+
const yBuffer = 2 / Scale.SizeRatio;
|
|
386
|
+
const height = lineHeight - yBuffer;
|
|
387
|
+
|
|
388
|
+
context.fillStyle = info.color;
|
|
389
|
+
context.globalAlpha = 0.65; // Make the blocks a bit faded
|
|
390
|
+
context.beginPath();
|
|
391
|
+
context.rect(startX, offsetY, width, height);
|
|
392
|
+
context.fill();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Undo the fade so it cannot bleed into later layers.
|
|
396
|
+
context.globalAlpha = 1;
|
|
397
|
+
|
|
398
|
+
offsetX += span.text.length * charWidth;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private getFontInfo(tags: string): FontInfo {
|
|
406
|
+
const cached = this._fontInfoMap.get(tags);
|
|
407
|
+
if (cached) {
|
|
408
|
+
return cached;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Create a mock token wrapped in a cm-line
|
|
412
|
+
const mockToken = crelt("span", { class: tags });
|
|
413
|
+
const mockLine = crelt(
|
|
414
|
+
"div",
|
|
415
|
+
{ class: "cm-line", style: "display: none" },
|
|
416
|
+
mockToken,
|
|
417
|
+
);
|
|
418
|
+
this.view.contentDOM.appendChild(mockLine);
|
|
419
|
+
|
|
420
|
+
// Get style information and store it
|
|
421
|
+
const style = window.getComputedStyle(mockToken);
|
|
422
|
+
const lineHeight = parseFloat(style.lineHeight) / Scale.SizeRatio;
|
|
423
|
+
const result = {
|
|
424
|
+
color: style.color,
|
|
425
|
+
font: `${style.fontStyle} ${style.fontWeight} ${lineHeight}px ${style.fontFamily}`,
|
|
426
|
+
lineHeight,
|
|
427
|
+
};
|
|
428
|
+
this._fontInfoMap.set(tags, result);
|
|
429
|
+
|
|
430
|
+
// Clean up and return
|
|
431
|
+
this.view.contentDOM.removeChild(mockLine);
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private themeChanged(): boolean {
|
|
436
|
+
const previous = this._themeClasses;
|
|
437
|
+
const now = new Set(this.view.dom.classList.values());
|
|
438
|
+
this._themeClasses = now;
|
|
439
|
+
|
|
440
|
+
if (!previous) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Ignore certain classes being added/removed
|
|
445
|
+
previous.delete("cm-focused");
|
|
446
|
+
now.delete("cm-focused");
|
|
447
|
+
|
|
448
|
+
if (previous.size !== now.size) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let containsAll = true;
|
|
453
|
+
previous.forEach((theme) => {
|
|
454
|
+
if (!now.has(theme)) {
|
|
455
|
+
containsAll = false;
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return !containsAll;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function text(view: EditorView): TextState {
|
|
464
|
+
return new TextState(view);
|
|
465
|
+
}
|