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
package/dist/index.js
CHANGED
|
@@ -350,6 +350,57 @@ class ClickableIndentationGuideWidget extends WidgetType {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
function buildIndentationDecorations(view, indentationMap, visibleLines) {
|
|
354
|
+
var _a;
|
|
355
|
+
const state = view.state;
|
|
356
|
+
const widgets = [];
|
|
357
|
+
const indentWidth = getIndentUnit(state);
|
|
358
|
+
const { thickness, activeThickness, hoverThickness, additionalPadding, foldBlockOnClick, highlightActiveBlockBackground, hideFirstIndent, } = state.facet(indentationGuidesConfig);
|
|
359
|
+
const lineLevels = new Map();
|
|
360
|
+
for (const line of visibleLines) {
|
|
361
|
+
const entry = indentationMap.get(line.number);
|
|
362
|
+
const level = (_a = entry === null || entry === void 0 ? void 0 : entry.level) !== null && _a !== void 0 ? _a : 0;
|
|
363
|
+
lineLevels.set(line.number, level);
|
|
364
|
+
if (!entry) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
/** Workaround for folded empty lines in python and similar languages.
|
|
368
|
+
* Without this check, unwanted indentation guides will be drawn after foldPlaceholder
|
|
369
|
+
*/
|
|
370
|
+
const foldedAndEmpty = isLineFolded(view, entry.line.number) &&
|
|
371
|
+
isLineEmpty(view, entry.line.number);
|
|
372
|
+
if (!level || foldedAndEmpty) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const deco = Decoration.widget({
|
|
376
|
+
widget: new ClickableIndentationGuideWidget(indentWidth, level, line.number, {
|
|
377
|
+
thickness,
|
|
378
|
+
activeThickness,
|
|
379
|
+
hoverThickness,
|
|
380
|
+
additionalPadding,
|
|
381
|
+
foldOnClick: foldBlockOnClick,
|
|
382
|
+
highlightBackground: highlightActiveBlockBackground,
|
|
383
|
+
hideFirstIndent,
|
|
384
|
+
}),
|
|
385
|
+
side: 0,
|
|
386
|
+
});
|
|
387
|
+
widgets.push(deco.range(line.from));
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
decoSet: Decoration.set(widgets),
|
|
391
|
+
lineLevels,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
396
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
397
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
398
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
399
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
400
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
401
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
402
|
+
});
|
|
403
|
+
};
|
|
353
404
|
/**
|
|
354
405
|
* Indentation map for a set of lines.
|
|
355
406
|
*
|
|
@@ -367,15 +418,40 @@ class IndentationMap {
|
|
|
367
418
|
* @param markerType - The type of indentation to use (terminate at end of scope vs last line of code in scope)
|
|
368
419
|
*/
|
|
369
420
|
constructor(lines, state, unitWidth, markerType) {
|
|
370
|
-
this.
|
|
421
|
+
this.terminated = false;
|
|
371
422
|
this.state = state;
|
|
372
423
|
this.map = new Map();
|
|
373
424
|
this.unitWidth = unitWidth;
|
|
374
425
|
this.markerType = markerType;
|
|
375
|
-
for (const line of
|
|
426
|
+
for (const line of lines) {
|
|
376
427
|
this.add(line);
|
|
377
428
|
}
|
|
378
429
|
}
|
|
430
|
+
initLinesSync(lines) {
|
|
431
|
+
const linesArr = [...lines];
|
|
432
|
+
for (let i = 0; i < linesArr.length; i++) {
|
|
433
|
+
const line = linesArr[i];
|
|
434
|
+
this.add(line);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
initLinesAsync(lines) {
|
|
438
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
439
|
+
const linesArr = [...lines];
|
|
440
|
+
for (let i = 0; i < linesArr.length; i++) {
|
|
441
|
+
if (this.terminated) {
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
const line = linesArr[i];
|
|
445
|
+
this.add(line);
|
|
446
|
+
if (i % 100 === 0) {
|
|
447
|
+
yield new Promise((resolve) => setTimeout(resolve));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
terminate() {
|
|
453
|
+
this.terminated = true;
|
|
454
|
+
}
|
|
379
455
|
/**
|
|
380
456
|
* Checks if the indentation map has an entry for the given line.
|
|
381
457
|
*
|
|
@@ -393,9 +469,6 @@ class IndentationMap {
|
|
|
393
469
|
*/
|
|
394
470
|
get(line) {
|
|
395
471
|
const entry = this.map.get(typeof line === 'number' ? line : line.number);
|
|
396
|
-
if (!entry) {
|
|
397
|
-
throw new Error('Line not found in indentation map');
|
|
398
|
-
}
|
|
399
472
|
return entry;
|
|
400
473
|
}
|
|
401
474
|
/**
|
|
@@ -417,6 +490,7 @@ class IndentationMap {
|
|
|
417
490
|
* @param line - The {@link Line} to add to the map.
|
|
418
491
|
*/
|
|
419
492
|
add(line) {
|
|
493
|
+
var _a;
|
|
420
494
|
if (this.has(line)) {
|
|
421
495
|
return this.get(line);
|
|
422
496
|
}
|
|
@@ -429,10 +503,13 @@ class IndentationMap {
|
|
|
429
503
|
// if we're at the end, we'll just use the previous line's indentation
|
|
430
504
|
if (line.number === this.state.doc.lines) {
|
|
431
505
|
const prev = this.closestNonEmpty(line, -1);
|
|
432
|
-
return this.set(line, 0, prev.level);
|
|
506
|
+
return this.set(line, 0, (_a = prev === null || prev === void 0 ? void 0 : prev.level) !== null && _a !== void 0 ? _a : 0);
|
|
433
507
|
}
|
|
434
508
|
const prev = this.closestNonEmpty(line, -1);
|
|
435
509
|
const next = this.closestNonEmpty(line, 1);
|
|
510
|
+
if (!prev || !next) {
|
|
511
|
+
return this.set(line, 0, 0);
|
|
512
|
+
}
|
|
436
513
|
// if the next line ends the block and the marker type is not set to codeOnly,
|
|
437
514
|
// we'll just use the previous line's indentation
|
|
438
515
|
if (prev.level >= next.level && this.markerType !== 'codeOnly') {
|
|
@@ -466,7 +543,7 @@ class IndentationMap {
|
|
|
466
543
|
while (dir === -1 ? lineNo >= 1 : lineNo <= this.state.doc.lines) {
|
|
467
544
|
if (this.has(lineNo)) {
|
|
468
545
|
const entry = this.get(lineNo);
|
|
469
|
-
if (!entry.empty) {
|
|
546
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.empty)) {
|
|
470
547
|
return entry;
|
|
471
548
|
}
|
|
472
549
|
}
|
|
@@ -496,6 +573,9 @@ class IndentationMap {
|
|
|
496
573
|
return;
|
|
497
574
|
}
|
|
498
575
|
let current = this.get(currentLine);
|
|
576
|
+
if (!current) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
499
579
|
// check if the current line is starting a new block, if yes, we want to
|
|
500
580
|
// start from inside the block.
|
|
501
581
|
if (this.has(current.line.number + 1)) {
|
|
@@ -524,7 +604,7 @@ class IndentationMap {
|
|
|
524
604
|
continue;
|
|
525
605
|
}
|
|
526
606
|
const prev = this.get(start - 1);
|
|
527
|
-
if (prev.level < current.level) {
|
|
607
|
+
if (!prev || prev.level < current.level) {
|
|
528
608
|
break;
|
|
529
609
|
}
|
|
530
610
|
prev.active = current.level;
|
|
@@ -536,7 +616,7 @@ class IndentationMap {
|
|
|
536
616
|
continue;
|
|
537
617
|
}
|
|
538
618
|
const next = this.get(end + 1);
|
|
539
|
-
if (next.level < current.level) {
|
|
619
|
+
if (!next || next.level < current.level) {
|
|
540
620
|
break;
|
|
541
621
|
}
|
|
542
622
|
next.active = current.level;
|
|
@@ -552,48 +632,6 @@ function getNewIndentationMap(state, lines) {
|
|
|
552
632
|
return new IndentationMap(lines, state, indentWidth, markerType);
|
|
553
633
|
}
|
|
554
634
|
|
|
555
|
-
function buildIndentationDecorations(view) {
|
|
556
|
-
var _a;
|
|
557
|
-
const state = view.state;
|
|
558
|
-
const widgets = [];
|
|
559
|
-
const lines = getVisibleLines(view);
|
|
560
|
-
const indentWidth = getIndentUnit(state);
|
|
561
|
-
const { thickness, activeThickness, hoverThickness, additionalPadding, foldBlockOnClick, highlightActiveBlockBackground, hideFirstIndent, } = state.facet(indentationGuidesConfig);
|
|
562
|
-
const map = getNewIndentationMap(state, lines);
|
|
563
|
-
const lineLevels = new Map();
|
|
564
|
-
for (const line of lines) {
|
|
565
|
-
const entry = map.get(line.number);
|
|
566
|
-
const level = (_a = entry === null || entry === void 0 ? void 0 : entry.level) !== null && _a !== void 0 ? _a : 0;
|
|
567
|
-
lineLevels.set(line.number, level);
|
|
568
|
-
/** Workaround for folded empty lines in python and similar languages.
|
|
569
|
-
* Without this check, unwanted indentation guides will be drawn after foldPlaceholder
|
|
570
|
-
*/
|
|
571
|
-
const foldedAndEmpty = isLineFolded(view, entry.line.number) &&
|
|
572
|
-
isLineEmpty(view, entry.line.number);
|
|
573
|
-
if (!level || foldedAndEmpty) {
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const deco = Decoration.widget({
|
|
577
|
-
widget: new ClickableIndentationGuideWidget(indentWidth, level, line.number, {
|
|
578
|
-
thickness,
|
|
579
|
-
activeThickness,
|
|
580
|
-
hoverThickness,
|
|
581
|
-
additionalPadding,
|
|
582
|
-
foldOnClick: foldBlockOnClick,
|
|
583
|
-
highlightBackground: highlightActiveBlockBackground,
|
|
584
|
-
hideFirstIndent,
|
|
585
|
-
}),
|
|
586
|
-
side: 0,
|
|
587
|
-
});
|
|
588
|
-
widgets.push(deco.range(line.from));
|
|
589
|
-
}
|
|
590
|
-
return {
|
|
591
|
-
indentationMap: map,
|
|
592
|
-
decoSet: Decoration.set(widgets),
|
|
593
|
-
lineLevels,
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
|
|
597
635
|
function handleGuideHover(guideElement, plugin) {
|
|
598
636
|
var _a, _b, _c, _d, _e;
|
|
599
637
|
if (!guideElement || !plugin) {
|
|
@@ -618,28 +656,42 @@ function handleGuideHover(guideElement, plugin) {
|
|
|
618
656
|
plugin.applyHoverClasses(start, end, col);
|
|
619
657
|
}
|
|
620
658
|
|
|
659
|
+
const DEBOUNCE_DELAY_MS = 500;
|
|
621
660
|
class IndentationViewPlugin {
|
|
622
661
|
constructor(view) {
|
|
623
662
|
var _a, _b;
|
|
624
663
|
this.hoveredGuide = null;
|
|
625
|
-
|
|
664
|
+
this.lastCursorPos = null;
|
|
665
|
+
this.allStateDebounceId = null;
|
|
666
|
+
const visibleLines = getVisibleLines(view);
|
|
667
|
+
this.indentationMap = this.updateIndentationMap(view, visibleLines);
|
|
668
|
+
const built = buildIndentationDecorations(view, this.indentationMap, visibleLines);
|
|
626
669
|
this.highlightHoveredGuide = false;
|
|
627
670
|
this.highlightHoveredBlock = false;
|
|
628
|
-
this.activeColEdges = [];
|
|
629
|
-
this.activeCol = 0;
|
|
630
671
|
this.view = view;
|
|
631
|
-
this.viewContentWidth =
|
|
632
|
-
|
|
633
|
-
this.contentElement = view.dom.querySelector('.cm-content');
|
|
672
|
+
this.viewContentWidth = (_b = (_a = view.contentDOM) === null || _a === void 0 ? void 0 : _a.clientWidth) !== null && _b !== void 0 ? _b : 0;
|
|
673
|
+
this.contentElement = view.contentDOM;
|
|
634
674
|
this.decorations = built.decoSet;
|
|
635
675
|
this.lineLevels = built.lineLevels;
|
|
636
|
-
this.indentationMap = built.indentationMap;
|
|
637
676
|
this.unitWidth = getIndentUnit(view.state);
|
|
638
677
|
this.currentLineNumber = getCurrentLine(view.state).number;
|
|
639
678
|
}
|
|
679
|
+
updateIndentationMap(view, visibleLines) {
|
|
680
|
+
var _a;
|
|
681
|
+
if (this.allStateDebounceId) {
|
|
682
|
+
clearTimeout(this.allStateDebounceId);
|
|
683
|
+
}
|
|
684
|
+
(_a = this.indentationMap) === null || _a === void 0 ? void 0 : _a.terminate();
|
|
685
|
+
this.indentationMap = getNewIndentationMap(view.state, visibleLines);
|
|
686
|
+
this.allStateDebounceId = setTimeout(() => {
|
|
687
|
+
const allLines = getAllLines(view);
|
|
688
|
+
this.indentationMap.initLinesAsync(allLines);
|
|
689
|
+
}, DEBOUNCE_DELAY_MS);
|
|
690
|
+
return this.indentationMap;
|
|
691
|
+
}
|
|
640
692
|
update(update) {
|
|
641
693
|
var _a, _b, _c;
|
|
642
|
-
(_a = this.contentElement) !== null && _a !== void 0 ? _a : (this.contentElement = update.view.
|
|
694
|
+
(_a = this.contentElement) !== null && _a !== void 0 ? _a : (this.contentElement = update.view.contentDOM);
|
|
643
695
|
const unitWidth = getIndentUnit(update.state);
|
|
644
696
|
const unitWidthChanged = unitWidth !== this.unitWidth;
|
|
645
697
|
if (unitWidthChanged) {
|
|
@@ -657,11 +709,25 @@ class IndentationViewPlugin {
|
|
|
657
709
|
update.viewportChanged ||
|
|
658
710
|
viewContentWidth !== this.viewContentWidth;
|
|
659
711
|
const activeBlockUpdateRequired = isHighlightActive && viewChanged;
|
|
660
|
-
|
|
661
|
-
|
|
712
|
+
const visibleLines = update.docChanged ||
|
|
713
|
+
update.viewportChanged ||
|
|
714
|
+
unitWidthChanged ||
|
|
715
|
+
activeBlockUpdateRequired
|
|
716
|
+
? getVisibleLines(update.view)
|
|
717
|
+
: null;
|
|
718
|
+
if (!visibleLines) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (update.docChanged || unitWidthChanged) {
|
|
722
|
+
this.updateIndentationMap(update.view, visibleLines);
|
|
723
|
+
}
|
|
724
|
+
if (update.viewportChanged || unitWidthChanged) {
|
|
725
|
+
if (!update.docChanged) {
|
|
726
|
+
this.indentationMap.initLinesSync(visibleLines);
|
|
727
|
+
}
|
|
728
|
+
const built = buildIndentationDecorations(update.view, this.indentationMap, visibleLines);
|
|
662
729
|
this.decorations = built.decoSet;
|
|
663
730
|
this.lineLevels = built.lineLevels;
|
|
664
|
-
this.indentationMap = built.indentationMap;
|
|
665
731
|
this.clearHoverClasses();
|
|
666
732
|
}
|
|
667
733
|
if (activeBlockUpdateRequired) {
|
|
@@ -671,46 +737,20 @@ class IndentationViewPlugin {
|
|
|
671
737
|
}
|
|
672
738
|
updateActiveColumns(lineNumber, highlightActiveMarker, highlightActiveBlockBackground) {
|
|
673
739
|
requestAnimationFrame(() => {
|
|
674
|
-
var _a
|
|
740
|
+
var _a;
|
|
675
741
|
this.clearActiveClasses();
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
if (lines) {
|
|
679
|
-
lines.forEach(line => {
|
|
680
|
-
const { active: activeCol } = line;
|
|
681
|
-
const lineNumber = line.line.number;
|
|
682
|
-
if (highlightActiveMarker) {
|
|
683
|
-
this.applyActiveMarker(lineNumber, activeCol !== null && activeCol !== void 0 ? activeCol : 0);
|
|
684
|
-
}
|
|
685
|
-
if (highlightActiveBlockBackground) {
|
|
686
|
-
this.applyBackgroundHighlight(lineNumber, activeCol !== null && activeCol !== void 0 ? activeCol : 0, 'active');
|
|
687
|
-
}
|
|
688
|
-
});
|
|
689
|
-
this.activeColEdges = [
|
|
690
|
-
lines[0].line.number,
|
|
691
|
-
lines[lines.length - 1].line.number,
|
|
692
|
-
];
|
|
693
|
-
this.activeCol = (_b = lines[0].active) !== null && _b !== void 0 ? _b : 0;
|
|
742
|
+
const activeLines = (_a = this.indentationMap) === null || _a === void 0 ? void 0 : _a.getActiveLinesNumbers(lineNumber);
|
|
743
|
+
if (!(activeLines === null || activeLines === void 0 ? void 0 : activeLines.length) || activeLines[0].active === undefined) {
|
|
694
744
|
return;
|
|
695
745
|
}
|
|
696
|
-
|
|
697
|
-
|
|
746
|
+
const minLineNumber = activeLines.reduce((acc, line) => Math.min(acc, line.line.number), Infinity) - 1;
|
|
747
|
+
const maxLineNumber = activeLines.reduce((acc, line) => Math.max(acc, line.line.number), -Infinity) + 1;
|
|
748
|
+
if (highlightActiveMarker) {
|
|
749
|
+
this.applyActiveMarker(minLineNumber, maxLineNumber, activeLines[0].active);
|
|
750
|
+
}
|
|
751
|
+
if (highlightActiveBlockBackground) {
|
|
752
|
+
this.applyBackgroundHighlight(minLineNumber, maxLineNumber, activeLines[0].active, 'active');
|
|
698
753
|
}
|
|
699
|
-
/** If current line is outside computed indentation map lines,
|
|
700
|
-
* compute active block lines from active column points */
|
|
701
|
-
const ranges = this.activeColEdges.map(edge => this.computeBlockRange(edge, this.activeCol));
|
|
702
|
-
ranges.forEach(range => {
|
|
703
|
-
for (let i = range.start; i <= range.end; i++) {
|
|
704
|
-
if (highlightActiveMarker) {
|
|
705
|
-
this.applyActiveMarker(lineNumber, this.activeCol);
|
|
706
|
-
}
|
|
707
|
-
if (highlightActiveBlockBackground) {
|
|
708
|
-
this.applyBackgroundHighlight(lineNumber, this.activeCol, 'active');
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
this.activeColEdges.push(range.start, range.end);
|
|
712
|
-
});
|
|
713
|
-
this.activeColEdges = Array.from(new Set(this.activeColEdges));
|
|
714
754
|
});
|
|
715
755
|
}
|
|
716
756
|
/** compute block (inclusive line numbers) for a given clicked line and column (0-based) */
|
|
@@ -742,29 +782,37 @@ class IndentationViewPlugin {
|
|
|
742
782
|
return { start, end };
|
|
743
783
|
}
|
|
744
784
|
clearActiveClasses() {
|
|
745
|
-
const dom = this.view.
|
|
785
|
+
const dom = this.view.contentDOM;
|
|
746
786
|
dom
|
|
747
787
|
.querySelectorAll('.cm-indentation-guide-button_active')
|
|
748
|
-
.forEach(el => el.classList.remove('cm-indentation-guide-button_active'));
|
|
788
|
+
.forEach((el) => el.classList.remove('cm-indentation-guide-button_active'));
|
|
749
789
|
dom
|
|
750
790
|
.querySelectorAll('.cm-indentation-guide-background-highlight_active')
|
|
751
|
-
.forEach(el => {
|
|
791
|
+
.forEach((el) => {
|
|
752
792
|
el.classList.remove('cm-indentation-guide-background-highlight_active');
|
|
753
793
|
});
|
|
754
794
|
}
|
|
755
|
-
applyActiveMarker(
|
|
756
|
-
const root = this.view.
|
|
757
|
-
const selector = `.cm-indentation-guide
|
|
758
|
-
root.querySelectorAll(selector).forEach(btn => {
|
|
759
|
-
|
|
795
|
+
applyActiveMarker(start, end, col) {
|
|
796
|
+
const root = this.view.contentDOM;
|
|
797
|
+
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
798
|
+
root.querySelectorAll(selector).forEach((btn) => {
|
|
799
|
+
var _a;
|
|
800
|
+
const lineAttr = (_a = btn.parentElement) === null || _a === void 0 ? void 0 : _a.dataset.line;
|
|
801
|
+
if (!lineAttr) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const ln = Number(lineAttr);
|
|
805
|
+
if (ln >= start && ln <= end) {
|
|
806
|
+
btn.classList.add('cm-indentation-guide-button_active');
|
|
807
|
+
}
|
|
760
808
|
});
|
|
761
809
|
}
|
|
762
810
|
/** highlight DOM buttons in column `col` for lines in [start, end] */
|
|
763
811
|
applyHoverClasses(start, end, col) {
|
|
764
812
|
this.clearHoverClasses();
|
|
765
|
-
const root = this.view.
|
|
813
|
+
const root = this.view.contentDOM;
|
|
766
814
|
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
767
|
-
root.querySelectorAll(selector).forEach(btn => {
|
|
815
|
+
root.querySelectorAll(selector).forEach((btn) => {
|
|
768
816
|
var _a;
|
|
769
817
|
const lineAttr = (_a = btn.parentElement) === null || _a === void 0 ? void 0 : _a.dataset.line;
|
|
770
818
|
if (!lineAttr) {
|
|
@@ -776,68 +824,88 @@ class IndentationViewPlugin {
|
|
|
776
824
|
btn.classList.add('cm-indentation-guide-button_hover');
|
|
777
825
|
}
|
|
778
826
|
if (this.highlightHoveredBlock) {
|
|
779
|
-
this.applyBackgroundHighlight(
|
|
827
|
+
this.applyBackgroundHighlight(start, end, col, 'hover');
|
|
780
828
|
}
|
|
781
829
|
}
|
|
782
830
|
});
|
|
783
831
|
this.hoveredGuide = { col, start, end };
|
|
784
832
|
}
|
|
785
|
-
applyBackgroundHighlight(
|
|
786
|
-
const root = this.view.
|
|
787
|
-
const selector = `.cm-indentation-guide
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
833
|
+
applyBackgroundHighlight(start, end, col, type) {
|
|
834
|
+
const root = this.view.contentDOM;
|
|
835
|
+
const selector = `.cm-indentation-guide-button[data-col="${col}"]`;
|
|
836
|
+
const highlightBackgroundSelector = `.cm-indentation-guide-background-highlight`;
|
|
837
|
+
root.querySelectorAll(selector).forEach((button) => {
|
|
838
|
+
var _a, _b;
|
|
839
|
+
const lineAttr = (_a = button.parentElement) === null || _a === void 0 ? void 0 : _a.dataset.line;
|
|
840
|
+
if (!lineAttr) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const ln = Number(lineAttr);
|
|
844
|
+
if (ln < start || ln > end) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
const backgroundDiv = (_b = button.parentElement) === null || _b === void 0 ? void 0 : _b.querySelector(highlightBackgroundSelector);
|
|
848
|
+
if (!backgroundDiv) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
let highlightLeft = button.offsetLeft;
|
|
852
|
+
let btnWidth = button.offsetWidth;
|
|
853
|
+
backgroundDiv.style.setProperty(`--indentation-guide-background-highlight-${type}-left`, `${highlightLeft}px`);
|
|
854
|
+
backgroundDiv.style.setProperty(`--indentation-guide-background-highlight-${type}-width`, `${this.viewContentWidth - (highlightLeft !== null && highlightLeft !== void 0 ? highlightLeft : 0) - (btnWidth !== null && btnWidth !== void 0 ? btnWidth : 0) / 2}px`);
|
|
855
|
+
backgroundDiv.classList.add(`cm-indentation-guide-background-highlight_${type}`);
|
|
856
|
+
});
|
|
802
857
|
}
|
|
803
858
|
clearHoverClasses() {
|
|
804
859
|
var _a;
|
|
805
860
|
if (((_a = this.hoveredGuide) === null || _a === void 0 ? void 0 : _a.col) === null) {
|
|
806
861
|
return;
|
|
807
862
|
}
|
|
808
|
-
const dom = this.view.
|
|
863
|
+
const dom = this.view.contentDOM;
|
|
809
864
|
dom
|
|
810
865
|
.querySelectorAll('.cm-indentation-guide-button_hover')
|
|
811
|
-
.forEach(el => el.classList.remove('cm-indentation-guide-button_hover'));
|
|
866
|
+
.forEach((el) => el.classList.remove('cm-indentation-guide-button_hover'));
|
|
812
867
|
this.hoveredGuide = null;
|
|
813
868
|
dom
|
|
814
869
|
.querySelectorAll('.cm-indentation-guide-background-highlight')
|
|
815
|
-
.forEach(el => {
|
|
870
|
+
.forEach((el) => {
|
|
816
871
|
el.classList.remove('cm-indentation-guide-background-highlight_hover');
|
|
817
872
|
});
|
|
818
873
|
}
|
|
819
874
|
}
|
|
820
875
|
const indentationGuidesPlugin = /*@__PURE__*/ViewPlugin.fromClass(IndentationViewPlugin, {
|
|
821
|
-
decorations: v => v.decorations,
|
|
876
|
+
decorations: (v) => v.decorations,
|
|
822
877
|
eventHandlers: {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (!
|
|
878
|
+
scroll() {
|
|
879
|
+
var _a;
|
|
880
|
+
if (!this.lastCursorPos) {
|
|
826
881
|
return;
|
|
827
882
|
}
|
|
828
|
-
const
|
|
829
|
-
|
|
883
|
+
const target = (_a = document
|
|
884
|
+
.elementFromPoint(this.lastCursorPos.x, this.lastCursorPos.y)) === null || _a === void 0 ? void 0 : _a.closest('.cm-indentation-guide-button');
|
|
885
|
+
if (target) {
|
|
886
|
+
handleGuideHover(target, this);
|
|
887
|
+
}
|
|
888
|
+
return;
|
|
889
|
+
},
|
|
890
|
+
mousemove(e, view) {
|
|
891
|
+
const { highlightHoveredMarker, highlightHoveredBlockBackground } = view.state.facet(indentationGuidesConfig);
|
|
892
|
+
if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
|
|
830
893
|
return;
|
|
831
894
|
}
|
|
832
895
|
const target = e.target.closest('.cm-indentation-guide-button');
|
|
833
896
|
if (target) {
|
|
834
|
-
handleGuideHover(target,
|
|
897
|
+
handleGuideHover(target, this);
|
|
898
|
+
this.lastCursorPos = {
|
|
899
|
+
x: e.clientX,
|
|
900
|
+
y: e.clientY,
|
|
901
|
+
};
|
|
835
902
|
}
|
|
836
903
|
else {
|
|
837
|
-
|
|
904
|
+
this.clearHoverClasses();
|
|
905
|
+
this.lastCursorPos = null;
|
|
838
906
|
}
|
|
839
907
|
},
|
|
840
|
-
mouseout
|
|
908
|
+
mouseout(e, view) {
|
|
841
909
|
const { highlightHoveredMarker, highlightHoveredBlockBackground } = view.state.facet(indentationGuidesConfig);
|
|
842
910
|
if (!highlightHoveredMarker && !highlightHoveredBlockBackground) {
|
|
843
911
|
return;
|
|
@@ -847,11 +915,10 @@ const indentationGuidesPlugin = /*@__PURE__*/ViewPlugin.fromClass(IndentationVie
|
|
|
847
915
|
if (!related ||
|
|
848
916
|
!related.closest ||
|
|
849
917
|
!related.closest('.cm-indentation-guide')) {
|
|
850
|
-
|
|
851
|
-
plugin === null || plugin === void 0 ? void 0 : plugin.clearHoverClasses();
|
|
918
|
+
this.clearHoverClasses();
|
|
852
919
|
}
|
|
853
920
|
},
|
|
854
|
-
mousedown
|
|
921
|
+
mousedown(e, view) {
|
|
855
922
|
var _a, _b;
|
|
856
923
|
const { foldBlockOnClick } = view.state.facet(indentationGuidesConfig);
|
|
857
924
|
if (!foldBlockOnClick) {
|
|
@@ -867,10 +934,6 @@ const indentationGuidesPlugin = /*@__PURE__*/ViewPlugin.fromClass(IndentationVie
|
|
|
867
934
|
if (!lineAttr || !colAttr) {
|
|
868
935
|
return false;
|
|
869
936
|
}
|
|
870
|
-
const plugin = view.plugin(indentationGuidesPlugin);
|
|
871
|
-
if (!plugin) {
|
|
872
|
-
return false;
|
|
873
|
-
}
|
|
874
937
|
const lineNumber = Number(lineAttr);
|
|
875
938
|
const col = Number(colAttr);
|
|
876
939
|
const lines = getAllLines(view);
|
|
@@ -881,8 +944,8 @@ const indentationGuidesPlugin = /*@__PURE__*/ViewPlugin.fromClass(IndentationVie
|
|
|
881
944
|
const level = (_b = entry === null || entry === void 0 ? void 0 : entry.level) !== null && _b !== void 0 ? _b : 0;
|
|
882
945
|
lineLevels.set(line.number, level);
|
|
883
946
|
}
|
|
884
|
-
|
|
885
|
-
const { start } =
|
|
947
|
+
this.lineLevels = lineLevels;
|
|
948
|
+
const { start } = this.computeBlockRange(lineNumber, col);
|
|
886
949
|
const range = getNearestFoldRange(view, start - 1);
|
|
887
950
|
if (!range) {
|
|
888
951
|
return;
|
package/package.json
CHANGED
package/public/example.png
CHANGED
|
Binary file
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { getIndentUnit } from '@codemirror/language';
|
|
2
2
|
import { EditorView, Decoration } from '@codemirror/view';
|
|
3
3
|
import { indentationGuidesConfig } from './config';
|
|
4
|
-
import {
|
|
5
|
-
import { Range } from '@codemirror/state';
|
|
4
|
+
import { isLineEmpty, isLineFolded } from './utils';
|
|
5
|
+
import { Line, Range } from '@codemirror/state';
|
|
6
6
|
import { ClickableIndentationGuideWidget } from './clickable-indentation-guide-widget';
|
|
7
|
-
import {
|
|
7
|
+
import { IndentationMap } from './map';
|
|
8
8
|
|
|
9
|
-
export function buildIndentationDecorations(
|
|
9
|
+
export function buildIndentationDecorations(
|
|
10
|
+
view: EditorView,
|
|
11
|
+
indentationMap: IndentationMap,
|
|
12
|
+
visibleLines: Set<Line>,
|
|
13
|
+
) {
|
|
10
14
|
const state = view.state;
|
|
11
15
|
const widgets: Range<Decoration>[] = [];
|
|
12
|
-
const lines = getVisibleLines(view);
|
|
13
16
|
const indentWidth = getIndentUnit(state);
|
|
14
17
|
const {
|
|
15
18
|
thickness,
|
|
@@ -20,13 +23,15 @@ export function buildIndentationDecorations(view: EditorView) {
|
|
|
20
23
|
highlightActiveBlockBackground,
|
|
21
24
|
hideFirstIndent,
|
|
22
25
|
} = state.facet(indentationGuidesConfig);
|
|
23
|
-
const map = getNewIndentationMap(state, lines);
|
|
24
26
|
const lineLevels = new Map<number, number>();
|
|
25
27
|
|
|
26
|
-
for (const line of
|
|
27
|
-
const entry =
|
|
28
|
+
for (const line of visibleLines) {
|
|
29
|
+
const entry = indentationMap.get(line.number);
|
|
28
30
|
const level = entry?.level ?? 0;
|
|
29
31
|
lineLevels.set(line.number, level);
|
|
32
|
+
if (!entry) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
30
35
|
|
|
31
36
|
/** Workaround for folded empty lines in python and similar languages.
|
|
32
37
|
* Without this check, unwanted indentation guides will be drawn after foldPlaceholder
|
|
@@ -52,7 +57,7 @@ export function buildIndentationDecorations(view: EditorView) {
|
|
|
52
57
|
foldOnClick: foldBlockOnClick,
|
|
53
58
|
highlightBackground: highlightActiveBlockBackground,
|
|
54
59
|
hideFirstIndent,
|
|
55
|
-
}
|
|
60
|
+
},
|
|
56
61
|
),
|
|
57
62
|
side: 0,
|
|
58
63
|
});
|
|
@@ -61,7 +66,6 @@ export function buildIndentationDecorations(view: EditorView) {
|
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
return {
|
|
64
|
-
indentationMap: map,
|
|
65
69
|
decoSet: Decoration.set(widgets),
|
|
66
70
|
lineLevels,
|
|
67
71
|
};
|