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.
@@ -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 built = buildIndentationDecorations(view);
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
- view.dom.querySelector('.cm-content')?.clientWidth ?? 0;
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.dom.querySelector('.cm-content');
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
- if (update.docChanged || update.viewportChanged || unitWidthChanged) {
95
- const built = buildIndentationDecorations(update.view);
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
- this.indentationMap = built.indentationMap;
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 lines = this.indentationMap
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 (!this.activeColEdges || !this.activeCol) {
170
+ if (!activeLines?.length || activeLines[0].active === undefined) {
145
171
  return;
146
172
  }
147
173
 
148
- /** If current line is outside computed indentation map lines,
149
- * compute active block lines from active column points */
150
- const ranges = this.activeColEdges.map(edge =>
151
- this.computeBlockRange(edge, this.activeCol)
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
- ranges.forEach(range => {
155
- for (let i = range.start; i <= range.end; i++) {
156
- if (highlightActiveMarker) {
157
- this.applyActiveMarker(lineNumber, this.activeCol);
158
- }
159
- if (highlightActiveBlockBackground) {
160
- this.applyBackgroundHighlight(lineNumber, this.activeCol, 'active');
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.dom;
233
+ const dom = this.view.contentDOM;
199
234
 
200
235
  dom
201
236
  .querySelectorAll('.cm-indentation-guide-button_active')
202
- .forEach(el => el.classList.remove('cm-indentation-guide-button_active'));
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(lineNumber: number, col: number) {
214
- const root = this.view.dom;
215
- const selector = `.cm-indentation-guide[data-line="${lineNumber}"] > .cm-indentation-guide-button[data-col="${col}"]`;
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.classList.add('cm-indentation-guide-button_active');
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.dom;
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(ln, col, 'hover');
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
- lineNumber: number,
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.dom;
251
- const selector = `.cm-indentation-guide[data-line="${lineNumber}"] > .cm-indentation-guide-button[data-col="${col}"]`;
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
- let highlightLeft = button.offsetLeft;
259
- let btnWidth = button.offsetWidth;
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
- const highlightBackgroundSelector = `.cm-indentation-guide[data-line="${lineNumber}"] > .cm-indentation-guide-background-highlight`;
262
- const backgroundDiv = root.querySelector<HTMLElement>(
263
- highlightBackgroundSelector
264
- );
265
- if (!backgroundDiv) {
266
- return;
267
- }
309
+ const backgroundDiv = button.parentElement?.querySelector<HTMLElement>(
310
+ highlightBackgroundSelector,
311
+ );
312
+ if (!backgroundDiv) {
313
+ return;
314
+ }
268
315
 
269
- backgroundDiv.style.setProperty(
270
- `--indentation-guide-background-highlight-${type}-left`,
271
- `${highlightLeft}px`
272
- );
273
- backgroundDiv.style.setProperty(
274
- `--indentation-guide-background-highlight-${type}-width`,
275
- `${this.viewContentWidth - (highlightLeft ?? 0) - (btnWidth ?? 0) / 2}px`
276
- );
277
- backgroundDiv.classList.add(
278
- `cm-indentation-guide-background-highlight_${type}`
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.dom;
337
+ const dom = this.view.contentDOM;
288
338
 
289
339
  dom
290
340
  .querySelectorAll('.cm-indentation-guide-button_hover')
291
- .forEach(el => el.classList.remove('cm-indentation-guide-button_hover'));
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
- mousemove: (e, view) => {
311
- const { highlightHoveredMarker, highlightHoveredBlockBackground } =
312
- view.state.facet(indentationGuidesConfig);
313
- if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
362
+ scroll() {
363
+ if (!this.lastCursorPos) {
314
364
  return;
315
365
  }
316
366
 
317
- const plugin = view.plugin(indentationGuidesPlugin);
318
- if (!plugin) {
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, plugin);
388
+ handleGuideHover(target, this);
389
+ this.lastCursorPos = {
390
+ x: e.clientX,
391
+ y: e.clientY,
392
+ };
327
393
  } else {
328
- plugin.clearHoverClasses();
394
+ this.clearHoverClasses();
395
+ this.lastCursorPos = null;
329
396
  }
330
397
  },
331
398
 
332
- mouseout: (e, view) => {
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
- const plugin = view.plugin(indentationGuidesPlugin);
347
- plugin?.clearHoverClasses();
413
+ this.clearHoverClasses();
348
414
  }
349
415
  },
350
416
 
351
- mousedown: (e, view) => {
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
- const plugin = view.plugin(indentationGuidesPlugin);
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
- plugin.lineLevels = lineLevels;
449
+ this.lineLevels = lineLevels;
387
450
 
388
- const { start } = plugin.computeBlockRange(lineNumber, col);
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 { Line, EditorState } from '@codemirror/state';
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 this.lines) {
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.level);
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.empty) {
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