codemirror-foldable-indentation-guides 1.0.2 → 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/README.md +4 -4
- package/dev/index.ts +7 -6
- package/dist/index.cjs +210 -147
- package/dist/index.js +210 -147
- package/package.json +1 -1
- package/public/example.png +0 -0
- package/src/build-indentation-decorations.ts +14 -10
- package/src/indentation-view-plugin.ts +187 -124
- package/src/map.ts +44 -19
|
@@ -12,11 +12,15 @@ import {
|
|
|
12
12
|
getAllLines,
|
|
13
13
|
getCurrentLine,
|
|
14
14
|
getNearestFoldRange,
|
|
15
|
+
getVisibleLines,
|
|
15
16
|
toggleFoldRange,
|
|
16
17
|
} from './utils';
|
|
17
18
|
import { buildIndentationDecorations } from './build-indentation-decorations';
|
|
18
19
|
import { getNewIndentationMap } from './get-new-indentation-map';
|
|
19
20
|
import { handleGuideHover } from './handle-guide-hover';
|
|
21
|
+
import { Line } from '@codemirror/state';
|
|
22
|
+
|
|
23
|
+
const DEBOUNCE_DELAY_MS = 500;
|
|
20
24
|
|
|
21
25
|
export class IndentationViewPlugin implements PluginValue {
|
|
22
26
|
private unitWidth: number;
|
|
@@ -26,10 +30,6 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
26
30
|
private viewContentWidth: number;
|
|
27
31
|
decorations: DecorationSet;
|
|
28
32
|
|
|
29
|
-
/** Edges of current active block in a viewport */
|
|
30
|
-
private activeColEdges: number[];
|
|
31
|
-
private activeCol: number;
|
|
32
|
-
|
|
33
33
|
/** map of lineNumber -> indent level (integer) */
|
|
34
34
|
lineLevels: Map<number, number>;
|
|
35
35
|
|
|
@@ -44,25 +44,49 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
44
44
|
end: number;
|
|
45
45
|
} | null = null;
|
|
46
46
|
|
|
47
|
+
lastCursorPos: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
} | null = null;
|
|
51
|
+
|
|
52
|
+
private allStateDebounceId: ReturnType<typeof setTimeout> | null = null;
|
|
53
|
+
|
|
47
54
|
constructor(view: EditorView) {
|
|
48
|
-
const
|
|
55
|
+
const visibleLines = getVisibleLines(view);
|
|
56
|
+
this.indentationMap = this.updateIndentationMap(view, visibleLines);
|
|
57
|
+
const built = buildIndentationDecorations(
|
|
58
|
+
view,
|
|
59
|
+
this.indentationMap,
|
|
60
|
+
visibleLines,
|
|
61
|
+
);
|
|
49
62
|
this.highlightHoveredGuide = false;
|
|
50
63
|
this.highlightHoveredBlock = false;
|
|
51
|
-
this.activeColEdges = [];
|
|
52
|
-
this.activeCol = 0;
|
|
53
64
|
this.view = view;
|
|
54
|
-
this.viewContentWidth =
|
|
55
|
-
|
|
56
|
-
this.contentElement = view.dom.querySelector('.cm-content');
|
|
65
|
+
this.viewContentWidth = view.contentDOM?.clientWidth ?? 0;
|
|
66
|
+
this.contentElement = view.contentDOM;
|
|
57
67
|
this.decorations = built.decoSet;
|
|
58
68
|
this.lineLevels = built.lineLevels;
|
|
59
|
-
this.indentationMap = built.indentationMap;
|
|
60
69
|
this.unitWidth = getIndentUnit(view.state);
|
|
61
70
|
this.currentLineNumber = getCurrentLine(view.state).number;
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
private updateIndentationMap(view: EditorView, visibleLines: Set<Line>) {
|
|
74
|
+
if (this.allStateDebounceId) {
|
|
75
|
+
clearTimeout(this.allStateDebounceId);
|
|
76
|
+
}
|
|
77
|
+
this.indentationMap?.terminate();
|
|
78
|
+
this.indentationMap = getNewIndentationMap(view.state, visibleLines);
|
|
79
|
+
|
|
80
|
+
this.allStateDebounceId = setTimeout(() => {
|
|
81
|
+
const allLines = getAllLines(view);
|
|
82
|
+
this.indentationMap.initLinesAsync(allLines);
|
|
83
|
+
}, DEBOUNCE_DELAY_MS);
|
|
84
|
+
|
|
85
|
+
return this.indentationMap;
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
update(update: ViewUpdate) {
|
|
65
|
-
this.contentElement ??= update.view.
|
|
89
|
+
this.contentElement ??= update.view.contentDOM;
|
|
66
90
|
const unitWidth = getIndentUnit(update.state);
|
|
67
91
|
const unitWidthChanged = unitWidth !== this.unitWidth;
|
|
68
92
|
if (unitWidthChanged) {
|
|
@@ -91,12 +115,34 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
91
115
|
|
|
92
116
|
const activeBlockUpdateRequired = isHighlightActive && viewChanged;
|
|
93
117
|
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
const visibleLines =
|
|
119
|
+
update.docChanged ||
|
|
120
|
+
update.viewportChanged ||
|
|
121
|
+
unitWidthChanged ||
|
|
122
|
+
activeBlockUpdateRequired
|
|
123
|
+
? getVisibleLines(update.view)
|
|
124
|
+
: null;
|
|
125
|
+
if (!visibleLines) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (update.docChanged || unitWidthChanged) {
|
|
130
|
+
this.updateIndentationMap(update.view, visibleLines);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (update.viewportChanged || unitWidthChanged) {
|
|
134
|
+
if (!update.docChanged) {
|
|
135
|
+
this.indentationMap.initLinesSync(visibleLines);
|
|
136
|
+
}
|
|
96
137
|
|
|
138
|
+
const built = buildIndentationDecorations(
|
|
139
|
+
update.view,
|
|
140
|
+
this.indentationMap,
|
|
141
|
+
visibleLines,
|
|
142
|
+
);
|
|
97
143
|
this.decorations = built.decoSet;
|
|
98
144
|
this.lineLevels = built.lineLevels;
|
|
99
|
-
|
|
145
|
+
|
|
100
146
|
this.clearHoverClasses();
|
|
101
147
|
}
|
|
102
148
|
|
|
@@ -105,7 +151,7 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
105
151
|
this.updateActiveColumns(
|
|
106
152
|
lineNumber,
|
|
107
153
|
highlightActiveMarker,
|
|
108
|
-
highlightActiveBlockBackground
|
|
154
|
+
highlightActiveBlockBackground,
|
|
109
155
|
);
|
|
110
156
|
}
|
|
111
157
|
}
|
|
@@ -113,56 +159,45 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
113
159
|
private updateActiveColumns(
|
|
114
160
|
lineNumber: number,
|
|
115
161
|
highlightActiveMarker: boolean,
|
|
116
|
-
highlightActiveBlockBackground: boolean
|
|
162
|
+
highlightActiveBlockBackground: boolean,
|
|
117
163
|
) {
|
|
118
164
|
requestAnimationFrame(() => {
|
|
119
165
|
this.clearActiveClasses();
|
|
120
166
|
|
|
121
|
-
const
|
|
122
|
-
.getActiveLinesNumbers(lineNumber)
|
|
123
|
-
?.sort((a, b) => a.line.number - b.line.number);
|
|
124
|
-
if (lines) {
|
|
125
|
-
lines.forEach(line => {
|
|
126
|
-
const { active: activeCol } = line;
|
|
127
|
-
const lineNumber = line.line.number;
|
|
128
|
-
|
|
129
|
-
if (highlightActiveMarker) {
|
|
130
|
-
this.applyActiveMarker(lineNumber, activeCol ?? 0);
|
|
131
|
-
}
|
|
132
|
-
if (highlightActiveBlockBackground) {
|
|
133
|
-
this.applyBackgroundHighlight(lineNumber, activeCol ?? 0, 'active');
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
this.activeColEdges = [
|
|
137
|
-
lines[0].line.number,
|
|
138
|
-
lines[lines.length - 1].line.number,
|
|
139
|
-
];
|
|
140
|
-
this.activeCol = lines[0].active ?? 0;
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
167
|
+
const activeLines =
|
|
168
|
+
this.indentationMap?.getActiveLinesNumbers(lineNumber);
|
|
143
169
|
|
|
144
|
-
if (!
|
|
170
|
+
if (!activeLines?.length || activeLines[0].active === undefined) {
|
|
145
171
|
return;
|
|
146
172
|
}
|
|
147
173
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
const minLineNumber =
|
|
175
|
+
activeLines.reduce(
|
|
176
|
+
(acc, line) => Math.min(acc, line.line.number),
|
|
177
|
+
Infinity,
|
|
178
|
+
) - 1;
|
|
179
|
+
const maxLineNumber =
|
|
180
|
+
activeLines.reduce(
|
|
181
|
+
(acc, line) => Math.max(acc, line.line.number),
|
|
182
|
+
-Infinity,
|
|
183
|
+
) + 1;
|
|
184
|
+
|
|
185
|
+
if (highlightActiveMarker) {
|
|
186
|
+
this.applyActiveMarker(
|
|
187
|
+
minLineNumber,
|
|
188
|
+
maxLineNumber,
|
|
189
|
+
activeLines[0].active,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
153
192
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
this.activeColEdges.push(range.start, range.end);
|
|
164
|
-
});
|
|
165
|
-
this.activeColEdges = Array.from(new Set(this.activeColEdges));
|
|
193
|
+
if (highlightActiveBlockBackground) {
|
|
194
|
+
this.applyBackgroundHighlight(
|
|
195
|
+
minLineNumber,
|
|
196
|
+
maxLineNumber,
|
|
197
|
+
activeLines[0].active,
|
|
198
|
+
'active',
|
|
199
|
+
);
|
|
200
|
+
}
|
|
166
201
|
});
|
|
167
202
|
}
|
|
168
203
|
|
|
@@ -195,36 +230,45 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
195
230
|
}
|
|
196
231
|
|
|
197
232
|
clearActiveClasses() {
|
|
198
|
-
const dom = this.view.
|
|
233
|
+
const dom = this.view.contentDOM;
|
|
199
234
|
|
|
200
235
|
dom
|
|
201
236
|
.querySelectorAll('.cm-indentation-guide-button_active')
|
|
202
|
-
.forEach(el =>
|
|
237
|
+
.forEach((el) =>
|
|
238
|
+
el.classList.remove('cm-indentation-guide-button_active'),
|
|
239
|
+
);
|
|
203
240
|
|
|
204
241
|
dom
|
|
205
242
|
.querySelectorAll<HTMLElement>(
|
|
206
|
-
'.cm-indentation-guide-background-highlight_active'
|
|
243
|
+
'.cm-indentation-guide-background-highlight_active',
|
|
207
244
|
)
|
|
208
|
-
.forEach(el => {
|
|
245
|
+
.forEach((el) => {
|
|
209
246
|
el.classList.remove('cm-indentation-guide-background-highlight_active');
|
|
210
247
|
});
|
|
211
248
|
}
|
|
212
249
|
|
|
213
|
-
applyActiveMarker(
|
|
214
|
-
const root = this.view.
|
|
215
|
-
const selector = `.cm-indentation-guide
|
|
250
|
+
private applyActiveMarker(start: number, end: number, col: number) {
|
|
251
|
+
const root = this.view.contentDOM;
|
|
252
|
+
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
216
253
|
|
|
217
|
-
root.querySelectorAll(selector).forEach(btn => {
|
|
218
|
-
btn.
|
|
254
|
+
root.querySelectorAll<HTMLButtonElement>(selector).forEach((btn) => {
|
|
255
|
+
const lineAttr = btn.parentElement?.dataset.line;
|
|
256
|
+
if (!lineAttr) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const ln = Number(lineAttr);
|
|
260
|
+
if (ln >= start && ln <= end) {
|
|
261
|
+
btn.classList.add('cm-indentation-guide-button_active');
|
|
262
|
+
}
|
|
219
263
|
});
|
|
220
264
|
}
|
|
221
265
|
|
|
222
266
|
/** highlight DOM buttons in column `col` for lines in [start, end] */
|
|
223
267
|
applyHoverClasses(start: number, end: number, col: number) {
|
|
224
268
|
this.clearHoverClasses();
|
|
225
|
-
const root = this.view.
|
|
269
|
+
const root = this.view.contentDOM;
|
|
226
270
|
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
227
|
-
root.querySelectorAll(selector).forEach(btn => {
|
|
271
|
+
root.querySelectorAll(selector).forEach((btn) => {
|
|
228
272
|
const lineAttr = btn.parentElement?.dataset.line;
|
|
229
273
|
if (!lineAttr) {
|
|
230
274
|
return;
|
|
@@ -235,7 +279,7 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
235
279
|
btn.classList.add('cm-indentation-guide-button_hover');
|
|
236
280
|
}
|
|
237
281
|
if (this.highlightHoveredBlock) {
|
|
238
|
-
this.applyBackgroundHighlight(
|
|
282
|
+
this.applyBackgroundHighlight(start, end, col, 'hover');
|
|
239
283
|
}
|
|
240
284
|
}
|
|
241
285
|
});
|
|
@@ -243,40 +287,46 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
243
287
|
}
|
|
244
288
|
|
|
245
289
|
applyBackgroundHighlight(
|
|
246
|
-
|
|
290
|
+
start: number,
|
|
291
|
+
end: number,
|
|
247
292
|
col: number,
|
|
248
|
-
type: 'active' | 'hover'
|
|
293
|
+
type: 'active' | 'hover',
|
|
249
294
|
) {
|
|
250
|
-
const root = this.view.
|
|
251
|
-
const selector = `.cm-indentation-guide
|
|
252
|
-
|
|
253
|
-
const button = root.querySelector<HTMLButtonElement>(selector);
|
|
254
|
-
if (!button) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
295
|
+
const root = this.view.contentDOM;
|
|
296
|
+
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
297
|
+
const highlightBackgroundSelector = `.cm-indentation-guide-background-highlight`;
|
|
257
298
|
|
|
258
|
-
|
|
259
|
-
|
|
299
|
+
root.querySelectorAll<HTMLButtonElement>(selector).forEach((button) => {
|
|
300
|
+
const lineAttr = button.parentElement?.dataset.line;
|
|
301
|
+
if (!lineAttr) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const ln = Number(lineAttr);
|
|
305
|
+
if (ln < start || ln > end) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
260
308
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
309
|
+
const backgroundDiv = button.parentElement?.querySelector<HTMLElement>(
|
|
310
|
+
highlightBackgroundSelector,
|
|
311
|
+
);
|
|
312
|
+
if (!backgroundDiv) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
268
315
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
316
|
+
let highlightLeft = button.offsetLeft;
|
|
317
|
+
let btnWidth = button.offsetWidth;
|
|
318
|
+
backgroundDiv.style.setProperty(
|
|
319
|
+
`--indentation-guide-background-highlight-${type}-left`,
|
|
320
|
+
`${highlightLeft}px`,
|
|
321
|
+
);
|
|
322
|
+
backgroundDiv.style.setProperty(
|
|
323
|
+
`--indentation-guide-background-highlight-${type}-width`,
|
|
324
|
+
`${this.viewContentWidth - (highlightLeft ?? 0) - (btnWidth ?? 0) / 2}px`,
|
|
325
|
+
);
|
|
326
|
+
backgroundDiv.classList.add(
|
|
327
|
+
`cm-indentation-guide-background-highlight_${type}`,
|
|
328
|
+
);
|
|
329
|
+
});
|
|
280
330
|
}
|
|
281
331
|
|
|
282
332
|
clearHoverClasses() {
|
|
@@ -284,18 +334,20 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
284
334
|
return;
|
|
285
335
|
}
|
|
286
336
|
|
|
287
|
-
const dom = this.view.
|
|
337
|
+
const dom = this.view.contentDOM;
|
|
288
338
|
|
|
289
339
|
dom
|
|
290
340
|
.querySelectorAll('.cm-indentation-guide-button_hover')
|
|
291
|
-
.forEach(el =>
|
|
341
|
+
.forEach((el) =>
|
|
342
|
+
el.classList.remove('cm-indentation-guide-button_hover'),
|
|
343
|
+
);
|
|
292
344
|
this.hoveredGuide = null;
|
|
293
345
|
|
|
294
346
|
dom
|
|
295
347
|
.querySelectorAll<HTMLElement>(
|
|
296
|
-
'.cm-indentation-guide-background-highlight'
|
|
348
|
+
'.cm-indentation-guide-background-highlight',
|
|
297
349
|
)
|
|
298
|
-
.forEach(el => {
|
|
350
|
+
.forEach((el) => {
|
|
299
351
|
el.classList.remove('cm-indentation-guide-background-highlight_hover');
|
|
300
352
|
});
|
|
301
353
|
}
|
|
@@ -304,32 +356,47 @@ export class IndentationViewPlugin implements PluginValue {
|
|
|
304
356
|
export const indentationGuidesPlugin = ViewPlugin.fromClass(
|
|
305
357
|
IndentationViewPlugin,
|
|
306
358
|
{
|
|
307
|
-
decorations: v => v.decorations,
|
|
359
|
+
decorations: (v) => v.decorations,
|
|
308
360
|
|
|
309
361
|
eventHandlers: {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
view.state.facet(indentationGuidesConfig);
|
|
313
|
-
if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
|
|
362
|
+
scroll() {
|
|
363
|
+
if (!this.lastCursorPos) {
|
|
314
364
|
return;
|
|
315
365
|
}
|
|
316
366
|
|
|
317
|
-
const
|
|
318
|
-
|
|
367
|
+
const target = document
|
|
368
|
+
.elementFromPoint(this.lastCursorPos.x, this.lastCursorPos.y)
|
|
369
|
+
?.closest('.cm-indentation-guide-button') as HTMLElement | null;
|
|
370
|
+
|
|
371
|
+
if (target) {
|
|
372
|
+
handleGuideHover(target, this);
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
mousemove(e, view) {
|
|
378
|
+
const { highlightHoveredMarker, highlightHoveredBlockBackground } =
|
|
379
|
+
view.state.facet(indentationGuidesConfig);
|
|
380
|
+
if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
|
|
319
381
|
return;
|
|
320
382
|
}
|
|
321
383
|
|
|
322
384
|
const target = (e.target as HTMLElement).closest(
|
|
323
|
-
'.cm-indentation-guide-button'
|
|
385
|
+
'.cm-indentation-guide-button',
|
|
324
386
|
) as HTMLElement | null;
|
|
325
387
|
if (target) {
|
|
326
|
-
handleGuideHover(target,
|
|
388
|
+
handleGuideHover(target, this);
|
|
389
|
+
this.lastCursorPos = {
|
|
390
|
+
x: e.clientX,
|
|
391
|
+
y: e.clientY,
|
|
392
|
+
};
|
|
327
393
|
} else {
|
|
328
|
-
|
|
394
|
+
this.clearHoverClasses();
|
|
395
|
+
this.lastCursorPos = null;
|
|
329
396
|
}
|
|
330
397
|
},
|
|
331
398
|
|
|
332
|
-
mouseout
|
|
399
|
+
mouseout(e, view) {
|
|
333
400
|
const { highlightHoveredMarker, highlightHoveredBlockBackground } =
|
|
334
401
|
view.state.facet(indentationGuidesConfig);
|
|
335
402
|
if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
|
|
@@ -343,19 +410,18 @@ export const indentationGuidesPlugin = ViewPlugin.fromClass(
|
|
|
343
410
|
!related.closest ||
|
|
344
411
|
!related.closest('.cm-indentation-guide')
|
|
345
412
|
) {
|
|
346
|
-
|
|
347
|
-
plugin?.clearHoverClasses();
|
|
413
|
+
this.clearHoverClasses();
|
|
348
414
|
}
|
|
349
415
|
},
|
|
350
416
|
|
|
351
|
-
mousedown
|
|
417
|
+
mousedown(e, view) {
|
|
352
418
|
const { foldBlockOnClick } = view.state.facet(indentationGuidesConfig);
|
|
353
419
|
if (!foldBlockOnClick) {
|
|
354
420
|
return;
|
|
355
421
|
}
|
|
356
422
|
|
|
357
423
|
const target = (e.target as HTMLElement).closest(
|
|
358
|
-
'.cm-indentation-guide-button'
|
|
424
|
+
'.cm-indentation-guide-button',
|
|
359
425
|
) as HTMLElement | null;
|
|
360
426
|
if (!target) {
|
|
361
427
|
return false;
|
|
@@ -368,10 +434,7 @@ export const indentationGuidesPlugin = ViewPlugin.fromClass(
|
|
|
368
434
|
if (!lineAttr || !colAttr) {
|
|
369
435
|
return false;
|
|
370
436
|
}
|
|
371
|
-
|
|
372
|
-
if (!plugin) {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
437
|
+
|
|
375
438
|
const lineNumber = Number(lineAttr);
|
|
376
439
|
const col = Number(colAttr);
|
|
377
440
|
|
|
@@ -383,9 +446,9 @@ export const indentationGuidesPlugin = ViewPlugin.fromClass(
|
|
|
383
446
|
const level = entry?.level ?? 0;
|
|
384
447
|
lineLevels.set(line.number, level);
|
|
385
448
|
}
|
|
386
|
-
|
|
449
|
+
this.lineLevels = lineLevels;
|
|
387
450
|
|
|
388
|
-
const { start } =
|
|
451
|
+
const { start } = this.computeBlockRange(lineNumber, col);
|
|
389
452
|
const range = getNearestFoldRange(view, start - 1);
|
|
390
453
|
if (!range) {
|
|
391
454
|
return;
|
|
@@ -395,5 +458,5 @@ export const indentationGuidesPlugin = ViewPlugin.fromClass(
|
|
|
395
458
|
return true;
|
|
396
459
|
},
|
|
397
460
|
},
|
|
398
|
-
}
|
|
461
|
+
},
|
|
399
462
|
);
|
package/src/map.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EditorState, Line } from '@codemirror/state';
|
|
2
2
|
import { numColumns } from './utils';
|
|
3
3
|
|
|
4
4
|
export interface IndentEntry {
|
|
@@ -22,9 +22,6 @@ export class IndentationMap {
|
|
|
22
22
|
/** The {@link EditorState} indentation is derived from. */
|
|
23
23
|
private state: EditorState;
|
|
24
24
|
|
|
25
|
-
/** The set of lines that are used as an entrypoint. */
|
|
26
|
-
private lines: Set<Line>;
|
|
27
|
-
|
|
28
25
|
/** The internal mapping of line numbers to {@link IndentEntry} objects. */
|
|
29
26
|
private map: Map<number, IndentEntry>;
|
|
30
27
|
|
|
@@ -34,6 +31,8 @@ export class IndentationMap {
|
|
|
34
31
|
/** The type of indentation to use (terminate at end of scope vs last non-empty line in scope) */
|
|
35
32
|
private markerType: 'fullScope' | 'codeOnly';
|
|
36
33
|
|
|
34
|
+
private terminated = false;
|
|
35
|
+
|
|
37
36
|
/**
|
|
38
37
|
* @param lines - The set of lines to get the indentation map for.
|
|
39
38
|
* @param state - The {@link EditorState} to derive the indentation map from.
|
|
@@ -44,19 +43,45 @@ export class IndentationMap {
|
|
|
44
43
|
lines: Set<Line>,
|
|
45
44
|
state: EditorState,
|
|
46
45
|
unitWidth: number,
|
|
47
|
-
markerType: 'fullScope' | 'codeOnly'
|
|
46
|
+
markerType: 'fullScope' | 'codeOnly',
|
|
48
47
|
) {
|
|
49
|
-
this.lines = lines;
|
|
50
48
|
this.state = state;
|
|
51
49
|
this.map = new Map();
|
|
52
50
|
this.unitWidth = unitWidth;
|
|
53
51
|
this.markerType = markerType;
|
|
54
52
|
|
|
55
|
-
for (const line of
|
|
53
|
+
for (const line of lines) {
|
|
56
54
|
this.add(line);
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
|
|
58
|
+
initLinesSync(lines: Set<Line>) {
|
|
59
|
+
const linesArr = [...lines];
|
|
60
|
+
for (let i = 0; i < linesArr.length; i++) {
|
|
61
|
+
const line = linesArr[i];
|
|
62
|
+
this.add(line);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async initLinesAsync(lines: Set<Line>) {
|
|
67
|
+
const linesArr = [...lines];
|
|
68
|
+
for (let i = 0; i < linesArr.length; i++) {
|
|
69
|
+
if (this.terminated) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const line = linesArr[i];
|
|
74
|
+
this.add(line);
|
|
75
|
+
if (i % 100 === 0) {
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
terminate() {
|
|
82
|
+
this.terminated = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
/**
|
|
61
86
|
* Checks if the indentation map has an entry for the given line.
|
|
62
87
|
*
|
|
@@ -76,10 +101,6 @@ export class IndentationMap {
|
|
|
76
101
|
get(line: Line | number) {
|
|
77
102
|
const entry = this.map.get(typeof line === 'number' ? line : line.number);
|
|
78
103
|
|
|
79
|
-
if (!entry) {
|
|
80
|
-
throw new Error('Line not found in indentation map');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
104
|
return entry;
|
|
84
105
|
}
|
|
85
106
|
|
|
@@ -119,11 +140,14 @@ export class IndentationMap {
|
|
|
119
140
|
if (line.number === this.state.doc.lines) {
|
|
120
141
|
const prev = this.closestNonEmpty(line, -1);
|
|
121
142
|
|
|
122
|
-
return this.set(line, 0, prev
|
|
143
|
+
return this.set(line, 0, prev?.level ?? 0);
|
|
123
144
|
}
|
|
124
145
|
|
|
125
146
|
const prev = this.closestNonEmpty(line, -1);
|
|
126
147
|
const next = this.closestNonEmpty(line, 1);
|
|
148
|
+
if (!prev || !next) {
|
|
149
|
+
return this.set(line, 0, 0);
|
|
150
|
+
}
|
|
127
151
|
|
|
128
152
|
// if the next line ends the block and the marker type is not set to codeOnly,
|
|
129
153
|
// we'll just use the previous line's indentation
|
|
@@ -165,7 +189,7 @@ export class IndentationMap {
|
|
|
165
189
|
while (dir === -1 ? lineNo >= 1 : lineNo <= this.state.doc.lines) {
|
|
166
190
|
if (this.has(lineNo)) {
|
|
167
191
|
const entry = this.get(lineNo);
|
|
168
|
-
if (!entry
|
|
192
|
+
if (!entry?.empty) {
|
|
169
193
|
return entry;
|
|
170
194
|
}
|
|
171
195
|
}
|
|
@@ -205,11 +229,14 @@ export class IndentationMap {
|
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
let current = this.get(currentLine);
|
|
232
|
+
if (!current) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
208
235
|
|
|
209
236
|
// check if the current line is starting a new block, if yes, we want to
|
|
210
237
|
// start from inside the block.
|
|
211
238
|
if (this.has(current.line.number + 1)) {
|
|
212
|
-
const next = this.get(current.line.number + 1)
|
|
239
|
+
const next = this.get(current.line.number + 1)!;
|
|
213
240
|
if (next.level > current.level) {
|
|
214
241
|
current = next;
|
|
215
242
|
}
|
|
@@ -217,7 +244,7 @@ export class IndentationMap {
|
|
|
217
244
|
|
|
218
245
|
// same, but if the current line is ending a block
|
|
219
246
|
if (this.has(current.line.number - 1)) {
|
|
220
|
-
const prev = this.get(current.line.number - 1)
|
|
247
|
+
const prev = this.get(current.line.number - 1)!;
|
|
221
248
|
if (prev.level > current.level) {
|
|
222
249
|
current = prev;
|
|
223
250
|
}
|
|
@@ -240,8 +267,7 @@ export class IndentationMap {
|
|
|
240
267
|
}
|
|
241
268
|
|
|
242
269
|
const prev = this.get(start - 1);
|
|
243
|
-
|
|
244
|
-
if (prev.level < current.level) {
|
|
270
|
+
if (!prev || prev.level < current.level) {
|
|
245
271
|
break;
|
|
246
272
|
}
|
|
247
273
|
|
|
@@ -256,8 +282,7 @@ export class IndentationMap {
|
|
|
256
282
|
}
|
|
257
283
|
|
|
258
284
|
const next = this.get(end + 1);
|
|
259
|
-
|
|
260
|
-
if (next.level < current.level) {
|
|
285
|
+
if (!next || next.level < current.level) {
|
|
261
286
|
break;
|
|
262
287
|
}
|
|
263
288
|
|