@typbase/codemirror 0.3.1 → 0.4.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/LICENSE +190 -661
- package/dist/index.d.mts +133 -75
- package/dist/index.mjs +585 -352
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { acceptCompletion, autocompletion, closeBracketsKeymap, completionKeymap, snippet, startCompletion } from "@codemirror/autocomplete";
|
|
2
2
|
import { indentService } from "@codemirror/language";
|
|
3
3
|
import { Decoration, EditorView, ViewPlugin, WidgetType, hoverTooltip, keymap, showTooltip } from "@codemirror/view";
|
|
4
|
-
import { ChangeSet, EditorSelection, EditorState, Facet,
|
|
4
|
+
import { ChangeSet, EditorSelection, EditorState, Facet, RangeSetBuilder, StateEffect, StateField, countColumn, findColumn } from "@codemirror/state";
|
|
5
5
|
import { copyLineDown, copyLineUp, indentLess, indentMore, insertBlankLine, moveLineDown, moveLineUp, redo, redoSelection, simplifySelection, standardKeymap, toggleBlockComment, toggleLineComment, undo, undoSelection } from "@codemirror/commands";
|
|
6
6
|
import { linter, nextDiagnostic, setDiagnostics } from "@codemirror/lint";
|
|
7
7
|
import { closeSearchPanel, findNext, findPrevious, gotoLine, openSearchPanel, selectNextOccurrence, selectSelectionMatches } from "@codemirror/search";
|
|
@@ -583,6 +583,25 @@ const typstLanguageData = EditorState.languageData.of(() => [{
|
|
|
583
583
|
}]);
|
|
584
584
|
//#endregion
|
|
585
585
|
//#region src/notebook.ts
|
|
586
|
+
/** Every type, in the order the menu lists them. */
|
|
587
|
+
const NOTEBOOK_CELL_TYPES = [
|
|
588
|
+
"prose",
|
|
589
|
+
"code",
|
|
590
|
+
"log",
|
|
591
|
+
"hidden"
|
|
592
|
+
];
|
|
593
|
+
/** True when the cell renders in place rather than showing source plus output. */
|
|
594
|
+
function rendersInPlace(kind) {
|
|
595
|
+
return kind === "prose";
|
|
596
|
+
}
|
|
597
|
+
/** True when the cell's source is not part of what the reader sees. */
|
|
598
|
+
function hidesSource(kind) {
|
|
599
|
+
return kind === "log" || kind === "hidden";
|
|
600
|
+
}
|
|
601
|
+
/** True when the cell has an output area below its source. */
|
|
602
|
+
function hasOutput(kind) {
|
|
603
|
+
return kind === "code" || kind === "log";
|
|
604
|
+
}
|
|
586
605
|
/** The options of the notebook extension active in this state, if any. */
|
|
587
606
|
const notebookOptionsFacet = Facet.define({ combine: (values) => values.at(-1) });
|
|
588
607
|
function notebookOptions(state) {
|
|
@@ -590,16 +609,15 @@ function notebookOptions(state) {
|
|
|
590
609
|
}
|
|
591
610
|
/** Re-renders notebooks from cached frames without recompiling. */
|
|
592
611
|
const notebookRefreshEffect = StateEffect.define();
|
|
593
|
-
/**
|
|
594
|
-
const notebookRunEffect = StateEffect.define();
|
|
595
|
-
/** First position that belongs to the cell (marker line, or content). */
|
|
612
|
+
/** First position that belongs to the cell (attribute line, or content). */
|
|
596
613
|
function cellStart(cell) {
|
|
597
614
|
return cell.marker_end > cell.marker_start ? cell.marker_start : cell.content_start;
|
|
598
615
|
}
|
|
599
|
-
|
|
616
|
+
/** True when the cell opened with an attribute line rather than a heading. */
|
|
617
|
+
function hasAttribute(cell) {
|
|
600
618
|
return cell.marker_end > cell.marker_start;
|
|
601
619
|
}
|
|
602
|
-
/** Index of the cell containing `pos
|
|
620
|
+
/** Index of the cell containing `pos`. Gaps belong to the cell above. */
|
|
603
621
|
function cellIndexAt(cells, pos) {
|
|
604
622
|
let index = cells.length ? 0 : -1;
|
|
605
623
|
for (let i = 0; i < cells.length; i++) if (pos >= cellStart(cells[i])) index = i;
|
|
@@ -615,128 +633,137 @@ function blockEnd(cells, index, text) {
|
|
|
615
633
|
const next = cells[index + 1];
|
|
616
634
|
return next ? cellStart(next) : text.length;
|
|
617
635
|
}
|
|
618
|
-
/**
|
|
619
|
-
|
|
620
|
-
|
|
636
|
+
/**
|
|
637
|
+
* The attribute line for a cell type. Prose needs no `kind=`, so a plain cell is
|
|
638
|
+
* three characters.
|
|
639
|
+
*/
|
|
640
|
+
function attributeText(type, name) {
|
|
641
|
+
const fields = [name ? `name=${name}` : "", type === "prose" ? "" : `kind=${type}`].filter(Boolean);
|
|
642
|
+
return fields.length ? `//% ${fields.join(" ")}` : "//%";
|
|
621
643
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
644
|
+
/**
|
|
645
|
+
* Matches a whole attribute line, indentation and newline included. The rest of
|
|
646
|
+
* the line is taken whole rather than field by field, because this is a text
|
|
647
|
+
* cleaner for exports and the engine already decided what is an attribute.
|
|
648
|
+
*/
|
|
649
|
+
const ATTRIBUTE_LINE = /^[ \t]*\/\/%[^\r\n]*\r?\n?/gm;
|
|
650
|
+
/**
|
|
651
|
+
* Removes cell attribute lines. Notebooks are comments-plus-content, so a
|
|
652
|
+
* cleaned file is the same document for anyone who does not know the
|
|
653
|
+
* convention. Only `//%` goes: any other comment is left as written.
|
|
654
|
+
*/
|
|
655
|
+
function stripCellAttributes(text) {
|
|
656
|
+
return text.replace(ATTRIBUTE_LINE, "");
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Splits a cell at `pos` inside its content. The new cell takes the text from
|
|
660
|
+
* `pos` and inherits the old cell's kind, because a split is a continuation.
|
|
661
|
+
*/
|
|
662
|
+
function splitCellText(text, cells, index, pos) {
|
|
635
663
|
const cell = cells[index];
|
|
636
|
-
|
|
664
|
+
if (!cell || pos < cell.content_start || pos > cell.content_end) return null;
|
|
665
|
+
const trimmed = text.slice(pos, cell.content_end).replace(/^\n+/, "");
|
|
666
|
+
const insert = `\n\n${attributeText(cell.kind, cell.name)}\n\n${trimmed}`;
|
|
637
667
|
return {
|
|
638
668
|
changes: {
|
|
639
|
-
from,
|
|
640
|
-
|
|
669
|
+
from: pos,
|
|
670
|
+
to: cell.content_end,
|
|
671
|
+
insert
|
|
641
672
|
},
|
|
642
|
-
anchor:
|
|
673
|
+
anchor: pos + 2 + attributeText(cell.kind, cell.name).length + 2
|
|
643
674
|
};
|
|
644
675
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
function
|
|
676
|
+
/**
|
|
677
|
+
* Merges a cell into the one above. The result takes the lower cell's kind,
|
|
678
|
+
* since that is the one the reader was last looking at.
|
|
679
|
+
*/
|
|
680
|
+
function mergeCellText(cells, index) {
|
|
650
681
|
const cell = cells[index];
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
from: 0,
|
|
654
|
-
to: text.length,
|
|
655
|
-
insert: ""
|
|
656
|
-
},
|
|
657
|
-
anchor: 0
|
|
658
|
-
};
|
|
682
|
+
const above = cells[index - 1];
|
|
683
|
+
if (!cell || !above) return null;
|
|
659
684
|
const from = cellStart(cell);
|
|
685
|
+
const head = above.content_end > above.content_start ? above.content_end : from;
|
|
686
|
+
const gap = text_between_gap(head, cell.content_start);
|
|
660
687
|
return {
|
|
661
688
|
changes: {
|
|
662
|
-
from,
|
|
663
|
-
to:
|
|
664
|
-
insert:
|
|
689
|
+
from: head,
|
|
690
|
+
to: cell.content_start,
|
|
691
|
+
insert: gap
|
|
665
692
|
},
|
|
666
|
-
anchor:
|
|
693
|
+
anchor: head
|
|
667
694
|
};
|
|
668
695
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
return
|
|
696
|
+
/** The whitespace between two offsets, which a merge keeps as a separator. */
|
|
697
|
+
function text_between_gap(from, to) {
|
|
698
|
+
return from < to ? "\n\n" : "";
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Rewrites a cell's attribute line. A cell that began at a heading has no line
|
|
702
|
+
* to rewrite, so one is inserted.
|
|
703
|
+
*/
|
|
704
|
+
function setCellTypeText(text, cells, index, type) {
|
|
705
|
+
const cell = cells[index];
|
|
706
|
+
if (!cell) return null;
|
|
707
|
+
if (cell.kind === type) return null;
|
|
708
|
+
const attribute = attributeText(type, cell.name);
|
|
709
|
+
if (hasAttribute(cell)) return {
|
|
681
710
|
changes: {
|
|
682
|
-
from,
|
|
683
|
-
to,
|
|
684
|
-
insert:
|
|
711
|
+
from: cell.marker_start,
|
|
712
|
+
to: cell.marker_end,
|
|
713
|
+
insert: attribute
|
|
685
714
|
},
|
|
686
|
-
anchor:
|
|
715
|
+
anchor: cell.content_start + (attribute.length - (cell.marker_end - cell.marker_start))
|
|
687
716
|
};
|
|
688
|
-
}
|
|
689
|
-
function splitCellText(text, cells, index, pos) {
|
|
690
|
-
const cell = cells[index];
|
|
691
|
-
if (pos < cell.content_start || pos > cell.content_end) return null;
|
|
692
717
|
return {
|
|
693
718
|
changes: {
|
|
694
|
-
from:
|
|
695
|
-
insert:
|
|
719
|
+
from: cellStart(cell),
|
|
720
|
+
insert: `${attribute}\n`
|
|
696
721
|
},
|
|
697
|
-
anchor:
|
|
722
|
+
anchor: cell.content_start + attribute.length + 1
|
|
698
723
|
};
|
|
699
724
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
725
|
+
/** Appends a cell of `type` after `index` (or at the end of the document). */
|
|
726
|
+
function insertCellText(text, cells, index, type, content = "") {
|
|
727
|
+
const attribute = attributeText(type);
|
|
728
|
+
const at = blockEnd(cells, index, text);
|
|
729
|
+
const prefix = text.slice(Math.max(0, at - 2), at).includes("\n\n") || at === 0 ? "" : "\n\n";
|
|
703
730
|
return {
|
|
704
731
|
changes: {
|
|
705
|
-
from:
|
|
706
|
-
|
|
707
|
-
insert: ""
|
|
732
|
+
from: at,
|
|
733
|
+
insert: `${prefix}${attribute}\n${content}\n\n`
|
|
708
734
|
},
|
|
709
|
-
anchor:
|
|
735
|
+
anchor: at + prefix.length + attribute.length + 1 + content.length
|
|
710
736
|
};
|
|
711
737
|
}
|
|
712
|
-
function
|
|
738
|
+
function deleteCellText(cells, index, docLength) {
|
|
713
739
|
const cell = cells[index];
|
|
714
|
-
|
|
715
|
-
|
|
740
|
+
if (!cell) return null;
|
|
741
|
+
const next = cells[index + 1];
|
|
742
|
+
const nextStart = next ? cellStart(next) : docLength;
|
|
743
|
+
const from = hasAttribute(cell) ? cell.marker_start : cell.content_start;
|
|
744
|
+
if (!cells.length || index === 0 && !next && from === 0) return {
|
|
716
745
|
changes: {
|
|
717
|
-
from:
|
|
718
|
-
to:
|
|
719
|
-
insert:
|
|
746
|
+
from: 0,
|
|
747
|
+
to: docLength,
|
|
748
|
+
insert: ""
|
|
720
749
|
},
|
|
721
|
-
anchor:
|
|
750
|
+
anchor: 0
|
|
722
751
|
};
|
|
723
752
|
return {
|
|
724
753
|
changes: {
|
|
725
|
-
from
|
|
726
|
-
|
|
754
|
+
from,
|
|
755
|
+
to: nextStart,
|
|
756
|
+
insert: ""
|
|
727
757
|
},
|
|
728
|
-
anchor:
|
|
758
|
+
anchor: from
|
|
729
759
|
};
|
|
730
760
|
}
|
|
731
|
-
/**
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
*/
|
|
738
|
-
function stripCellMarkers(text) {
|
|
739
|
-
return text.replace(MARKER_LINE, "");
|
|
761
|
+
/** Copies a cell's kind, name, and body below it. */
|
|
762
|
+
function duplicateCellText(text, cells, index) {
|
|
763
|
+
const cell = cells[index];
|
|
764
|
+
if (!cell) return null;
|
|
765
|
+
const body = text.slice(cell.content_start, cell.content_end);
|
|
766
|
+
return insertCellText(text, cells, index, cell.kind, body);
|
|
740
767
|
}
|
|
741
768
|
function dispatchEdit(view, edit) {
|
|
742
769
|
view.dispatch({
|
|
@@ -746,22 +773,13 @@ function dispatchEdit(view, edit) {
|
|
|
746
773
|
});
|
|
747
774
|
return true;
|
|
748
775
|
}
|
|
749
|
-
/**
|
|
750
|
-
function runCell(view, index) {
|
|
751
|
-
if (!notebookOptions(view.state) || index < 0) return false;
|
|
752
|
-
view.dispatch({ effects: notebookRunEffect.of({ index }) });
|
|
753
|
-
return true;
|
|
754
|
-
}
|
|
755
|
-
function runAllCells(view) {
|
|
756
|
-
if (!notebookOptions(view.state)) return false;
|
|
757
|
-
view.dispatch({ effects: notebookRunEffect.of({ index: "all" }) });
|
|
758
|
-
return true;
|
|
759
|
-
}
|
|
776
|
+
/** Selects a cell's content so the keyboard commands act on it. */
|
|
760
777
|
function focusCell(view, index, edge = "start") {
|
|
761
778
|
const cell = cellsOf(view.state)[index];
|
|
762
779
|
if (!cell) return false;
|
|
780
|
+
const pos = edge === "start" ? cell.content_start : cell.content_end;
|
|
763
781
|
view.dispatch({
|
|
764
|
-
selection: { anchor:
|
|
782
|
+
selection: { anchor: pos },
|
|
765
783
|
scrollIntoView: true
|
|
766
784
|
});
|
|
767
785
|
view.focus();
|
|
@@ -770,148 +788,105 @@ function focusCell(view, index, edge = "start") {
|
|
|
770
788
|
function currentCell(view) {
|
|
771
789
|
return cellIndexAt(cellsOf(view.state), view.state.selection.main.head);
|
|
772
790
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
function insertCellAt(view, index, where) {
|
|
791
|
+
/** Inserts a cell below `index`, or at the end of the document. */
|
|
792
|
+
function insertCell(view, type, content = "", index = -1) {
|
|
777
793
|
const cells = cellsOf(view.state);
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
return dispatchEdit(view, insertCellText(view.state.doc.toString(), cells, index, where, type));
|
|
781
|
-
}
|
|
782
|
-
function duplicateCell(view) {
|
|
783
|
-
return duplicateCellAt(view, currentCell(view));
|
|
794
|
+
const edit = insertCellText(view.state.doc.toString(), cells, index >= 0 ? index : cells.length ? currentCell(view) : -1, type, content);
|
|
795
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
784
796
|
}
|
|
785
|
-
function
|
|
797
|
+
function duplicateCell(view, index = -1) {
|
|
786
798
|
const cells = cellsOf(view.state);
|
|
787
|
-
|
|
788
|
-
|
|
799
|
+
const at = index >= 0 ? index : currentCell(view);
|
|
800
|
+
const edit = duplicateCellText(view.state.doc.toString(), cells, at);
|
|
801
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
789
802
|
}
|
|
790
|
-
function deleteCell(view) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
function deleteCellAt(view, index) {
|
|
794
|
-
const cells = cellsOf(view.state);
|
|
795
|
-
if (index < 0 || !cells[index]) return false;
|
|
796
|
-
return dispatchEdit(view, deleteCellText(view.state.doc.toString(), cells, index));
|
|
803
|
+
function deleteCell(view, index = -1) {
|
|
804
|
+
const edit = deleteCellText(cellsOf(view.state), index >= 0 ? index : currentCell(view), view.state.doc.length);
|
|
805
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
797
806
|
}
|
|
807
|
+
/** Moves the active cell up or down. */
|
|
798
808
|
function moveCell(view, delta) {
|
|
799
|
-
return moveCellAt(view, currentCell(view), delta);
|
|
800
|
-
}
|
|
801
|
-
function moveCellAt(view, index, delta) {
|
|
802
|
-
const cells = cellsOf(view.state);
|
|
803
|
-
if (index < 0 || !cells[index]) return false;
|
|
804
|
-
const edit = moveCellText(view.state.doc.toString(), cells, index, delta);
|
|
805
|
-
if (!edit) return false;
|
|
806
|
-
return dispatchEdit(view, edit);
|
|
807
|
-
}
|
|
808
|
-
function splitCell(view) {
|
|
809
809
|
const cells = cellsOf(view.state);
|
|
810
810
|
const index = currentCell(view);
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
811
|
+
const next = index + delta;
|
|
812
|
+
if (next < 0 || next >= cells.length || index < 0) return false;
|
|
813
|
+
const text = view.state.doc.toString();
|
|
814
|
+
const first = cells[index];
|
|
815
|
+
const second = cells[next];
|
|
816
|
+
const from = cellStart(first);
|
|
817
|
+
const to = cellStart(second);
|
|
818
|
+
const firstEnd = blockEnd(cells, index, text);
|
|
819
|
+
const secondEnd = blockEnd(cells, next, text);
|
|
820
|
+
view.dispatch({
|
|
821
|
+
changes: [{
|
|
822
|
+
from,
|
|
823
|
+
to: firstEnd,
|
|
824
|
+
insert: text.slice(to, secondEnd)
|
|
825
|
+
}, {
|
|
826
|
+
from: to,
|
|
827
|
+
to: secondEnd,
|
|
828
|
+
insert: text.slice(from, firstEnd)
|
|
829
|
+
}],
|
|
830
|
+
selection: { anchor: from },
|
|
831
|
+
scrollIntoView: true
|
|
832
|
+
});
|
|
833
|
+
return true;
|
|
830
834
|
}
|
|
831
835
|
function setCellType(view, index, type) {
|
|
832
836
|
const cells = cellsOf(view.state);
|
|
833
|
-
|
|
834
|
-
return dispatchEdit(view,
|
|
837
|
+
const edit = setCellTypeText(view.state.doc.toString(), cells, index, type);
|
|
838
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
835
839
|
}
|
|
836
|
-
function
|
|
837
|
-
const cells = cellsOf(view.state);
|
|
838
|
-
const index = currentCell(view);
|
|
839
|
-
if (index < 0) return false;
|
|
840
|
-
runCell(view, index);
|
|
841
|
-
if (cells[index + 1]) focusCell(view, index + 1);
|
|
842
|
-
else dispatchEdit(view, insertCellText(view.state.doc.toString(), cells, index, "below", cells[index]?.kind ?? "markup"));
|
|
843
|
-
return true;
|
|
844
|
-
}
|
|
845
|
-
function runAndInsert(view) {
|
|
840
|
+
function splitCell(view) {
|
|
846
841
|
const cells = cellsOf(view.state);
|
|
847
842
|
const index = currentCell(view);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return dispatchEdit(view, insertCellText(view.state.doc.toString(), cells, index, "below", cells[index]?.kind ?? "markup"));
|
|
843
|
+
const edit = splitCellText(view.state.doc.toString(), cells, index, view.state.selection.main.from);
|
|
844
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
851
845
|
}
|
|
852
|
-
function
|
|
853
|
-
const
|
|
854
|
-
return
|
|
855
|
-
}
|
|
856
|
-
/** Arrow keys at a cell's first or last line move between cells. */
|
|
857
|
-
function boundaryMove(view, direction) {
|
|
858
|
-
const { state } = view;
|
|
859
|
-
const range = state.selection.main;
|
|
860
|
-
if (!range.empty) return false;
|
|
861
|
-
const cells = cellsOf(state);
|
|
862
|
-
const index = cellIndexAt(cells, range.head);
|
|
863
|
-
const cell = cells[index];
|
|
864
|
-
if (!cell) return false;
|
|
865
|
-
if (direction === -1) {
|
|
866
|
-
const first = state.doc.lineAt(cell.content_start);
|
|
867
|
-
if (range.head < first.from || range.head > first.to || index === 0) return false;
|
|
868
|
-
return focusCell(view, index - 1, "end");
|
|
869
|
-
}
|
|
870
|
-
const last = state.doc.lineAt(cell.content_end);
|
|
871
|
-
if (range.head < last.from || range.head > last.to || index >= cells.length - 1) return false;
|
|
872
|
-
return focusCell(view, index + 1);
|
|
846
|
+
function mergeCell(view) {
|
|
847
|
+
const edit = mergeCellText(cellsOf(view.state), currentCell(view));
|
|
848
|
+
return edit ? dispatchEdit(view, edit) : false;
|
|
873
849
|
}
|
|
874
850
|
/**
|
|
875
|
-
*
|
|
876
|
-
*
|
|
851
|
+
* The editor's cell commands. Everything here is also in the cell menu, and
|
|
852
|
+
* every one of them is a text edit, so the undo stack covers them all.
|
|
877
853
|
*/
|
|
878
|
-
const notebookKeymap =
|
|
879
|
-
{
|
|
880
|
-
key: "Shift-Enter",
|
|
881
|
-
run: runAndAdvance
|
|
882
|
-
},
|
|
854
|
+
const notebookKeymap = keymap.of([
|
|
883
855
|
{
|
|
884
856
|
key: "Mod-Enter",
|
|
885
|
-
|
|
857
|
+
preventDefault: true,
|
|
858
|
+
run: (view) => focusCell(view, currentCell(view), "end")
|
|
886
859
|
},
|
|
887
860
|
{
|
|
888
|
-
key: "
|
|
889
|
-
|
|
861
|
+
key: "Mod-Shift-Enter",
|
|
862
|
+
preventDefault: true,
|
|
863
|
+
run: (view) => splitCell(view)
|
|
890
864
|
},
|
|
891
865
|
{
|
|
892
|
-
key: "
|
|
866
|
+
key: "Mod-Shift-ArrowUp",
|
|
867
|
+
preventDefault: true,
|
|
893
868
|
run: (view) => moveCell(view, -1)
|
|
894
869
|
},
|
|
895
870
|
{
|
|
896
|
-
key: "
|
|
871
|
+
key: "Mod-Shift-ArrowDown",
|
|
872
|
+
preventDefault: true,
|
|
897
873
|
run: (view) => moveCell(view, 1)
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
key: "Escape",
|
|
901
|
-
run: (view) => {
|
|
902
|
-
notebookOptions(view.state)?.onCommandMode?.();
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
key: "ArrowUp",
|
|
908
|
-
run: (view) => boundaryMove(view, -1)
|
|
909
|
-
},
|
|
910
|
-
{
|
|
911
|
-
key: "ArrowDown",
|
|
912
|
-
run: (view) => boundaryMove(view, 1)
|
|
913
874
|
}
|
|
914
|
-
])
|
|
875
|
+
]);
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/sync.ts
|
|
878
|
+
/**
|
|
879
|
+
* Keeps the engine's sources in step with the editor, without rendering.
|
|
880
|
+
*/
|
|
881
|
+
const typstSourceSync = (fileId, prelude, typstState) => EditorState.transactionExtender.of((transaction) => {
|
|
882
|
+
if (!transaction.docChanged) return null;
|
|
883
|
+
try {
|
|
884
|
+
typstState.syncSource(fileId, transaction.newDoc.toString(), prelude.value);
|
|
885
|
+
} catch (error) {
|
|
886
|
+
console.error("[typst] source sync panicked:", error);
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
});
|
|
915
890
|
//#endregion
|
|
916
891
|
//#region src/diagnostics.ts
|
|
917
892
|
/**
|
|
@@ -919,7 +894,7 @@ const notebookKeymap = Prec.high(keymap.of([
|
|
|
919
894
|
* diagnostics of every compile here, so the linter source can reuse them
|
|
920
895
|
* instead of compiling the same text a second time.
|
|
921
896
|
*/
|
|
922
|
-
const diagnosticsCache =
|
|
897
|
+
const diagnosticsCache = new LRUCache({ max: 2 });
|
|
923
898
|
function rememberDiagnostics(path, text, diagnostics) {
|
|
924
899
|
diagnosticsCache.set(path, {
|
|
925
900
|
text,
|
|
@@ -958,11 +933,6 @@ function toLintDiagnostics(typstDiagnostics) {
|
|
|
958
933
|
}
|
|
959
934
|
/**
|
|
960
935
|
* Typst diagnostics as a CodeMirror linter source.
|
|
961
|
-
*
|
|
962
|
-
* This runs in every editor mode and combines with other sources (the
|
|
963
|
-
* spellcheck linter), so neither one replaces the other. The WYSIWYG plugin's
|
|
964
|
-
* compile feeds the cache; split and source mode fall back to a compile here
|
|
965
|
-
* so they get diagnostics too.
|
|
966
936
|
*/
|
|
967
937
|
const typstLinter = (fileId, spaceId, path, prelude, typstState, options = {}) => linter(async (view) => {
|
|
968
938
|
const text = view.state.doc.toString();
|
|
@@ -970,14 +940,14 @@ const typstLinter = (fileId, spaceId, path, prelude, typstState, options = {}) =
|
|
|
970
940
|
if (cached) return toLintDiagnostics(cached);
|
|
971
941
|
let result;
|
|
972
942
|
try {
|
|
973
|
-
result = typstState.
|
|
943
|
+
result = typstState.diagnosePaged(fileId, text, prelude.value);
|
|
974
944
|
} catch (error) {
|
|
975
945
|
console.error("[typst] diagnostics compile panicked:", error);
|
|
976
946
|
options.onPanic?.(fileId);
|
|
977
947
|
return [];
|
|
978
948
|
}
|
|
979
949
|
if (result.requests.length > 0 && options.onRequests) {
|
|
980
|
-
if (await options.onRequests(result.requests, spaceId)) result = typstState.
|
|
950
|
+
if (await options.onRequests(result.requests, spaceId)) result = typstState.diagnosePaged(fileId, text, prelude.value);
|
|
981
951
|
}
|
|
982
952
|
rememberDiagnostics(path, text, result.diagnostics);
|
|
983
953
|
options.onCompile?.();
|
|
@@ -986,6 +956,26 @@ const typstLinter = (fileId, spaceId, path, prelude, typstState, options = {}) =
|
|
|
986
956
|
//#endregion
|
|
987
957
|
//#region src/frames.ts
|
|
988
958
|
const containerCache = new LRUCache({ max: 128 });
|
|
959
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
960
|
+
let sharedDefs;
|
|
961
|
+
/**
|
|
962
|
+
* Places the glyph and gradient definitions a frame set references.
|
|
963
|
+
*/
|
|
964
|
+
function installSharedDefs(defs) {
|
|
965
|
+
if (!defs) return;
|
|
966
|
+
if (!sharedDefs || !sharedDefs.isConnected) {
|
|
967
|
+
const host = document.createElementNS(SVG_NS, "svg");
|
|
968
|
+
host.setAttribute("width", "0");
|
|
969
|
+
host.setAttribute("height", "0");
|
|
970
|
+
host.setAttribute("aria-hidden", "true");
|
|
971
|
+
host.style.position = "absolute";
|
|
972
|
+
sharedDefs = document.createElementNS(SVG_NS, "defs");
|
|
973
|
+
host.append(sharedDefs);
|
|
974
|
+
document.body.append(host);
|
|
975
|
+
}
|
|
976
|
+
sharedDefs.innerHTML = "";
|
|
977
|
+
sharedDefs.innerHTML = defs;
|
|
978
|
+
}
|
|
989
979
|
const openInNewTab = (url) => {
|
|
990
980
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
991
981
|
};
|
|
@@ -1012,8 +1002,8 @@ function frameSize(frame) {
|
|
|
1012
1002
|
* container keeps max-width: 100% so it never exceeds the line. An explicit
|
|
1013
1003
|
* container width would make CodeMirror's flex layout grow the content width
|
|
1014
1004
|
* forever (resize -> wider widget -> wider content). The pinned SVG overflows
|
|
1015
|
-
* the clamped container while the pane is narrower than the compiled width
|
|
1016
|
-
*
|
|
1005
|
+
* the clamped container while the pane is narrower than the compiled width.
|
|
1006
|
+
* The scroller clips it, so the render stays its size instead of scaling.
|
|
1017
1007
|
*/
|
|
1018
1008
|
function syncFrameContainer(container, frame) {
|
|
1019
1009
|
const size = frameSize(frame);
|
|
@@ -1082,7 +1072,7 @@ function frameIsInactive(view, state, start, end) {
|
|
|
1082
1072
|
* the source stays visible with the frame's height reserved, so the pane does
|
|
1083
1073
|
* not jump between render and source. Matches the inline WYSIWYG behavior.
|
|
1084
1074
|
*
|
|
1085
|
-
* `lineHeights` carries measured source heights from the remeasure pass
|
|
1075
|
+
* `lineHeights` carries measured source heights from the remeasure pass. When
|
|
1086
1076
|
* it is absent the view still reports the widget's height for those lines.
|
|
1087
1077
|
*/
|
|
1088
1078
|
function frameActiveDecorations(view, state, frame, lineHeights) {
|
|
@@ -1147,46 +1137,46 @@ var TypstWidget = class extends WidgetType {
|
|
|
1147
1137
|
};
|
|
1148
1138
|
//#endregion
|
|
1149
1139
|
//#region src/notebook-widgets.ts
|
|
1150
|
-
function
|
|
1140
|
+
function icon(name, size) {
|
|
1151
1141
|
const span = document.createElement("span");
|
|
1152
1142
|
span.className = "ms-icon material-symbols-rounded";
|
|
1153
|
-
span.textContent = name;
|
|
1154
1143
|
span.setAttribute("aria-hidden", "true");
|
|
1144
|
+
if (size !== void 0) span.style.fontSize = `calc(${size}px * var(--ui-size, 1))`;
|
|
1145
|
+
span.textContent = name;
|
|
1155
1146
|
return span;
|
|
1156
1147
|
}
|
|
1157
|
-
/**
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
toggleSource: "Collapse cell",
|
|
1168
|
-
noOutput: "No output",
|
|
1169
|
-
error: "Cell error"
|
|
1170
|
-
};
|
|
1171
|
-
/** Buttons must not hand the mousedown to CodeMirror's selection handling. */
|
|
1172
|
-
function actionButton(icon, label, onClick, className = "") {
|
|
1173
|
-
const button = document.createElement("button");
|
|
1174
|
-
button.type = "button";
|
|
1175
|
-
button.className = `tb-cell-btn ${className}`.trim();
|
|
1176
|
-
button.title = label;
|
|
1177
|
-
button.setAttribute("aria-label", label);
|
|
1178
|
-
button.append(iconSpan(icon));
|
|
1179
|
-
button.addEventListener("mousedown", (event) => event.preventDefault());
|
|
1180
|
-
button.addEventListener("click", (event) => {
|
|
1148
|
+
/** A button that must not hand the mousedown to CodeMirror's selection. */
|
|
1149
|
+
function button(name, label, onClick, className = "") {
|
|
1150
|
+
const element = document.createElement("button");
|
|
1151
|
+
element.type = "button";
|
|
1152
|
+
element.className = `tb-cell-btn${className ? ` ${className}` : ""}`;
|
|
1153
|
+
element.title = label;
|
|
1154
|
+
element.setAttribute("aria-label", label);
|
|
1155
|
+
element.append(icon(name, RAIL_ICON_SIZE));
|
|
1156
|
+
element.addEventListener("mousedown", (event) => event.preventDefault());
|
|
1157
|
+
element.addEventListener("click", (event) => {
|
|
1181
1158
|
event.preventDefault();
|
|
1182
1159
|
event.stopPropagation();
|
|
1183
|
-
onClick(
|
|
1160
|
+
onClick();
|
|
1184
1161
|
});
|
|
1185
|
-
return
|
|
1162
|
+
return element;
|
|
1186
1163
|
}
|
|
1187
|
-
|
|
1164
|
+
/**
|
|
1165
|
+
* Icon size across the rail: the chip glyph, its overflow button, and the menu's
|
|
1166
|
+
* items, which is the same row and `UiMenuItem`'s default.
|
|
1167
|
+
*/
|
|
1168
|
+
const RAIL_ICON_SIZE = 20;
|
|
1169
|
+
/** The glyph and label for a cell kind. */
|
|
1170
|
+
const KIND_PRESENTATION = {
|
|
1171
|
+
prose: "notes",
|
|
1172
|
+
code: "code",
|
|
1173
|
+
log: "terminal",
|
|
1174
|
+
hidden: "visibility_off"
|
|
1175
|
+
};
|
|
1176
|
+
var NotebookRailWidget = class extends WidgetType {
|
|
1188
1177
|
view;
|
|
1189
1178
|
args;
|
|
1179
|
+
menu = null;
|
|
1190
1180
|
constructor(view, args) {
|
|
1191
1181
|
super();
|
|
1192
1182
|
this.view = view;
|
|
@@ -1195,48 +1185,139 @@ var NotebookHeaderWidget = class extends WidgetType {
|
|
|
1195
1185
|
eq(other) {
|
|
1196
1186
|
const a = this.args;
|
|
1197
1187
|
const b = other.args;
|
|
1198
|
-
return a.index === b.index && a.kind === b.kind && a.
|
|
1188
|
+
return a.index === b.index && a.kind === b.kind && a.name === b.name && a.collapsed === b.collapsed && a.held === b.held && a.labels === b.labels;
|
|
1189
|
+
}
|
|
1190
|
+
ignoreEvent(event) {
|
|
1191
|
+
return event.type === "mousedown" && event.target.classList.contains("tb-cell-rail");
|
|
1199
1192
|
}
|
|
1200
1193
|
toDOM() {
|
|
1201
|
-
const { index, kind, labels } = this.args;
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1194
|
+
const { index, kind, name, labels } = this.args;
|
|
1195
|
+
const rail = document.createElement("div");
|
|
1196
|
+
rail.className = "tb-cell-rail";
|
|
1197
|
+
rail.dataset.kind = kind;
|
|
1198
|
+
rail.dataset.cell = String(index);
|
|
1199
|
+
const chip = document.createElement("span");
|
|
1200
|
+
chip.className = "tb-cell-chip";
|
|
1201
|
+
chip.append(icon(KIND_PRESENTATION[kind], RAIL_ICON_SIZE));
|
|
1202
|
+
chip.append(document.createTextNode(name || labels[kind]));
|
|
1203
|
+
rail.append(chip);
|
|
1204
|
+
if (this.args.held) {
|
|
1205
|
+
const held = document.createElement("span");
|
|
1206
|
+
held.className = "tb-cell-held";
|
|
1207
|
+
held.textContent = labels.held;
|
|
1208
|
+
rail.append(held);
|
|
1214
1209
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const actions = document.createElement("span");
|
|
1220
|
-
actions.className = "tb-cell-actions";
|
|
1221
|
-
actions.append(actionButton("arrow_upward", labels.moveUp, () => moveCellAt(this.view, index, -1)), actionButton("arrow_downward", labels.moveDown, () => moveCellAt(this.view, index, 1)), actionButton("content_copy", labels.duplicate, () => duplicateCellAt(this.view, index)), actionButton(this.args.collapsed ? "unfold_more" : "unfold_less", labels.toggleSource, () => {
|
|
1222
|
-
notebookOptions(this.view.state)?.onToggleCollapse?.(index);
|
|
1223
|
-
this.view.dispatch({ effects: notebookRefreshEffect.of(null) });
|
|
1224
|
-
}), actionButton("delete_sweep", labels.clearOutput, () => {
|
|
1225
|
-
notebookOptions(this.view.state)?.onClearOutput?.(index);
|
|
1226
|
-
this.view.dispatch({ effects: notebookRefreshEffect.of(null) });
|
|
1227
|
-
}), actionButton("delete", labels.remove, () => deleteCellAt(this.view, index)));
|
|
1228
|
-
header.append(actions);
|
|
1229
|
-
header.addEventListener("mousedown", (event) => {
|
|
1230
|
-
if (event.target?.closest("button")) return;
|
|
1210
|
+
const menu = button("more_vert", labels.menu, () => this.toggleMenu(rail), "tb-cell-btn--menu");
|
|
1211
|
+
rail.append(menu);
|
|
1212
|
+
rail.addEventListener("mousedown", (event) => {
|
|
1213
|
+
if (event.target.closest("button")) return;
|
|
1231
1214
|
event.preventDefault();
|
|
1232
1215
|
focusCell(this.view, index);
|
|
1233
1216
|
});
|
|
1234
|
-
return
|
|
1217
|
+
return rail;
|
|
1235
1218
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1219
|
+
toggleMenu(anchor) {
|
|
1220
|
+
if (this.menu) {
|
|
1221
|
+
this.closeMenu();
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const { labels, index, collapsed, held } = this.args;
|
|
1225
|
+
const items = [
|
|
1226
|
+
{
|
|
1227
|
+
command: "toggleSource",
|
|
1228
|
+
label: labels.toggleSource,
|
|
1229
|
+
name: collapsed ? "unfold_more" : "unfold_less"
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
command: "split",
|
|
1233
|
+
label: labels.split,
|
|
1234
|
+
name: "call_split"
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
command: "merge",
|
|
1238
|
+
label: labels.merge,
|
|
1239
|
+
name: "merge_type"
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
command: "moveUp",
|
|
1243
|
+
label: labels.moveUp,
|
|
1244
|
+
name: "keyboard_double_arrow_up"
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
command: "moveDown",
|
|
1248
|
+
label: labels.moveDown,
|
|
1249
|
+
name: "keyboard_double_arrow_down"
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
command: "duplicate",
|
|
1253
|
+
label: labels.duplicate,
|
|
1254
|
+
name: "content_copy"
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
command: "copy",
|
|
1258
|
+
label: labels.copy,
|
|
1259
|
+
name: "content_paste"
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
command: held ? "release" : "hold",
|
|
1263
|
+
label: held ? labels.release : labels.hold,
|
|
1264
|
+
name: held ? "play_arrow" : "pause"
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
command: "clearOutput",
|
|
1268
|
+
label: labels.clearOutput,
|
|
1269
|
+
name: "delete_sweep"
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
command: "remove",
|
|
1273
|
+
label: labels.remove,
|
|
1274
|
+
name: "delete"
|
|
1275
|
+
}
|
|
1276
|
+
];
|
|
1277
|
+
const menu = document.createElement("div");
|
|
1278
|
+
menu.className = "menu tb-cell-menu";
|
|
1279
|
+
menu.setAttribute("role", "menu");
|
|
1280
|
+
for (const item of items) {
|
|
1281
|
+
const entry = document.createElement("button");
|
|
1282
|
+
entry.type = "button";
|
|
1283
|
+
entry.className = "menu__item";
|
|
1284
|
+
entry.setAttribute("role", "menuitem");
|
|
1285
|
+
entry.append(icon(item.name, RAIL_ICON_SIZE));
|
|
1286
|
+
entry.append(document.createTextNode(item.label));
|
|
1287
|
+
entry.addEventListener("click", () => {
|
|
1288
|
+
this.closeMenu();
|
|
1289
|
+
this.args.onCommand(item.command, index);
|
|
1290
|
+
});
|
|
1291
|
+
menu.append(entry);
|
|
1292
|
+
}
|
|
1293
|
+
document.body.append(menu);
|
|
1294
|
+
const box = anchor.getBoundingClientRect();
|
|
1295
|
+
menu.style.position = "fixed";
|
|
1296
|
+
menu.style.left = `${Math.round(box.left)}px`;
|
|
1297
|
+
menu.style.top = `${Math.round(box.bottom + 4)}px`;
|
|
1298
|
+
this.menu = menu;
|
|
1299
|
+
const dismiss = (event) => {
|
|
1300
|
+
if (menu.contains(event.target)) return;
|
|
1301
|
+
this.closeMenu();
|
|
1302
|
+
};
|
|
1303
|
+
setTimeout(() => document.addEventListener("mousedown", dismiss), 0);
|
|
1304
|
+
document.addEventListener("keyup", this.onEscape);
|
|
1305
|
+
}
|
|
1306
|
+
onEscape = (event) => {
|
|
1307
|
+
if (event.key === "Escape") this.closeMenu();
|
|
1308
|
+
};
|
|
1309
|
+
closeMenu() {
|
|
1310
|
+
this.menu?.remove();
|
|
1311
|
+
this.menu = null;
|
|
1312
|
+
document.removeEventListener("keyup", this.onEscape);
|
|
1313
|
+
}
|
|
1314
|
+
destroy() {
|
|
1315
|
+
this.closeMenu();
|
|
1238
1316
|
}
|
|
1239
1317
|
};
|
|
1318
|
+
/**
|
|
1319
|
+
* A cell's output: its frames and the diagnostics whose span falls inside it.
|
|
1320
|
+
*/
|
|
1240
1321
|
var NotebookOutputWidget = class extends WidgetType {
|
|
1241
1322
|
view;
|
|
1242
1323
|
fileId;
|
|
@@ -1251,30 +1332,25 @@ var NotebookOutputWidget = class extends WidgetType {
|
|
|
1251
1332
|
this.args = args;
|
|
1252
1333
|
this.openExternal = openExternal;
|
|
1253
1334
|
}
|
|
1254
|
-
/** Hash of everything the DOM depends on; DOM identity is expensive. */
|
|
1255
1335
|
get key() {
|
|
1256
1336
|
const frames = this.args.frames.map((frame) => {
|
|
1257
1337
|
const size = frameSize(frame);
|
|
1258
1338
|
return `${frame.render.hash}:${Math.round(size.width)}x${Math.round(size.height)}`;
|
|
1259
1339
|
}).join(",");
|
|
1260
1340
|
const diagnostics = this.args.diagnostics.map((diagnostic) => diagnostic.message).join("|");
|
|
1261
|
-
return `${this.args.index}:${this.args.cleared}:${this.args.
|
|
1341
|
+
return `${this.args.index}:${this.args.cleared}:${this.args.held}:${frames}:${diagnostics}`;
|
|
1262
1342
|
}
|
|
1263
1343
|
eq(other) {
|
|
1264
1344
|
return this.key === other.key;
|
|
1265
1345
|
}
|
|
1266
1346
|
toDOM() {
|
|
1267
1347
|
const root = document.createElement("div");
|
|
1268
|
-
const { cleared, labels } = this.args;
|
|
1348
|
+
const { cleared, held, labels } = this.args;
|
|
1269
1349
|
root.className = "tb-cell-output";
|
|
1270
1350
|
if (cleared) {
|
|
1271
1351
|
root.classList.add("tb-cell-output--cleared");
|
|
1272
1352
|
return root;
|
|
1273
1353
|
}
|
|
1274
|
-
if (!(this.args.frames.length > 0 || this.args.diagnostics.length > 0 || this.args.hasRun)) {
|
|
1275
|
-
root.classList.add("tb-cell-output--empty");
|
|
1276
|
-
return root;
|
|
1277
|
-
}
|
|
1278
1354
|
const body = document.createElement("div");
|
|
1279
1355
|
body.className = "tb-cell-output-body";
|
|
1280
1356
|
for (const diagnostic of this.args.diagnostics) {
|
|
@@ -1300,7 +1376,12 @@ var NotebookOutputWidget = class extends WidgetType {
|
|
|
1300
1376
|
holder.append(container);
|
|
1301
1377
|
body.append(holder);
|
|
1302
1378
|
}
|
|
1303
|
-
if (
|
|
1379
|
+
if (held) {
|
|
1380
|
+
const note = document.createElement("div");
|
|
1381
|
+
note.className = "tb-cell-stale";
|
|
1382
|
+
note.textContent = labels.held;
|
|
1383
|
+
body.append(note);
|
|
1384
|
+
} else if (!this.args.frames.length && !this.args.diagnostics.length) {
|
|
1304
1385
|
const empty = document.createElement("div");
|
|
1305
1386
|
empty.className = "tb-cell-empty";
|
|
1306
1387
|
empty.textContent = labels.noOutput;
|
|
@@ -1315,51 +1396,109 @@ var NotebookOutputWidget = class extends WidgetType {
|
|
|
1315
1396
|
return height + 16;
|
|
1316
1397
|
}
|
|
1317
1398
|
};
|
|
1399
|
+
/**
|
|
1400
|
+
* The height an off-screen cell's output reserves, without building any of it.
|
|
1401
|
+
*/
|
|
1402
|
+
var NotebookOutputSpacer = class extends WidgetType {
|
|
1403
|
+
height;
|
|
1404
|
+
constructor(height) {
|
|
1405
|
+
super();
|
|
1406
|
+
this.height = height;
|
|
1407
|
+
}
|
|
1408
|
+
eq(other) {
|
|
1409
|
+
return this.height === other.height;
|
|
1410
|
+
}
|
|
1411
|
+
toDOM() {
|
|
1412
|
+
const root = document.createElement("div");
|
|
1413
|
+
root.className = "tb-cell-output tb-cell-output--offscreen";
|
|
1414
|
+
return root;
|
|
1415
|
+
}
|
|
1416
|
+
get estimatedHeight() {
|
|
1417
|
+
return this.height;
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1318
1420
|
function decorateNotebook(args) {
|
|
1319
|
-
const { view, state, cells, frames, diagnostics, fileId, typstState, locked, onExternalLink, options } = args;
|
|
1421
|
+
const { view, state, cells, frames, diagnostics, fileId, typstState, locked, readOnly, renderFrames, activeIndex, onCommand, onExternalLink, options } = args;
|
|
1320
1422
|
const decorations = [];
|
|
1321
1423
|
const byCell = cells.map(() => []);
|
|
1322
1424
|
for (const frame of frames) {
|
|
1323
1425
|
const index = cellIndexAt(cells, frame.range.start);
|
|
1324
1426
|
if (index >= 0) byCell[index].push(frame);
|
|
1325
1427
|
}
|
|
1326
|
-
const
|
|
1428
|
+
const labels = options.labels ?? FALLBACK_LABELS;
|
|
1429
|
+
const cellState = (index) => options.state?.(index) ?? {};
|
|
1430
|
+
const visible = /* @__PURE__ */ new Set();
|
|
1431
|
+
for (const range of view.visibleRanges) {
|
|
1432
|
+
const from = cellIndexAt(cells, range.from);
|
|
1433
|
+
const to = cellIndexAt(cells, range.to);
|
|
1434
|
+
for (let index = Math.max(0, from); index <= Math.min(cells.length - 1, to); index++) visible.add(index);
|
|
1435
|
+
}
|
|
1327
1436
|
cells.forEach((cell, index) => {
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1330
|
-
decorations.push(Decoration.
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1437
|
+
const own = cellState(index);
|
|
1438
|
+
const firstLine = state.doc.lineAt(cellStart(cell)).from;
|
|
1439
|
+
if (index === activeIndex) decorations.push(Decoration.line({ class: "tb-cell-active" }).range(firstLine));
|
|
1440
|
+
if (!readOnly) {
|
|
1441
|
+
decorations.push(Decoration.widget({
|
|
1442
|
+
widget: new NotebookRailWidget(view, {
|
|
1443
|
+
index,
|
|
1444
|
+
kind: cell.kind,
|
|
1445
|
+
name: cell.name,
|
|
1446
|
+
collapsed: own.collapsed ?? false,
|
|
1447
|
+
held: own.held ?? false,
|
|
1448
|
+
labels,
|
|
1449
|
+
onCommand
|
|
1450
|
+
}),
|
|
1451
|
+
block: !hasAttribute(cell),
|
|
1452
|
+
side: -1
|
|
1453
|
+
}).range(firstLine));
|
|
1454
|
+
if (hasAttribute(cell)) decorations.push(Decoration.line({ class: "tb-cell-attribute" }).range(state.doc.lineAt(cell.marker_start).from));
|
|
1455
|
+
}
|
|
1456
|
+
const contentStart = cell.content_start;
|
|
1457
|
+
const contentEnd = cell.content_end;
|
|
1458
|
+
const hideSource = (own.collapsed ?? false) || hidesSource(cell.kind);
|
|
1459
|
+
if (hideSource && contentEnd > contentStart) decorations.push(Decoration.replace({}).range(contentStart, contentEnd));
|
|
1460
|
+
if (hideSource) {
|
|
1461
|
+
if (renderFrames) pushDiagnostics(decorations, state, cell, diagnostics);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (rendersInPlace(cell.kind)) {
|
|
1465
|
+
if (!renderFrames) return;
|
|
1345
1466
|
for (const frame of byCell[index] ?? []) {
|
|
1346
1467
|
const { start, end } = frame.range;
|
|
1347
1468
|
if (!frame.render || end <= start) continue;
|
|
1348
1469
|
if (frameIsInactive(view, state, start, end)) decorations.push(Decoration.replace({ widget: new TypstWidget(view, frame, locked, fileId, typstState, onExternalLink) }).range(start, end));
|
|
1349
1470
|
else decorations.push(...frameActiveDecorations(view, state, frame));
|
|
1350
1471
|
}
|
|
1472
|
+
if (renderFrames) pushDiagnostics(decorations, state, cell, diagnostics);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (!hasOutput(cell.kind)) return;
|
|
1476
|
+
if (!renderFrames) return;
|
|
1477
|
+
const outputPos = contentEnd > contentStart ? state.doc.lineAt(contentEnd).to : contentStart;
|
|
1478
|
+
const held = own.held ?? false;
|
|
1479
|
+
const output = held ? own.frozen ?? {
|
|
1480
|
+
frames: [],
|
|
1481
|
+
diagnostics: []
|
|
1482
|
+
} : {
|
|
1483
|
+
frames: byCell[index] ?? [],
|
|
1484
|
+
diagnostics: cellDiagnosticsFor(cell, diagnostics)
|
|
1485
|
+
};
|
|
1486
|
+
if (!visible.has(index)) {
|
|
1487
|
+
decorations.push(Decoration.widget({
|
|
1488
|
+
widget: new NotebookOutputSpacer(outputHeight(output.frames)),
|
|
1489
|
+
block: true,
|
|
1490
|
+
side: 1
|
|
1491
|
+
}).range(outputPos));
|
|
1351
1492
|
return;
|
|
1352
1493
|
}
|
|
1353
|
-
const outputPos = cell.content_end > cell.content_start ? state.doc.lineAt(cell.content_end).to : cell.content_start;
|
|
1354
|
-
const cellDiagnostics = diagnostics.filter((diagnostic) => diagnostic.range.start >= cell.content_start && diagnostic.range.start <= cell.content_end);
|
|
1355
1494
|
decorations.push(Decoration.widget({
|
|
1356
1495
|
widget: new NotebookOutputWidget(view, fileId, typstState, {
|
|
1357
1496
|
index,
|
|
1358
|
-
frames:
|
|
1359
|
-
diagnostics:
|
|
1360
|
-
cleared:
|
|
1361
|
-
|
|
1362
|
-
labels
|
|
1497
|
+
frames: output.frames,
|
|
1498
|
+
diagnostics: output.diagnostics,
|
|
1499
|
+
cleared: own.cleared ?? false,
|
|
1500
|
+
held,
|
|
1501
|
+
labels
|
|
1363
1502
|
}, onExternalLink),
|
|
1364
1503
|
block: true,
|
|
1365
1504
|
side: 1
|
|
@@ -1367,9 +1506,79 @@ function decorateNotebook(args) {
|
|
|
1367
1506
|
});
|
|
1368
1507
|
return decorations;
|
|
1369
1508
|
}
|
|
1509
|
+
function outputHeight(frames) {
|
|
1510
|
+
let height = 0;
|
|
1511
|
+
for (const frame of frames) height += frameSize(frame).height;
|
|
1512
|
+
return height + 16;
|
|
1513
|
+
}
|
|
1514
|
+
/** The diagnostics whose span starts inside a cell's content. */
|
|
1515
|
+
function cellDiagnosticsFor(cell, diagnostics) {
|
|
1516
|
+
return diagnostics.filter((diagnostic) => diagnostic.range.start >= cell.content_start && diagnostic.range.start <= cell.content_end);
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* A cell's diagnostics as a block under its content, so an error is locatable
|
|
1520
|
+
* by cell rather than only in the lint panel.
|
|
1521
|
+
*/
|
|
1522
|
+
function pushDiagnostics(decorations, state, cell, diagnostics) {
|
|
1523
|
+
const own = cellDiagnosticsFor(cell, diagnostics);
|
|
1524
|
+
if (!own.length) return;
|
|
1525
|
+
const pos = cell.content_end > cell.content_start ? state.doc.lineAt(cell.content_end).to : cell.content_start;
|
|
1526
|
+
decorations.push(Decoration.widget({
|
|
1527
|
+
widget: new NotebookDiagnosticsWidget(own),
|
|
1528
|
+
block: true,
|
|
1529
|
+
side: 1
|
|
1530
|
+
}).range(pos));
|
|
1531
|
+
}
|
|
1532
|
+
var NotebookDiagnosticsWidget = class extends WidgetType {
|
|
1533
|
+
diagnostics;
|
|
1534
|
+
constructor(diagnostics) {
|
|
1535
|
+
super();
|
|
1536
|
+
this.diagnostics = diagnostics;
|
|
1537
|
+
}
|
|
1538
|
+
eq(other) {
|
|
1539
|
+
return this.diagnostics.length === other.diagnostics.length && this.diagnostics.every((diagnostic, index) => diagnostic.message === other.diagnostics[index]?.message);
|
|
1540
|
+
}
|
|
1541
|
+
toDOM() {
|
|
1542
|
+
const root = document.createElement("div");
|
|
1543
|
+
root.className = "tb-cell-diagnostics";
|
|
1544
|
+
for (const diagnostic of this.diagnostics) {
|
|
1545
|
+
const item = document.createElement("div");
|
|
1546
|
+
item.className = `tb-cell-diagnostic tb-cell-diagnostic--${diagnostic.severity}`;
|
|
1547
|
+
item.textContent = diagnostic.message;
|
|
1548
|
+
root.append(item);
|
|
1549
|
+
}
|
|
1550
|
+
return root;
|
|
1551
|
+
}
|
|
1552
|
+
get estimatedHeight() {
|
|
1553
|
+
return this.diagnostics.length * 22 + 8;
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
const FALLBACK_LABELS = {
|
|
1557
|
+
code: "Code",
|
|
1558
|
+
prose: "Text",
|
|
1559
|
+
log: "Log",
|
|
1560
|
+
hidden: "Hidden",
|
|
1561
|
+
menu: "Cell actions",
|
|
1562
|
+
split: "Split cell",
|
|
1563
|
+
merge: "Merge with the cell above",
|
|
1564
|
+
moveUp: "Move cell up",
|
|
1565
|
+
moveDown: "Move cell down",
|
|
1566
|
+
duplicate: "Duplicate cell",
|
|
1567
|
+
remove: "Delete cell",
|
|
1568
|
+
copy: "Copy cell",
|
|
1569
|
+
hold: "Hold output",
|
|
1570
|
+
release: "Release output",
|
|
1571
|
+
clearOutput: "Clear output",
|
|
1572
|
+
toggleSource: "Collapse cell",
|
|
1573
|
+
noOutput: "No output",
|
|
1574
|
+
held: "Held"
|
|
1575
|
+
};
|
|
1370
1576
|
//#endregion
|
|
1371
1577
|
//#region src/widgets.ts
|
|
1372
|
-
|
|
1578
|
+
/**
|
|
1579
|
+
* Compiled frames per cache key, where the key is `path:revision:appRevision`.
|
|
1580
|
+
*/
|
|
1581
|
+
const compileCache = new LRUCache({ max: 2 });
|
|
1373
1582
|
const updateFlagStore = /* @__PURE__ */ new Set();
|
|
1374
1583
|
/**
|
|
1375
1584
|
* Dispatch this to recompile even when the document did not change. Used when
|
|
@@ -1390,11 +1599,14 @@ function editorRenderWidth(scrollDOM, contentDOM) {
|
|
|
1390
1599
|
const tooltipsStateEffect = StateEffect.define();
|
|
1391
1600
|
const tooltipsStateField = StateField.define({
|
|
1392
1601
|
create() {
|
|
1393
|
-
return
|
|
1602
|
+
return {
|
|
1603
|
+
tooltips: [],
|
|
1604
|
+
ranges: []
|
|
1605
|
+
};
|
|
1394
1606
|
},
|
|
1395
|
-
update(
|
|
1607
|
+
update(overlays, transaction) {
|
|
1396
1608
|
const effect = transaction.effects.find((e) => e.is(tooltipsStateEffect));
|
|
1397
|
-
return effect ? effect.value :
|
|
1609
|
+
return effect ? effect.value : overlays;
|
|
1398
1610
|
}
|
|
1399
1611
|
});
|
|
1400
1612
|
/**
|
|
@@ -1440,25 +1652,27 @@ function measureLineHeights(view, frames) {
|
|
|
1440
1652
|
}
|
|
1441
1653
|
return heights;
|
|
1442
1654
|
}
|
|
1443
|
-
function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidget: _updateInWidget, widthChanged, forced, typstState, revision, onRequests, onPanic, onCompile, onExternalLink, notebook }) {
|
|
1655
|
+
function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidget: _updateInWidget, widthChanged, forced, typstState, revision, onRequests, onPanic, onCompile, onExternalLink, notebook, renderFrames = true }) {
|
|
1444
1656
|
const text = update.state.doc.toString();
|
|
1445
1657
|
const isFlaggedForUpdate = updateFlagStore.has(path);
|
|
1446
1658
|
const cacheKey = compileCacheKey(path, typstState, revision);
|
|
1447
1659
|
let frames;
|
|
1448
1660
|
let tooltips;
|
|
1661
|
+
let equationRanges;
|
|
1449
1662
|
let diagnostics;
|
|
1450
1663
|
if (forced || update.docChanged || widthChanged || !compileCache.has(cacheKey)) {
|
|
1451
1664
|
if (isFlaggedForUpdate) updateFlagStore.delete(path);
|
|
1452
1665
|
else updateFlagStore.add(path);
|
|
1453
1666
|
let compileResult;
|
|
1454
1667
|
try {
|
|
1455
|
-
compileResult = typstState.compilePaged(fileId, text, prelude);
|
|
1668
|
+
compileResult = typstState.compilePaged(fileId, text, prelude, update.state.selection.main.from);
|
|
1456
1669
|
} catch (error) {
|
|
1457
1670
|
console.error("[typst] compile panicked:", error);
|
|
1458
1671
|
onPanic?.(fileId);
|
|
1459
1672
|
return {
|
|
1460
1673
|
decorations: Decoration.none,
|
|
1461
1674
|
tooltips: [],
|
|
1675
|
+
equationRanges: [],
|
|
1462
1676
|
frames: [],
|
|
1463
1677
|
active: []
|
|
1464
1678
|
};
|
|
@@ -1470,21 +1684,18 @@ function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidg
|
|
|
1470
1684
|
if (!wasUpdated) return;
|
|
1471
1685
|
update.view.dispatch({ effects: typstRecompileEffect.of(null) });
|
|
1472
1686
|
});
|
|
1473
|
-
(
|
|
1687
|
+
installSharedDefs(compileResult.defs);
|
|
1688
|
+
({frames, tooltips, equation_ranges: equationRanges} = compileResult);
|
|
1474
1689
|
diagnostics = compileResult.diagnostics;
|
|
1475
1690
|
compileCache.set(cacheKey, {
|
|
1476
1691
|
frames,
|
|
1477
1692
|
tooltips,
|
|
1478
|
-
|
|
1479
|
-
});
|
|
1480
|
-
notebook?.onCompile?.({
|
|
1481
|
-
text,
|
|
1482
|
-
frames,
|
|
1693
|
+
equationRanges,
|
|
1483
1694
|
diagnostics
|
|
1484
1695
|
});
|
|
1485
1696
|
} else {
|
|
1486
1697
|
const cached = compileCache.get(cacheKey);
|
|
1487
|
-
({frames, tooltips, diagnostics} = cached);
|
|
1698
|
+
({frames, tooltips, equationRanges, diagnostics} = cached);
|
|
1488
1699
|
}
|
|
1489
1700
|
const { view, state } = update;
|
|
1490
1701
|
if (notebook) {
|
|
@@ -1497,12 +1708,19 @@ function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidg
|
|
|
1497
1708
|
return {
|
|
1498
1709
|
decorations: Decoration.none,
|
|
1499
1710
|
tooltips: [],
|
|
1711
|
+
equationRanges: [],
|
|
1500
1712
|
frames: [],
|
|
1501
1713
|
active: []
|
|
1502
1714
|
};
|
|
1503
1715
|
}
|
|
1504
1716
|
notebook.onCells?.(cells);
|
|
1505
|
-
notebook.
|
|
1717
|
+
notebook.onCompile?.({
|
|
1718
|
+
text,
|
|
1719
|
+
frames,
|
|
1720
|
+
diagnostics
|
|
1721
|
+
});
|
|
1722
|
+
const activeIndex = cells.length ? cellIndexAt(cells, state.selection.main.head) : null;
|
|
1723
|
+
notebook.onActiveCell?.(activeIndex);
|
|
1506
1724
|
return {
|
|
1507
1725
|
decorations: Decoration.set(decorateNotebook({
|
|
1508
1726
|
view,
|
|
@@ -1513,10 +1731,15 @@ function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidg
|
|
|
1513
1731
|
fileId,
|
|
1514
1732
|
typstState,
|
|
1515
1733
|
locked,
|
|
1734
|
+
readOnly: notebook.readOnly?.() ?? false,
|
|
1735
|
+
renderFrames,
|
|
1736
|
+
activeIndex,
|
|
1516
1737
|
onExternalLink,
|
|
1738
|
+
onCommand: (command, index) => notebook.onCommand?.(command, index),
|
|
1517
1739
|
options: notebook
|
|
1518
1740
|
}), true),
|
|
1519
1741
|
tooltips,
|
|
1742
|
+
equationRanges,
|
|
1520
1743
|
frames,
|
|
1521
1744
|
active: []
|
|
1522
1745
|
};
|
|
@@ -1533,6 +1756,7 @@ function decorate({ fileId, spaceId, path, prelude, locked, update, updateInWidg
|
|
|
1533
1756
|
return {
|
|
1534
1757
|
decorations: built.decorations,
|
|
1535
1758
|
tooltips,
|
|
1759
|
+
equationRanges,
|
|
1536
1760
|
frames,
|
|
1537
1761
|
active: built.active
|
|
1538
1762
|
};
|
|
@@ -1552,6 +1776,7 @@ const typstStateField = StateField.define({
|
|
|
1552
1776
|
const typstViewPlugin = (fileId, spaceId, path, text, prelude, locked, typstState, options = {}) => ViewPlugin.define((_view) => {
|
|
1553
1777
|
let firstUpdate = true;
|
|
1554
1778
|
let resizeTimer;
|
|
1779
|
+
const renderFrames = options.renderFrames ?? true;
|
|
1555
1780
|
const remeasureKey = {};
|
|
1556
1781
|
/**
|
|
1557
1782
|
* A pass that shows a block's source runs while the block's widget is still
|
|
@@ -1591,12 +1816,9 @@ const typstViewPlugin = (fileId, spaceId, path, text, prelude, locked, typstStat
|
|
|
1591
1816
|
return {
|
|
1592
1817
|
update(update) {
|
|
1593
1818
|
let widthChanged = false;
|
|
1594
|
-
const
|
|
1595
|
-
const run = effects.find((effect) => effect.is(notebookRunEffect));
|
|
1596
|
-
const refresh = effects.some((effect) => effect.is(notebookRefreshEffect));
|
|
1819
|
+
const refresh = update.transactions.flatMap((transaction) => transaction.effects).some((effect) => effect.is(notebookRefreshEffect));
|
|
1597
1820
|
const forced = firstUpdate || update.transactions.some((transaction) => transaction.effects.some((effect) => effect.is(typstRecompileEffect)));
|
|
1598
1821
|
firstUpdate = false;
|
|
1599
|
-
if (run) options.notebook?.onRun?.(run.value.index);
|
|
1600
1822
|
if (update.geometryChanged) {
|
|
1601
1823
|
const { scrollDOM, contentDOM } = update.view;
|
|
1602
1824
|
try {
|
|
@@ -1613,7 +1835,7 @@ const typstViewPlugin = (fileId, spaceId, path, text, prelude, locked, typstStat
|
|
|
1613
1835
|
update.view.dispatch({ effects: typstRecompileEffect.of(null) });
|
|
1614
1836
|
}, 150);
|
|
1615
1837
|
}
|
|
1616
|
-
if (update.docChanged || update.selectionSet || update.focusChanged || forced ||
|
|
1838
|
+
if (update.docChanged || update.selectionSet || update.focusChanged || forced || refresh) {
|
|
1617
1839
|
queueMicrotask(() => {
|
|
1618
1840
|
let result;
|
|
1619
1841
|
try {
|
|
@@ -1626,14 +1848,15 @@ const typstViewPlugin = (fileId, spaceId, path, text, prelude, locked, typstStat
|
|
|
1626
1848
|
update,
|
|
1627
1849
|
updateInWidget: false,
|
|
1628
1850
|
widthChanged,
|
|
1629
|
-
forced
|
|
1851
|
+
forced,
|
|
1630
1852
|
typstState,
|
|
1631
1853
|
revision: options.revision,
|
|
1632
1854
|
onRequests: options.onRequests,
|
|
1633
1855
|
onPanic: options.onPanic,
|
|
1634
1856
|
onCompile: options.onCompile,
|
|
1635
1857
|
onExternalLink: options.onExternalLink,
|
|
1636
|
-
notebook: options.notebook
|
|
1858
|
+
notebook: options.notebook,
|
|
1859
|
+
renderFrames
|
|
1637
1860
|
});
|
|
1638
1861
|
} catch (error) {
|
|
1639
1862
|
console.error("[typst] decorate panicked:", error);
|
|
@@ -1641,7 +1864,10 @@ const typstViewPlugin = (fileId, spaceId, path, text, prelude, locked, typstStat
|
|
|
1641
1864
|
return;
|
|
1642
1865
|
}
|
|
1643
1866
|
if (result) {
|
|
1644
|
-
const stateEffects = [typstStateEffect.of({ decorations: result.decorations }), tooltipsStateEffect.of(
|
|
1867
|
+
const stateEffects = [typstStateEffect.of({ decorations: result.decorations }), tooltipsStateEffect.of({
|
|
1868
|
+
tooltips: result.tooltips,
|
|
1869
|
+
ranges: result.equationRanges
|
|
1870
|
+
})];
|
|
1645
1871
|
update.view.dispatch({ effects: stateEffects });
|
|
1646
1872
|
remeasureActive(update, result);
|
|
1647
1873
|
}
|
|
@@ -1680,7 +1906,13 @@ const tooltipViewPlugin = () => ViewPlugin.define((view) => ({ update(update) {
|
|
|
1680
1906
|
}
|
|
1681
1907
|
const state = view.state;
|
|
1682
1908
|
const pos = state.selection.main.from;
|
|
1683
|
-
const tooltips = state.field(tooltipsStateField);
|
|
1909
|
+
const { tooltips, ranges } = state.field(tooltipsStateField);
|
|
1910
|
+
if (!tooltips.some(({ range }) => pos >= range.start && pos <= range.end)) {
|
|
1911
|
+
if (ranges.some(([start, end]) => pos >= start && pos <= end)) {
|
|
1912
|
+
view.dispatch({ effects: typstRecompileEffect.of(null) });
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1684
1916
|
let tooltip = null;
|
|
1685
1917
|
for (const { render, range } of tooltips) {
|
|
1686
1918
|
const { start, end } = range;
|
|
@@ -1694,8 +1926,8 @@ const tooltipViewPlugin = () => ViewPlugin.define((view) => ({ update(update) {
|
|
|
1694
1926
|
svg.setHTMLUnsafe(render.svg);
|
|
1695
1927
|
container.append(svg);
|
|
1696
1928
|
tooltip = {
|
|
1697
|
-
pos: start
|
|
1698
|
-
end
|
|
1929
|
+
pos: start,
|
|
1930
|
+
end,
|
|
1699
1931
|
create(tooltipView) {
|
|
1700
1932
|
return {
|
|
1701
1933
|
dom: container,
|
|
@@ -1704,8 +1936,8 @@ const tooltipViewPlugin = () => ViewPlugin.define((view) => ({ update(update) {
|
|
|
1704
1936
|
const endCoords = tooltipView.coordsAtPos(end);
|
|
1705
1937
|
return {
|
|
1706
1938
|
left: startCoords.left,
|
|
1707
|
-
right:
|
|
1708
|
-
top:
|
|
1939
|
+
right: endCoords.right,
|
|
1940
|
+
top: startCoords.top,
|
|
1709
1941
|
bottom: endCoords.bottom
|
|
1710
1942
|
};
|
|
1711
1943
|
}
|
|
@@ -1730,6 +1962,7 @@ const typstPlugin = (fileId, spaceId, path, text, prelude, locked, typstState, o
|
|
|
1730
1962
|
autocompletion({ override: [(context) => autocomplete(context, fileId, typstState)] }),
|
|
1731
1963
|
typstKeymap,
|
|
1732
1964
|
typstLanguageData,
|
|
1965
|
+
typstSourceSync(fileId, prelude, typstState),
|
|
1733
1966
|
typstSyntaxHighlighting(fileId, typstState),
|
|
1734
1967
|
typstHoverTooltip(fileId, typstState),
|
|
1735
1968
|
addSpaceBeforeClosingBracket,
|
|
@@ -2098,4 +2331,4 @@ function stringEnd(line, from) {
|
|
|
2098
2331
|
return line.length;
|
|
2099
2332
|
}
|
|
2100
2333
|
//#endregion
|
|
2101
|
-
export { TypstWidget, attachFrameInteractions, autocomplete, cellIndexAt, cellStart, cellsOf, compileCache, createFrameContainer, cursorAddVertical, cycleHeading, deleteCell,
|
|
2334
|
+
export { NOTEBOOK_CELL_TYPES, TypstWidget, attachFrameInteractions, attributeText, autocomplete, cellIndexAt, cellStart, cellsOf, compileCache, createFrameContainer, currentCell, cursorAddVertical, cycleHeading, deleteCell, deleteCellText, duplicateCell, duplicateCellText, focusCell, frameActiveDecorations, frameIsInactive, frameSize, hasAttribute, hasOutput, hidesSource, insertAtCursor, insertCell, insertCellText, insertFontFamily, insertTabLike, installSharedDefs, mergeCell, mergeCellText, moveCell, notebookKeymap, notebookOptions, notebookOptionsFacet, notebookRefreshEffect, parseBackticks, rememberDiagnostics, rendersInPlace, setCellType, setCellTypeText, splitCell, splitCellText, stripCellAttributes, syncFrameContainer, toLintDiagnostics, toggleAroundSelection, toggleCode, toggleEmph, toggleEnum, toggleList, toggleListLike, toggleMath, toggleStrike, toggleStrong, toggleUnderline, tooltipStateField, tooltipViewPlugin, tooltipsStateEffect, tooltipsStateField, typstHoverTooltip, typstKeymap, typstLanguageData, typstLinter, typstPlugin, typstRecompileEffect, typstSourceSync, typstStateField, typstStaticHighlighting, typstSyntaxHighlighting, typstViewPlugin, wrapOrInsert };
|