mrmd-editor 0.7.1 → 0.8.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/package.json +3 -1
- package/src/commands.js +112 -4
- package/src/comment-syntax.js +364 -39
- package/src/config/handlers.js +1 -2
- package/src/config/schema.js +46 -4
- package/src/document-template.js +2236 -0
- package/src/frontmatter-updater.js +204 -74
- package/src/grammar.js +758 -0
- package/src/index.js +1074 -55
- package/src/keymap.js +11 -2
- package/src/markdown/block-decorations.js +108 -5
- package/src/markdown/facets.js +37 -0
- package/src/markdown/html-inline.js +9 -5
- package/src/markdown/index.js +13 -3
- package/src/markdown/inline-commands.js +256 -0
- package/src/markdown/inline-model.js +578 -0
- package/src/markdown/inline-state.js +103 -0
- package/src/markdown/renderer.js +219 -12
- package/src/markdown/styles.js +290 -3
- package/src/markdown/widgets/alert-title.js +10 -8
- package/src/markdown/widgets/frontmatter.js +0 -6
- package/src/markdown/widgets/index.js +1 -0
- package/src/markdown/widgets/list-marker.js +29 -0
- package/src/markdown/wysiwyg.js +1158 -0
- package/src/mrp-types.js +2 -0
- package/src/output-widget.js +532 -18
- package/src/page-view-pagination.js +127 -0
- package/src/runtime-lsp.js +1757 -150
- package/src/section-controls/commands.js +617 -0
- package/src/section-controls/index.js +63 -0
- package/src/section-controls/plugin.js +165 -0
- package/src/section-controls/widgets.js +936 -0
- package/src/shell/ai-menu.js +11 -0
- package/src/shell/components/context-panel.js +572 -0
- package/src/shell/components/status-bar.js +10 -2
- package/src/shell/layouts/studio.js +206 -14
- package/src/shell/orchestrator-client.js +69 -0
- package/src/spellcheck.js +166 -0
- package/src/tables/README.md +97 -0
- package/src/tables/commands/insert-linked-table.js +122 -0
- package/src/tables/commands/open-table-workspace.js +43 -0
- package/src/tables/index.js +24 -0
- package/src/tables/jobs/client.js +158 -0
- package/src/tables/parsing/anchors.js +82 -0
- package/src/tables/parsing/linked-table-blocks.js +61 -0
- package/src/tables/state/linked-table-state.js +68 -0
- package/src/tables/widgets/linked-table-source-banner.js +77 -0
- package/src/tables/widgets/linked-table-widget.js +256 -0
- package/src/tables/workspace/controller.js +616 -0
- package/src/term-pty-client.js +51 -2
- package/src/term-widget.js +43 -3
- package/src/widgets/theme-utils.js +24 -16
- package/src/widgets/theme.js +1015 -1
- package/src/runtime-codelens/detector.js +0 -279
- package/src/runtime-codelens/index.js +0 -76
- package/src/runtime-codelens/plugin.js +0 -142
- package/src/runtime-codelens/styles.js +0 -184
- package/src/runtime-codelens/widgets.js +0 -216
package/src/index.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// #region IMPORTS
|
|
32
32
|
import { EditorView, basicSetup } from 'codemirror';
|
|
33
33
|
import { EditorState, StateEffect, Compartment, Text, Transaction } from '@codemirror/state';
|
|
34
|
-
import { keymap, Decoration, ViewPlugin, WidgetType, placeholder } from '@codemirror/view';
|
|
34
|
+
import { keymap, Decoration, ViewPlugin, WidgetType, placeholder, highlightWhitespace } from '@codemirror/view';
|
|
35
35
|
import { StreamLanguage, syntaxTree, syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
|
|
36
36
|
import { createCodemirrorTheme } from './widgets/codemirror-theme.js';
|
|
37
37
|
|
|
@@ -64,8 +64,44 @@ import { WebsocketProvider } from 'y-websocket';
|
|
|
64
64
|
// Internal modules
|
|
65
65
|
import { findCells, getCellAtCursor, countCells, findTerminalBlocks, isTerminalLanguage } from './cells.js';
|
|
66
66
|
import { RuntimeRegistry, createRuntimeRegistry } from './runtime.js';
|
|
67
|
+
import {
|
|
68
|
+
defaultDocumentTemplate,
|
|
69
|
+
documentTemplatePresets,
|
|
70
|
+
normalizeDocumentTemplate,
|
|
71
|
+
cloneDocumentTemplate,
|
|
72
|
+
createDocumentTemplateExtension,
|
|
73
|
+
compileDocumentTemplateCSS,
|
|
74
|
+
serializeDocumentTemplateToCss,
|
|
75
|
+
findDocumentTemplatePreset,
|
|
76
|
+
resolveFontForExport,
|
|
77
|
+
serializeDocumentTemplateToPandocMeta,
|
|
78
|
+
serializeDocumentTemplateToPandocYaml,
|
|
79
|
+
serializeDocumentTemplateToLatexPreamble,
|
|
80
|
+
serializeDocumentTemplateToHtml,
|
|
81
|
+
serializeDocumentTemplateToWordStyleMap,
|
|
82
|
+
buildPandocCommand,
|
|
83
|
+
} from './document-template.js';
|
|
84
|
+
import { parseFrontmatter, readFrontmatterValue, updateFrontmatterField } from './frontmatter-updater.js';
|
|
67
85
|
import { ExecutionManager, createExecutionManager } from './execution.js';
|
|
68
86
|
import { MonitorCoordination, EXECUTION_STATUS, createMonitorCoordination } from './monitor-coordination.js';
|
|
87
|
+
import * as linkedTables from './tables/index.js';
|
|
88
|
+
import {
|
|
89
|
+
LINKED_TABLE_EVENT,
|
|
90
|
+
dispatchLinkedTableAction,
|
|
91
|
+
openLinkedTableWorkspace,
|
|
92
|
+
canImportLinkedTableFromHost,
|
|
93
|
+
normalizeLinkedTableBlockInsertion,
|
|
94
|
+
insertLinkedTableBlock,
|
|
95
|
+
importLinkedTableFromHost,
|
|
96
|
+
TableJobsClient,
|
|
97
|
+
TABLE_JOB_STATUS,
|
|
98
|
+
createTableJobsClient,
|
|
99
|
+
createLinkedTableController,
|
|
100
|
+
LinkedTableController,
|
|
101
|
+
createLinkedTableBlockAnchor,
|
|
102
|
+
resolveLinkedTableBlockAnchor,
|
|
103
|
+
linkedTableMarkdownState,
|
|
104
|
+
} from './tables/index.js';
|
|
69
105
|
import { MRPClient } from './mrp-client.js';
|
|
70
106
|
|
|
71
107
|
// Shell (status bar, file management, studio layout)
|
|
@@ -83,20 +119,8 @@ import * as commentSyntaxModule from './comment-syntax.js';
|
|
|
83
119
|
// Cell controls (run buttons, queue, status)
|
|
84
120
|
import { createCellControls, CellControlsSystem } from './cell-controls/index.js';
|
|
85
121
|
|
|
86
|
-
//
|
|
87
|
-
import {
|
|
88
|
-
createRuntimeCodeLensExtensions,
|
|
89
|
-
rebuildRuntimeCodeLens,
|
|
90
|
-
runtimeCodeLensFacet,
|
|
91
|
-
runtimeCodeLensPlugin,
|
|
92
|
-
rebuildRuntimeCodeLensEffect,
|
|
93
|
-
injectRuntimeCodeLensStyles,
|
|
94
|
-
removeRuntimeCodeLensStyles,
|
|
95
|
-
findRuntimeBlocks,
|
|
96
|
-
findYamlConfigBlocks,
|
|
97
|
-
findSessionFrontmatter,
|
|
98
|
-
RuntimeCodeLensWidget,
|
|
99
|
-
} from './runtime-codelens/index.js';
|
|
122
|
+
// Section controls (AI/formatting next to focused line)
|
|
123
|
+
import { sectionControls } from './section-controls/index.js';
|
|
100
124
|
|
|
101
125
|
// Commands and keymap
|
|
102
126
|
import { commandRegistry } from './commands.js';
|
|
@@ -108,10 +132,24 @@ import {
|
|
|
108
132
|
adaptMRPClient,
|
|
109
133
|
createRuntimeHoverExtension,
|
|
110
134
|
createRuntimeCompletionExtension,
|
|
135
|
+
createRuntimeSignatureHelpExtension,
|
|
111
136
|
createVariableExplorer,
|
|
112
137
|
injectRuntimeLspStyles,
|
|
113
138
|
} from './runtime-lsp.js';
|
|
114
139
|
|
|
140
|
+
// Spellcheck (prose-only, disabled in code blocks)
|
|
141
|
+
import { createSpellcheckExtensions } from './spellcheck.js';
|
|
142
|
+
// Grammar diagnostics (LanguageTool host integration)
|
|
143
|
+
import {
|
|
144
|
+
createLanguageToolDiagnosticsExtension,
|
|
145
|
+
collectVisibleProseFragments,
|
|
146
|
+
forceLanguageToolRefresh,
|
|
147
|
+
refreshLanguageToolDiagnostics,
|
|
148
|
+
applyFirstLanguageToolSuggestion,
|
|
149
|
+
getLanguageToolSuggestionMenu,
|
|
150
|
+
applyLanguageToolSuggestionAt,
|
|
151
|
+
} from './grammar.js';
|
|
152
|
+
|
|
115
153
|
// Wiki-link completion ([[internal-links]])
|
|
116
154
|
import {
|
|
117
155
|
projectFilesFacet,
|
|
@@ -157,6 +195,14 @@ import {
|
|
|
157
195
|
markdown as markdownRendering,
|
|
158
196
|
markdownRenderer,
|
|
159
197
|
assetResolverFacet, // Facet for resolving asset URLs in Electron/desktop apps
|
|
198
|
+
sourceModeFacet, // Facet to toggle source/raw markdown view
|
|
199
|
+
wysiwygModeFacet, // Facet to toggle protected WYSIWYG rendering
|
|
200
|
+
createWysiwygExtensions,
|
|
201
|
+
createInlineEditingExtensions,
|
|
202
|
+
toggleInlineFormat,
|
|
203
|
+
toggleInlineMark,
|
|
204
|
+
getSelectionFormattingState,
|
|
205
|
+
findFencedCodeAt,
|
|
160
206
|
blockDecorations, // StateField for tables, display math (multi-line replace)
|
|
161
207
|
lineHeightTracker, // ViewPlugin for accurate line height tracking
|
|
162
208
|
markdownStyles,
|
|
@@ -174,6 +220,9 @@ import {
|
|
|
174
220
|
AlertTitleWidget,
|
|
175
221
|
} from './markdown/index.js';
|
|
176
222
|
|
|
223
|
+
// Page view pagination (spacer-based page breaks)
|
|
224
|
+
import { pageViewPagination } from './page-view-pagination.js';
|
|
225
|
+
|
|
177
226
|
// Awareness system
|
|
178
227
|
import {
|
|
179
228
|
createAwareness,
|
|
@@ -681,6 +730,12 @@ const codeBlockBackground = ViewPlugin.fromClass(class {
|
|
|
681
730
|
const firstLine = view.state.doc.lineAt(from);
|
|
682
731
|
const lastLine = view.state.doc.lineAt(to);
|
|
683
732
|
|
|
733
|
+
// Extract language from the opening fence line
|
|
734
|
+
const fenceText = firstLine.text;
|
|
735
|
+
const langMatch = fenceText.match(/^(\s*`{3,}|~{3,})\s*(\S*)/);
|
|
736
|
+
const rawLang = langMatch?.[2] || '';
|
|
737
|
+
const language = normalizeCodeLanguage(rawLang);
|
|
738
|
+
|
|
684
739
|
// Iterate through each line in the code block
|
|
685
740
|
for (let pos = from; pos < to;) {
|
|
686
741
|
const line = view.state.doc.lineAt(pos);
|
|
@@ -690,12 +745,18 @@ const codeBlockBackground = ViewPlugin.fromClass(class {
|
|
|
690
745
|
if (isFirstLine || isLastLine) {
|
|
691
746
|
// Fence lines - subtle styling
|
|
692
747
|
decorations.push(
|
|
693
|
-
Decoration.line({
|
|
748
|
+
Decoration.line({
|
|
749
|
+
class: 'cm-codeblock-fence',
|
|
750
|
+
attributes: language ? { 'data-lang': language } : undefined,
|
|
751
|
+
}).range(line.from)
|
|
694
752
|
);
|
|
695
753
|
} else {
|
|
696
754
|
// Content lines - normal code block styling
|
|
697
755
|
decorations.push(
|
|
698
|
-
Decoration.line({
|
|
756
|
+
Decoration.line({
|
|
757
|
+
class: 'cm-codeblock-line',
|
|
758
|
+
attributes: language ? { 'data-lang': language } : undefined,
|
|
759
|
+
}).range(line.from)
|
|
699
760
|
);
|
|
700
761
|
}
|
|
701
762
|
pos = line.to + 1;
|
|
@@ -754,6 +815,99 @@ const codeBlockStyles = EditorView.theme({
|
|
|
754
815
|
});
|
|
755
816
|
// #endregion CODE_BLOCK_BACKGROUND
|
|
756
817
|
|
|
818
|
+
// #region INVISIBLE_CHARACTERS
|
|
819
|
+
/**
|
|
820
|
+
* Extension that shows newline markers (¶) at the end of each line.
|
|
821
|
+
* Used in combination with CM6's built-in highlightWhitespace() for
|
|
822
|
+
* spaces and tabs. Together they provide full invisible character rendering.
|
|
823
|
+
*/
|
|
824
|
+
class NewlineMarkerWidget extends WidgetType {
|
|
825
|
+
eq() { return true; }
|
|
826
|
+
toDOM() {
|
|
827
|
+
const span = document.createElement('span');
|
|
828
|
+
span.className = 'cm-newline-marker';
|
|
829
|
+
span.textContent = '¶';
|
|
830
|
+
span.setAttribute('aria-hidden', 'true');
|
|
831
|
+
return span;
|
|
832
|
+
}
|
|
833
|
+
ignoreEvent() { return true; }
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const newlineMarkerPlugin = ViewPlugin.fromClass(
|
|
837
|
+
class {
|
|
838
|
+
constructor(view) {
|
|
839
|
+
this.decorations = this.buildDecorations(view);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
update(update) {
|
|
843
|
+
if (update.docChanged || update.viewportChanged) {
|
|
844
|
+
this.decorations = this.buildDecorations(update.view);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
buildDecorations(view) {
|
|
849
|
+
const decorations = [];
|
|
850
|
+
const doc = view.state.doc;
|
|
851
|
+
|
|
852
|
+
for (let i = doc.lineAt(view.viewport.from).number; i <= doc.lineAt(view.viewport.to).number; i++) {
|
|
853
|
+
const line = doc.line(i);
|
|
854
|
+
// Add newline marker at the end of each line (except the last line if it has no trailing newline)
|
|
855
|
+
if (i < doc.lines) {
|
|
856
|
+
decorations.push(
|
|
857
|
+
Decoration.widget({
|
|
858
|
+
widget: new NewlineMarkerWidget(),
|
|
859
|
+
side: 1, // After the line content
|
|
860
|
+
}).range(line.to)
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return Decoration.set(decorations, true);
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
{ decorations: (v) => v.decorations }
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Styles for invisible character markers.
|
|
873
|
+
* Overrides CM6's default whitespace styling with more visible symbols.
|
|
874
|
+
*/
|
|
875
|
+
const invisibleCharStyles = EditorView.theme({
|
|
876
|
+
// Newline markers (¶)
|
|
877
|
+
'.cm-newline-marker': {
|
|
878
|
+
color: 'var(--md-marker-color, #aaa)',
|
|
879
|
+
opacity: '0.5',
|
|
880
|
+
fontSize: '0.8em',
|
|
881
|
+
userSelect: 'none',
|
|
882
|
+
pointerEvents: 'none',
|
|
883
|
+
},
|
|
884
|
+
// Override CM6's default space dots - make them subtler
|
|
885
|
+
'.cm-highlightSpace': {
|
|
886
|
+
backgroundImage: 'radial-gradient(circle at 50% 55%, var(--md-marker-color, #aaa) 20%, transparent 5%)',
|
|
887
|
+
opacity: '0.4',
|
|
888
|
+
},
|
|
889
|
+
// Override CM6's tab arrows
|
|
890
|
+
'.cm-highlightTab': {
|
|
891
|
+
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23aaa" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
|
|
892
|
+
opacity: '0.5',
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Create the invisibles extension bundle.
|
|
898
|
+
* Includes whitespace highlighting + newline markers + styles.
|
|
899
|
+
*
|
|
900
|
+
* @returns {import('@codemirror/state').Extension}
|
|
901
|
+
*/
|
|
902
|
+
function createInvisiblesExtension() {
|
|
903
|
+
return [
|
|
904
|
+
highlightWhitespace(),
|
|
905
|
+
newlineMarkerPlugin,
|
|
906
|
+
invisibleCharStyles,
|
|
907
|
+
];
|
|
908
|
+
}
|
|
909
|
+
// #endregion INVISIBLE_CHARACTERS
|
|
910
|
+
|
|
757
911
|
// #region WRITER
|
|
758
912
|
/**
|
|
759
913
|
* Writer for streaming content into the editor.
|
|
@@ -833,6 +987,413 @@ function findInitialCursorPosition(content) {
|
|
|
833
987
|
|
|
834
988
|
return 0; // fallback to start
|
|
835
989
|
}
|
|
990
|
+
|
|
991
|
+
function wrapSelectionsWith(view, open, close = open, userEvent = 'input.wysiwyg.format') {
|
|
992
|
+
const state = view.state;
|
|
993
|
+
const changes = [];
|
|
994
|
+
const ranges = [];
|
|
995
|
+
let delta = 0;
|
|
996
|
+
|
|
997
|
+
for (const range of state.selection.ranges) {
|
|
998
|
+
if (range.empty) {
|
|
999
|
+
changes.push({ from: range.from, insert: open + close });
|
|
1000
|
+
const pos = range.from + open.length + delta;
|
|
1001
|
+
ranges.push({ anchor: pos, head: pos });
|
|
1002
|
+
delta += open.length + close.length;
|
|
1003
|
+
} else {
|
|
1004
|
+
changes.push({ from: range.from, insert: open });
|
|
1005
|
+
changes.push({ from: range.to, insert: close });
|
|
1006
|
+
ranges.push({ anchor: range.from + open.length + delta, head: range.to + open.length + delta });
|
|
1007
|
+
delta += open.length + close.length;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
view.dispatch({
|
|
1012
|
+
changes,
|
|
1013
|
+
selection: { ranges, mainIndex: state.selection.mainIndex },
|
|
1014
|
+
userEvent,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function currentLineStructuralPrefix(lineText) {
|
|
1019
|
+
const heading = lineText.match(/^\s{0,3}(#{1,6})\s+/);
|
|
1020
|
+
if (heading) return heading[0];
|
|
1021
|
+
const quote = lineText.match(/^(\s*>\s?)+/);
|
|
1022
|
+
if (quote) return quote[0];
|
|
1023
|
+
const list = lineText.match(/^(\s*)(?:[-+*]|\d+\.)\s+/);
|
|
1024
|
+
if (list) return list[0];
|
|
1025
|
+
return '';
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function getCurrentBlockTypeInfo(view) {
|
|
1029
|
+
const pos = view.state.selection.main.head;
|
|
1030
|
+
const line = view.state.doc.lineAt(pos);
|
|
1031
|
+
const text = line.text;
|
|
1032
|
+
|
|
1033
|
+
// ── Check if inside a fenced code block ──
|
|
1034
|
+
const tree = syntaxTree(view.state);
|
|
1035
|
+
let fencedCode = null;
|
|
1036
|
+
tree.iterate({
|
|
1037
|
+
from: Math.max(0, pos - 1),
|
|
1038
|
+
to: pos + 1,
|
|
1039
|
+
enter: (node) => {
|
|
1040
|
+
if (node.name === 'FencedCode' && node.from <= pos && node.to >= pos) {
|
|
1041
|
+
fencedCode = node;
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
if (fencedCode) {
|
|
1047
|
+
// Extract language from the opening fence line (```python, ```r, etc.)
|
|
1048
|
+
const fenceLine = view.state.doc.lineAt(fencedCode.from);
|
|
1049
|
+
const fenceText = fenceLine.text;
|
|
1050
|
+
const langMatch = fenceText.match(/^(\s*`{3,}|~{3,})\s*(\S*)/);
|
|
1051
|
+
const rawLang = langMatch?.[2] || '';
|
|
1052
|
+
const language = normalizeCodeLanguage(rawLang);
|
|
1053
|
+
|
|
1054
|
+
// Determine if cursor is on the fence line itself or inside code content
|
|
1055
|
+
const isOnFence = line.number === fenceLine.number ||
|
|
1056
|
+
line.number === view.state.doc.lineAt(fencedCode.to).number;
|
|
1057
|
+
|
|
1058
|
+
// Detect the syntax token under the cursor
|
|
1059
|
+
const syntaxToken = isOnFence ? null : getSyntaxTokenAtPos(view, pos);
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
type: 'codeblock',
|
|
1063
|
+
level: 0,
|
|
1064
|
+
label: language ? `codeblock-${language}` : 'codeblock',
|
|
1065
|
+
language,
|
|
1066
|
+
isOnFence,
|
|
1067
|
+
syntaxToken,
|
|
1068
|
+
fenceFrom: fencedCode.from,
|
|
1069
|
+
fenceTo: fencedCode.to,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const heading = text.match(/^\s{0,3}(#{1,6})\s+/);
|
|
1074
|
+
if (heading) {
|
|
1075
|
+
return { type: 'heading', level: heading[1].length, label: `h${heading[1].length}` };
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (/^(\s*>\s?)+/.test(text)) {
|
|
1079
|
+
return { type: 'blockquote', level: 1, label: 'blockquote' };
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (/^(\s*)(?:[-+*])\s+/.test(text)) {
|
|
1083
|
+
return { type: 'unordered-list', level: 1, label: 'unordered-list' };
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (/^(\s*)(?:\d+\.)\s+/.test(text)) {
|
|
1087
|
+
return { type: 'ordered-list', level: 1, label: 'ordered-list' };
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
return { type: 'paragraph', level: 0, label: 'paragraph' };
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Normalize code language aliases to canonical names matching template keys.
|
|
1095
|
+
*/
|
|
1096
|
+
function normalizeCodeLanguage(raw) {
|
|
1097
|
+
if (!raw) return '';
|
|
1098
|
+
const lang = raw.toLowerCase().trim();
|
|
1099
|
+
const map = {
|
|
1100
|
+
'js': 'javascript', 'node': 'javascript', 'ecmascript': 'javascript',
|
|
1101
|
+
'ts': 'typescript',
|
|
1102
|
+
'py': 'python', 'python3': 'python',
|
|
1103
|
+
'rb': 'ruby',
|
|
1104
|
+
'rs': 'rust',
|
|
1105
|
+
'sh': 'shell', 'bash': 'shell', 'zsh': 'shell', 'fish': 'shell',
|
|
1106
|
+
'ps1': 'powershell', 'pwsh': 'powershell',
|
|
1107
|
+
'yml': 'yaml',
|
|
1108
|
+
'htm': 'html',
|
|
1109
|
+
'c': 'cpp', 'c++': 'cpp', 'cxx': 'cpp', 'h': 'cpp', 'hpp': 'cpp',
|
|
1110
|
+
'golang': 'go',
|
|
1111
|
+
'jl': 'julia',
|
|
1112
|
+
'rlang': 'r',
|
|
1113
|
+
'mysql': 'sql', 'postgresql': 'sql', 'postgres': 'sql', 'sqlite': 'sql',
|
|
1114
|
+
'jsonc': 'json',
|
|
1115
|
+
'jsx': 'javascript', 'tsx': 'typescript',
|
|
1116
|
+
'xml': 'html', 'svg': 'html',
|
|
1117
|
+
};
|
|
1118
|
+
return map[lang] || lang;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Get the semantic syntax token type at a position within a code block.
|
|
1123
|
+
* Maps CodeMirror/Lezer highlight tags to our template token names.
|
|
1124
|
+
*
|
|
1125
|
+
* @param {EditorView} view
|
|
1126
|
+
* @param {number} pos
|
|
1127
|
+
* @returns {string|null} Token name like 'keyword', 'string', 'comment', etc.
|
|
1128
|
+
*/
|
|
1129
|
+
function getSyntaxTokenAtPos(view, pos) {
|
|
1130
|
+
const tree = syntaxTree(view.state);
|
|
1131
|
+
let bestNode = null;
|
|
1132
|
+
let bestSize = Infinity;
|
|
1133
|
+
|
|
1134
|
+
// Find the most specific (smallest) node at pos
|
|
1135
|
+
tree.iterate({
|
|
1136
|
+
from: pos,
|
|
1137
|
+
to: pos + 1,
|
|
1138
|
+
enter: (node) => {
|
|
1139
|
+
const size = node.to - node.from;
|
|
1140
|
+
if (size < bestSize && node.from <= pos && node.to > pos) {
|
|
1141
|
+
bestNode = node;
|
|
1142
|
+
bestSize = size;
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
if (!bestNode) return null;
|
|
1148
|
+
const name = bestNode.name;
|
|
1149
|
+
|
|
1150
|
+
// Map Lezer tree node names to our semantic token names.
|
|
1151
|
+
// These are the node names from @lezer/highlight and language parsers.
|
|
1152
|
+
const tokenMap = {
|
|
1153
|
+
// Keywords
|
|
1154
|
+
'Keyword': 'keyword',
|
|
1155
|
+
'keyword': 'keyword',
|
|
1156
|
+
'ControlKeyword': 'controlKeyword',
|
|
1157
|
+
'controlKeyword': 'controlKeyword',
|
|
1158
|
+
'for': 'controlKeyword',
|
|
1159
|
+
'if': 'controlKeyword',
|
|
1160
|
+
'else': 'controlKeyword',
|
|
1161
|
+
'while': 'controlKeyword',
|
|
1162
|
+
'return': 'keyword',
|
|
1163
|
+
'def': 'keyword',
|
|
1164
|
+
'class': 'keyword',
|
|
1165
|
+
'import': 'keyword',
|
|
1166
|
+
'from': 'keyword',
|
|
1167
|
+
'as': 'keyword',
|
|
1168
|
+
'in': 'keyword',
|
|
1169
|
+
'not': 'keyword',
|
|
1170
|
+
'and': 'keyword',
|
|
1171
|
+
'or': 'keyword',
|
|
1172
|
+
'is': 'keyword',
|
|
1173
|
+
'with': 'keyword',
|
|
1174
|
+
'try': 'controlKeyword',
|
|
1175
|
+
'except': 'controlKeyword',
|
|
1176
|
+
'finally': 'controlKeyword',
|
|
1177
|
+
'raise': 'keyword',
|
|
1178
|
+
'yield': 'keyword',
|
|
1179
|
+
'lambda': 'keyword',
|
|
1180
|
+
'pass': 'keyword',
|
|
1181
|
+
'break': 'controlKeyword',
|
|
1182
|
+
'continue': 'controlKeyword',
|
|
1183
|
+
'del': 'keyword',
|
|
1184
|
+
'global': 'keyword',
|
|
1185
|
+
'nonlocal': 'keyword',
|
|
1186
|
+
'assert': 'keyword',
|
|
1187
|
+
'async': 'keyword',
|
|
1188
|
+
'await': 'keyword',
|
|
1189
|
+
'let': 'keyword',
|
|
1190
|
+
'const': 'keyword',
|
|
1191
|
+
'var': 'keyword',
|
|
1192
|
+
'function': 'keyword',
|
|
1193
|
+
'switch': 'controlKeyword',
|
|
1194
|
+
'case': 'controlKeyword',
|
|
1195
|
+
'default': 'controlKeyword',
|
|
1196
|
+
'do': 'controlKeyword',
|
|
1197
|
+
'throw': 'keyword',
|
|
1198
|
+
'catch': 'controlKeyword',
|
|
1199
|
+
'new': 'keyword',
|
|
1200
|
+
'this': 'keyword',
|
|
1201
|
+
'super': 'keyword',
|
|
1202
|
+
'extends': 'keyword',
|
|
1203
|
+
'implements': 'keyword',
|
|
1204
|
+
'interface': 'keyword',
|
|
1205
|
+
'enum': 'keyword',
|
|
1206
|
+
'export': 'keyword',
|
|
1207
|
+
'typeof': 'keyword',
|
|
1208
|
+
'instanceof': 'keyword',
|
|
1209
|
+
'void': 'keyword',
|
|
1210
|
+
'delete': 'keyword',
|
|
1211
|
+
|
|
1212
|
+
// Strings
|
|
1213
|
+
'String': 'string',
|
|
1214
|
+
'string': 'string',
|
|
1215
|
+
'TemplateString': 'string',
|
|
1216
|
+
'FormatString': 'string',
|
|
1217
|
+
'Character': 'string',
|
|
1218
|
+
|
|
1219
|
+
// Numbers
|
|
1220
|
+
'Number': 'number',
|
|
1221
|
+
'number': 'number',
|
|
1222
|
+
'Integer': 'number',
|
|
1223
|
+
'Float': 'number',
|
|
1224
|
+
|
|
1225
|
+
// Comments
|
|
1226
|
+
'Comment': 'comment',
|
|
1227
|
+
'comment': 'comment',
|
|
1228
|
+
'LineComment': 'comment',
|
|
1229
|
+
'BlockComment': 'comment',
|
|
1230
|
+
|
|
1231
|
+
// Functions
|
|
1232
|
+
'FunctionDefinition': 'function',
|
|
1233
|
+
'FunctionDeclaration': 'function',
|
|
1234
|
+
'CallExpression': 'function',
|
|
1235
|
+
|
|
1236
|
+
// Variables
|
|
1237
|
+
'VariableName': 'variable',
|
|
1238
|
+
'VariableDefinition': 'variable',
|
|
1239
|
+
|
|
1240
|
+
// Types
|
|
1241
|
+
'TypeName': 'type',
|
|
1242
|
+
'TypeDefinition': 'type',
|
|
1243
|
+
'ClassName': 'type',
|
|
1244
|
+
'ClassDefinition': 'type',
|
|
1245
|
+
|
|
1246
|
+
// Operators
|
|
1247
|
+
'ArithOp': 'operator',
|
|
1248
|
+
'LogicOp': 'operator',
|
|
1249
|
+
'BitOp': 'operator',
|
|
1250
|
+
'CompareOp': 'operator',
|
|
1251
|
+
'AssignOp': 'operator',
|
|
1252
|
+
'Equals': 'operator',
|
|
1253
|
+
|
|
1254
|
+
// Punctuation
|
|
1255
|
+
'Punctuation': 'punctuation',
|
|
1256
|
+
'(': 'punctuation',
|
|
1257
|
+
')': 'punctuation',
|
|
1258
|
+
'[': 'punctuation',
|
|
1259
|
+
']': 'punctuation',
|
|
1260
|
+
'{': 'punctuation',
|
|
1261
|
+
'}': 'punctuation',
|
|
1262
|
+
'.': 'punctuation',
|
|
1263
|
+
',': 'punctuation',
|
|
1264
|
+
';': 'punctuation',
|
|
1265
|
+
':': 'punctuation',
|
|
1266
|
+
|
|
1267
|
+
// Properties
|
|
1268
|
+
'PropertyName': 'property',
|
|
1269
|
+
'PropertyDefinition': 'property',
|
|
1270
|
+
|
|
1271
|
+
// Constants
|
|
1272
|
+
'BooleanLiteral': 'constant',
|
|
1273
|
+
'Boolean': 'constant',
|
|
1274
|
+
'True': 'constant',
|
|
1275
|
+
'False': 'constant',
|
|
1276
|
+
'None': 'constant',
|
|
1277
|
+
'Null': 'constant',
|
|
1278
|
+
'null': 'constant',
|
|
1279
|
+
'undefined': 'constant',
|
|
1280
|
+
|
|
1281
|
+
// Regex
|
|
1282
|
+
'RegExp': 'regexp',
|
|
1283
|
+
|
|
1284
|
+
// Escape
|
|
1285
|
+
'Escape': 'escape',
|
|
1286
|
+
'EscapeSequence': 'escape',
|
|
1287
|
+
|
|
1288
|
+
// Tags (HTML/XML)
|
|
1289
|
+
'TagName': 'tag',
|
|
1290
|
+
'StartTag': 'tag',
|
|
1291
|
+
'EndTag': 'tag',
|
|
1292
|
+
'SelfClosingTag': 'tag',
|
|
1293
|
+
|
|
1294
|
+
// Attributes
|
|
1295
|
+
'AttributeName': 'attribute',
|
|
1296
|
+
'AttributeValue': 'attributeValue',
|
|
1297
|
+
|
|
1298
|
+
// Meta / decorators
|
|
1299
|
+
'Decorator': 'meta',
|
|
1300
|
+
'Annotation': 'meta',
|
|
1301
|
+
'Meta': 'meta',
|
|
1302
|
+
'ProcessingInstruction': 'meta',
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// Direct match
|
|
1306
|
+
if (tokenMap[name]) return tokenMap[name];
|
|
1307
|
+
|
|
1308
|
+
// Try resolving from the tree directly and walking up parent nodes
|
|
1309
|
+
try {
|
|
1310
|
+
let node = tree.resolveInner(pos, 1);
|
|
1311
|
+
let depth = 0;
|
|
1312
|
+
while (node && depth < 8) {
|
|
1313
|
+
if (tokenMap[node.name]) return tokenMap[node.name];
|
|
1314
|
+
node = node.parent;
|
|
1315
|
+
depth++;
|
|
1316
|
+
}
|
|
1317
|
+
} catch (e) { /* ignore tree resolution errors */ }
|
|
1318
|
+
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function getSelectionFormattingInfo(view) {
|
|
1323
|
+
return getSelectionFormattingState(view);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function setCurrentBlockType(view, type, options = {}) {
|
|
1327
|
+
const state = view.state;
|
|
1328
|
+
const pos = state.selection.main.head;
|
|
1329
|
+
const line = state.doc.lineAt(pos);
|
|
1330
|
+
const prefix = currentLineStructuralPrefix(line.text);
|
|
1331
|
+
const contentStart = line.from + prefix.length;
|
|
1332
|
+
let newPrefix = '';
|
|
1333
|
+
|
|
1334
|
+
if (type === 'paragraph') {
|
|
1335
|
+
newPrefix = '';
|
|
1336
|
+
} else if (type === 'heading') {
|
|
1337
|
+
const level = Math.max(1, Math.min(6, Number(options.level) || 1));
|
|
1338
|
+
newPrefix = '#'.repeat(level) + ' ';
|
|
1339
|
+
} else if (type === 'blockquote') {
|
|
1340
|
+
newPrefix = '> ';
|
|
1341
|
+
} else if (type === 'unordered-list') {
|
|
1342
|
+
newPrefix = '- ';
|
|
1343
|
+
} else if (type === 'ordered-list') {
|
|
1344
|
+
newPrefix = '1. ';
|
|
1345
|
+
} else {
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
view.dispatch({
|
|
1350
|
+
changes: { from: line.from, to: contentStart, insert: newPrefix },
|
|
1351
|
+
selection: { anchor: line.from + newPrefix.length + Math.max(0, pos - contentStart) },
|
|
1352
|
+
userEvent: 'input.wysiwyg.blocktype',
|
|
1353
|
+
});
|
|
1354
|
+
return true;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function insertLinkAtSelection(view, url, text = null) {
|
|
1358
|
+
const state = view.state;
|
|
1359
|
+
const range = state.selection.main;
|
|
1360
|
+
const selected = state.sliceDoc(range.from, range.to);
|
|
1361
|
+
const label = text ?? selected ?? 'link';
|
|
1362
|
+
const link = `[${label}](${url})`;
|
|
1363
|
+
const insertFrom = range.from;
|
|
1364
|
+
const insertTo = range.to;
|
|
1365
|
+
view.dispatch({
|
|
1366
|
+
changes: { from: insertFrom, to: insertTo, insert: link },
|
|
1367
|
+
selection: { anchor: insertFrom + 1, head: insertFrom + 1 + label.length },
|
|
1368
|
+
userEvent: 'input.wysiwyg.link',
|
|
1369
|
+
});
|
|
1370
|
+
return true;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function insertImageAtSelection(view, url, alt = 'image') {
|
|
1374
|
+
const pos = view.state.selection.main.head;
|
|
1375
|
+
const image = ``;
|
|
1376
|
+
view.dispatch({
|
|
1377
|
+
changes: { from: pos, insert: image },
|
|
1378
|
+
selection: { anchor: pos + 2, head: pos + 2 + alt.length },
|
|
1379
|
+
userEvent: 'input.wysiwyg.image',
|
|
1380
|
+
});
|
|
1381
|
+
return true;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function insertCodeBlockAtCursor(view, language = '') {
|
|
1385
|
+
const pos = view.state.selection.main.head;
|
|
1386
|
+
const lang = language ? String(language).trim() : '';
|
|
1387
|
+
const prefix = pos > 0 && view.state.sliceDoc(pos - 1, pos) !== '\n' ? '\n' : '';
|
|
1388
|
+
const block = `${prefix}\`\`\`${lang}\n\n\`\`\``;
|
|
1389
|
+
const cursor = pos + prefix.length + 3 + lang.length + 1;
|
|
1390
|
+
view.dispatch({
|
|
1391
|
+
changes: { from: pos, insert: block },
|
|
1392
|
+
selection: { anchor: cursor },
|
|
1393
|
+
userEvent: 'input.wysiwyg.codeblock',
|
|
1394
|
+
});
|
|
1395
|
+
return true;
|
|
1396
|
+
}
|
|
836
1397
|
// #endregion INITIAL_CURSOR
|
|
837
1398
|
|
|
838
1399
|
// #region CREATE
|
|
@@ -869,6 +1430,7 @@ function create(target, options = {}) {
|
|
|
869
1430
|
const dark = config.appearance.dark;
|
|
870
1431
|
const placeholderText = config.appearance.placeholder;
|
|
871
1432
|
const readonly = config.appearance.readonly;
|
|
1433
|
+
const spellcheck = config.appearance.spellcheck;
|
|
872
1434
|
const userName = config.user.name;
|
|
873
1435
|
const userColor = config.user.color;
|
|
874
1436
|
const userType = config.user.type;
|
|
@@ -883,6 +1445,11 @@ function create(target, options = {}) {
|
|
|
883
1445
|
awarenessUI = true,
|
|
884
1446
|
} = options;
|
|
885
1447
|
|
|
1448
|
+
const linkedTableHostContext = {
|
|
1449
|
+
projectRoot: options.projectRoot || null,
|
|
1450
|
+
documentPath: options.documentPath || null,
|
|
1451
|
+
};
|
|
1452
|
+
|
|
886
1453
|
// Runtimes from normalized config
|
|
887
1454
|
const runtimes = {};
|
|
888
1455
|
for (const [name, rtConfig] of Object.entries(config.runtimes)) {
|
|
@@ -935,10 +1502,16 @@ function create(target, options = {}) {
|
|
|
935
1502
|
|
|
936
1503
|
// Always read initial content from Yjs (source of truth)
|
|
937
1504
|
const initialContent = yText.toString();
|
|
1505
|
+
const initialDocumentTemplate = normalizeDocumentTemplate(options.documentTemplate || defaultDocumentTemplate);
|
|
938
1506
|
const themeCompartment = new Compartment();
|
|
1507
|
+
const documentTemplateCompartment = new Compartment();
|
|
939
1508
|
const readonlyCompartment = new Compartment();
|
|
940
1509
|
const keymapCompartment = new Compartment();
|
|
941
1510
|
const projectFilesCompartment = new Compartment();
|
|
1511
|
+
const sectionControlsCompartment = new Compartment();
|
|
1512
|
+
const sourceModeCompartment = new Compartment();
|
|
1513
|
+
const wysiwygModeCompartment = new Compartment();
|
|
1514
|
+
const invisiblesCompartment = new Compartment();
|
|
942
1515
|
|
|
943
1516
|
// Create UndoManager for undo/redo tracking
|
|
944
1517
|
// We create it ourselves so we can listen to stack changes
|
|
@@ -977,7 +1550,7 @@ function create(target, options = {}) {
|
|
|
977
1550
|
// Use explicit theme if set, otherwise auto-select based on dark mode
|
|
978
1551
|
const resolveThemeName = (theme, isDarkMode) => {
|
|
979
1552
|
if (theme) return theme;
|
|
980
|
-
return
|
|
1553
|
+
return 'plain-light';
|
|
981
1554
|
};
|
|
982
1555
|
const initialThemeName = resolveThemeName(config.appearance?.theme, isDark);
|
|
983
1556
|
|
|
@@ -1003,8 +1576,11 @@ function create(target, options = {}) {
|
|
|
1003
1576
|
documentTheme,
|
|
1004
1577
|
codeBlockBackground, // Add gray background to code blocks
|
|
1005
1578
|
codeBlockStyles,
|
|
1579
|
+
// Spellcheck: enable browser-native spellcheck on prose, disable in code
|
|
1580
|
+
...(spellcheck !== false ? createSpellcheckExtensions() : []),
|
|
1006
1581
|
EditorView.lineWrapping, // Always wrap markdown text
|
|
1007
1582
|
themeCompartment.of(initialCMTheme),
|
|
1583
|
+
documentTemplateCompartment.of(createDocumentTemplateExtension(initialDocumentTemplate)),
|
|
1008
1584
|
readonlyCompartment.of(readonly ? EditorState.readOnly.of(true) : []),
|
|
1009
1585
|
placeholderText ? placeholder(placeholderText) : [],
|
|
1010
1586
|
// Yjs collaboration - y-codemirror.next handles sync and undo
|
|
@@ -1016,13 +1592,26 @@ function create(target, options = {}) {
|
|
|
1016
1592
|
// Initially empty, configured after api is created
|
|
1017
1593
|
keymapCompartment.of([]),
|
|
1018
1594
|
outputWidgetPlugin, // ANSI output rendering
|
|
1595
|
+
...createInlineEditingExtensions(),
|
|
1019
1596
|
lineHeightTracker, // ViewPlugin: tracks line height for spacer calculations
|
|
1597
|
+
linkedTableMarkdownState,
|
|
1020
1598
|
blockDecorations, // StateField for tables, display math (multi-line)
|
|
1021
1599
|
markdownRenderer, // ViewPlugin for everything else (inline)
|
|
1600
|
+
pageViewPagination, // ViewPlugin: page-view spacers at page boundaries
|
|
1601
|
+
...createWysiwygExtensions(),
|
|
1602
|
+
...commentSyntaxModule.createCommentSyntaxExtension(),
|
|
1022
1603
|
// Wiki-link completion - just the facet for project files
|
|
1023
1604
|
// The actual completion is provided by runtime-lsp (via additionalSources)
|
|
1024
1605
|
// or by a standalone autocompletion added below if no runtime providers exist
|
|
1025
1606
|
projectFilesCompartment.of(projectFilesFacet.of([])),
|
|
1607
|
+
// Section controls are configured after API creation
|
|
1608
|
+
sectionControlsCompartment.of([]),
|
|
1609
|
+
// Source mode (show all raw markdown syntax)
|
|
1610
|
+
sourceModeCompartment.of(sourceModeFacet.of(false)),
|
|
1611
|
+
// WYSIWYG mode (fully rendered, syntax-protected editing)
|
|
1612
|
+
wysiwygModeCompartment.of(wysiwygModeFacet.of(false)),
|
|
1613
|
+
// Invisible characters (whitespace visualization)
|
|
1614
|
+
invisiblesCompartment.of([]),
|
|
1026
1615
|
];
|
|
1027
1616
|
|
|
1028
1617
|
// Inject markdown styles
|
|
@@ -1092,6 +1681,7 @@ function create(target, options = {}) {
|
|
|
1092
1681
|
|
|
1093
1682
|
// Event handlers
|
|
1094
1683
|
const changeHandlers = [];
|
|
1684
|
+
const selectionHandlers = [];
|
|
1095
1685
|
const saveHandlers = [];
|
|
1096
1686
|
const frontmatterTitleCommitHandlers = [];
|
|
1097
1687
|
const viewSourceHandlers = [];
|
|
@@ -1219,6 +1809,12 @@ function create(target, options = {}) {
|
|
|
1219
1809
|
});
|
|
1220
1810
|
runtimeLspExtensions.push(completionExt);
|
|
1221
1811
|
|
|
1812
|
+
const signatureHelpExt = createRuntimeSignatureHelpExtension({
|
|
1813
|
+
providers: runtimeLspProviders,
|
|
1814
|
+
getContent: () => view.state.doc.toString(),
|
|
1815
|
+
});
|
|
1816
|
+
runtimeLspExtensions.push(signatureHelpExt);
|
|
1817
|
+
|
|
1222
1818
|
// Add extensions to the view
|
|
1223
1819
|
view.dispatch({
|
|
1224
1820
|
effects: StateEffect.appendConfig.of(runtimeLspExtensions),
|
|
@@ -1285,6 +1881,7 @@ function create(target, options = {}) {
|
|
|
1285
1881
|
// Runtime
|
|
1286
1882
|
registry,
|
|
1287
1883
|
execution: null, // Set below
|
|
1884
|
+
linkedTables: null, // Set below
|
|
1288
1885
|
|
|
1289
1886
|
// Runtime LSP (hover, completions, variables)
|
|
1290
1887
|
runtimeLspProviders,
|
|
@@ -1310,6 +1907,36 @@ function create(target, options = {}) {
|
|
|
1310
1907
|
return yText;
|
|
1311
1908
|
},
|
|
1312
1909
|
|
|
1910
|
+
getLinkedTableHostContext() {
|
|
1911
|
+
return {
|
|
1912
|
+
...linkedTableHostContext,
|
|
1913
|
+
...(this.linkedTables?.getHostContext?.() || {}),
|
|
1914
|
+
};
|
|
1915
|
+
},
|
|
1916
|
+
|
|
1917
|
+
setLinkedTableHostContext(context = {}) {
|
|
1918
|
+
if (context.projectRoot !== undefined) linkedTableHostContext.projectRoot = context.projectRoot;
|
|
1919
|
+
if (context.documentPath !== undefined) linkedTableHostContext.documentPath = context.documentPath;
|
|
1920
|
+
this.linkedTables?.setHostContext?.(linkedTableHostContext);
|
|
1921
|
+
return this.getLinkedTableHostContext();
|
|
1922
|
+
},
|
|
1923
|
+
|
|
1924
|
+
canImportLinkedTableFromHost(hostApi) {
|
|
1925
|
+
return canImportLinkedTableFromHost(hostApi);
|
|
1926
|
+
},
|
|
1927
|
+
|
|
1928
|
+
insertLinkedTableBlock(blockMarkdown, options = {}) {
|
|
1929
|
+
return insertLinkedTableBlock(this, blockMarkdown, options);
|
|
1930
|
+
},
|
|
1931
|
+
|
|
1932
|
+
async importLinkedTableFromHost(sourceFilePath, options = {}) {
|
|
1933
|
+
return importLinkedTableFromHost(this, {
|
|
1934
|
+
...this.getLinkedTableHostContext(),
|
|
1935
|
+
...options,
|
|
1936
|
+
sourceFilePath,
|
|
1937
|
+
});
|
|
1938
|
+
},
|
|
1939
|
+
|
|
1313
1940
|
setContent(text) {
|
|
1314
1941
|
view.dispatch({
|
|
1315
1942
|
changes: { from: 0, to: view.state.doc.length, insert: text }
|
|
@@ -1335,6 +1962,82 @@ function create(target, options = {}) {
|
|
|
1335
1962
|
});
|
|
1336
1963
|
},
|
|
1337
1964
|
|
|
1965
|
+
// ===========================================================================
|
|
1966
|
+
// WYSIWYG Editing Helpers
|
|
1967
|
+
// ===========================================================================
|
|
1968
|
+
|
|
1969
|
+
toggleBold() {
|
|
1970
|
+
return toggleInlineMark(view, 'bold');
|
|
1971
|
+
},
|
|
1972
|
+
|
|
1973
|
+
toggleItalic() {
|
|
1974
|
+
return toggleInlineMark(view, 'italic');
|
|
1975
|
+
},
|
|
1976
|
+
|
|
1977
|
+
toggleUnderline() {
|
|
1978
|
+
return toggleInlineMark(view, 'underline');
|
|
1979
|
+
},
|
|
1980
|
+
|
|
1981
|
+
toggleStrikethrough() {
|
|
1982
|
+
return toggleInlineMark(view, 'strike');
|
|
1983
|
+
},
|
|
1984
|
+
|
|
1985
|
+
toggleInlineCode() {
|
|
1986
|
+
return toggleInlineMark(view, 'code');
|
|
1987
|
+
},
|
|
1988
|
+
|
|
1989
|
+
setBlockType(type, options = {}) {
|
|
1990
|
+
return setCurrentBlockType(view, type, options);
|
|
1991
|
+
},
|
|
1992
|
+
|
|
1993
|
+
insertLink(url, text = null) {
|
|
1994
|
+
return insertLinkAtSelection(view, url, text);
|
|
1995
|
+
},
|
|
1996
|
+
|
|
1997
|
+
insertCodeBlock(language = '') {
|
|
1998
|
+
return insertCodeBlockAtCursor(view, language);
|
|
1999
|
+
},
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Delete the fenced code block surrounding the cursor.
|
|
2003
|
+
* @returns {boolean} true if a code block was found and deleted
|
|
2004
|
+
*/
|
|
2005
|
+
deleteCodeBlock() {
|
|
2006
|
+
const pos = view.state.selection.main.head;
|
|
2007
|
+
const fence = findFencedCodeAt(view.state, pos);
|
|
2008
|
+
if (!fence) return false;
|
|
2009
|
+
const doc = view.state.doc;
|
|
2010
|
+
let delFrom = fence.from;
|
|
2011
|
+
let delTo = Math.min(fence.to, doc.length);
|
|
2012
|
+
if (delFrom > 0 && doc.sliceString(delFrom - 1, delFrom) === '\n') delFrom--;
|
|
2013
|
+
if (delTo < doc.length && doc.sliceString(delTo, delTo + 1) === '\n') delTo++;
|
|
2014
|
+
view.dispatch({
|
|
2015
|
+
changes: { from: delFrom, to: delTo, insert: '' },
|
|
2016
|
+
userEvent: 'delete.wysiwyg.delete-codeblock',
|
|
2017
|
+
});
|
|
2018
|
+
return true;
|
|
2019
|
+
},
|
|
2020
|
+
|
|
2021
|
+
insertImage(url, alt = 'image') {
|
|
2022
|
+
return insertImageAtSelection(view, url, alt);
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
getCurrentBlockType() {
|
|
2026
|
+
return getCurrentBlockTypeInfo(view);
|
|
2027
|
+
},
|
|
2028
|
+
|
|
2029
|
+
getSelectionFormatting() {
|
|
2030
|
+
return getSelectionFormattingInfo(view);
|
|
2031
|
+
},
|
|
2032
|
+
|
|
2033
|
+
onSelectionChange(callback) {
|
|
2034
|
+
selectionHandlers.push(callback);
|
|
2035
|
+
return () => {
|
|
2036
|
+
const idx = selectionHandlers.indexOf(callback);
|
|
2037
|
+
if (idx >= 0) selectionHandlers.splice(idx, 1);
|
|
2038
|
+
};
|
|
2039
|
+
},
|
|
2040
|
+
|
|
1338
2041
|
// ===========================================================================
|
|
1339
2042
|
// Streaming Writer
|
|
1340
2043
|
// ===========================================================================
|
|
@@ -1381,6 +2084,218 @@ function create(target, options = {}) {
|
|
|
1381
2084
|
return getThemeNames();
|
|
1382
2085
|
},
|
|
1383
2086
|
|
|
2087
|
+
/**
|
|
2088
|
+
* Apply a semantic document template to the editor content surface.
|
|
2089
|
+
* This is separate from the app/editor chrome theme.
|
|
2090
|
+
*
|
|
2091
|
+
* @param {object} template
|
|
2092
|
+
* @returns {object} normalized template
|
|
2093
|
+
*/
|
|
2094
|
+
setDocumentTemplate(template) {
|
|
2095
|
+
const next = normalizeDocumentTemplate(template || defaultDocumentTemplate);
|
|
2096
|
+
this._documentTemplate = cloneDocumentTemplate(next);
|
|
2097
|
+
this._documentTemplateName = next.name || 'Untitled Template';
|
|
2098
|
+
view.dispatch({
|
|
2099
|
+
effects: documentTemplateCompartment.reconfigure(
|
|
2100
|
+
createDocumentTemplateExtension(next)
|
|
2101
|
+
),
|
|
2102
|
+
});
|
|
2103
|
+
return this.getDocumentTemplate();
|
|
2104
|
+
},
|
|
2105
|
+
|
|
2106
|
+
getDocumentTemplate() {
|
|
2107
|
+
return cloneDocumentTemplate(this._documentTemplate || initialDocumentTemplate);
|
|
2108
|
+
},
|
|
2109
|
+
|
|
2110
|
+
getDocumentTemplateName() {
|
|
2111
|
+
return this._documentTemplateName || this._documentTemplate?.name || initialDocumentTemplate.name;
|
|
2112
|
+
},
|
|
2113
|
+
|
|
2114
|
+
getDocumentTemplatePresets() {
|
|
2115
|
+
return documentTemplatePresets.map(cloneDocumentTemplate);
|
|
2116
|
+
},
|
|
2117
|
+
|
|
2118
|
+
compileDocumentTemplate(template = null) {
|
|
2119
|
+
return compileDocumentTemplateCSS(template || this.getDocumentTemplate());
|
|
2120
|
+
},
|
|
2121
|
+
|
|
2122
|
+
serializeDocumentTemplate(template = null, scope = '.markdown-body') {
|
|
2123
|
+
return serializeDocumentTemplateToCss(template || this.getDocumentTemplate(), scope);
|
|
2124
|
+
},
|
|
2125
|
+
|
|
2126
|
+
/**
|
|
2127
|
+
* Serialize the current document template to Pandoc YAML metadata.
|
|
2128
|
+
* @param {object} [template]
|
|
2129
|
+
* @returns {string} YAML string
|
|
2130
|
+
*/
|
|
2131
|
+
serializeDocumentTemplatePandoc(template = null) {
|
|
2132
|
+
return serializeDocumentTemplateToPandocYaml(template || this.getDocumentTemplate());
|
|
2133
|
+
},
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* Serialize the current document template to a LaTeX preamble.
|
|
2137
|
+
* @param {object} [template]
|
|
2138
|
+
* @returns {string} LaTeX commands
|
|
2139
|
+
*/
|
|
2140
|
+
serializeDocumentTemplateLatex(template = null) {
|
|
2141
|
+
return serializeDocumentTemplateToLatexPreamble(template || this.getDocumentTemplate());
|
|
2142
|
+
},
|
|
2143
|
+
|
|
2144
|
+
/**
|
|
2145
|
+
* Serialize the current document template to a standalone HTML wrapper.
|
|
2146
|
+
* @param {object} [template]
|
|
2147
|
+
* @param {object} [options]
|
|
2148
|
+
* @returns {string} HTML document string
|
|
2149
|
+
*/
|
|
2150
|
+
serializeDocumentTemplateHtml(template = null, options = {}) {
|
|
2151
|
+
return serializeDocumentTemplateToHtml(template || this.getDocumentTemplate(), options);
|
|
2152
|
+
},
|
|
2153
|
+
|
|
2154
|
+
/**
|
|
2155
|
+
* Get a Word style mapping for the current document template.
|
|
2156
|
+
* @param {object} [template]
|
|
2157
|
+
* @returns {object}
|
|
2158
|
+
*/
|
|
2159
|
+
getDocumentTemplateWordStyleMap(template = null) {
|
|
2160
|
+
return serializeDocumentTemplateToWordStyleMap(template || this.getDocumentTemplate());
|
|
2161
|
+
},
|
|
2162
|
+
|
|
2163
|
+
/**
|
|
2164
|
+
* Generate a recommended Pandoc command for the current document template.
|
|
2165
|
+
* @param {object} options - { format, input, output, referenceDoc, preambleFile }
|
|
2166
|
+
* @returns {string}
|
|
2167
|
+
*/
|
|
2168
|
+
buildPandocCommand(options = {}) {
|
|
2169
|
+
return buildPandocCommand(this.getDocumentTemplate(), options);
|
|
2170
|
+
},
|
|
2171
|
+
|
|
2172
|
+
bindDocumentTemplate(name) {
|
|
2173
|
+
const result = updateFrontmatterField(this.getContent(), 'template', name);
|
|
2174
|
+
if (!result) return false;
|
|
2175
|
+
view.dispatch({
|
|
2176
|
+
changes: result.changes,
|
|
2177
|
+
userEvent: 'input.document-template-binding',
|
|
2178
|
+
});
|
|
2179
|
+
return true;
|
|
2180
|
+
},
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* Update section controls configuration.
|
|
2184
|
+
* @param {{enabled?: boolean, showAi?: boolean, showFormatting?: boolean, mode?: 'full' | 'dots-hover' | 'dots-click'}} updates
|
|
2185
|
+
*/
|
|
2186
|
+
setSectionControls(updates = {}) {
|
|
2187
|
+
this.config.sectionControls = {
|
|
2188
|
+
...this.config.sectionControls,
|
|
2189
|
+
...updates,
|
|
2190
|
+
};
|
|
2191
|
+
},
|
|
2192
|
+
|
|
2193
|
+
/**
|
|
2194
|
+
* Get current section controls configuration.
|
|
2195
|
+
* @returns {{enabled: boolean, showAi: boolean, showFormatting: boolean, mode: string}}
|
|
2196
|
+
*/
|
|
2197
|
+
getSectionControls() {
|
|
2198
|
+
return { ...(this.config.sectionControls || {}) };
|
|
2199
|
+
},
|
|
2200
|
+
|
|
2201
|
+
// ===========================================================================
|
|
2202
|
+
// Source Mode, WYSIWYG Mode & Invisible Characters
|
|
2203
|
+
// ===========================================================================
|
|
2204
|
+
|
|
2205
|
+
/** @private */
|
|
2206
|
+
_sourceMode: false,
|
|
2207
|
+
/** @private */
|
|
2208
|
+
_wysiwygMode: false,
|
|
2209
|
+
/** @private */
|
|
2210
|
+
_showInvisibles: false,
|
|
2211
|
+
/** @private */
|
|
2212
|
+
_documentTemplate: cloneDocumentTemplate(initialDocumentTemplate),
|
|
2213
|
+
/** @private */
|
|
2214
|
+
_documentTemplateName: initialDocumentTemplate.name || 'Default',
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* Toggle source mode (show all raw markdown syntax).
|
|
2218
|
+
* Mutually exclusive with WYSIWYG mode.
|
|
2219
|
+
*
|
|
2220
|
+
* @param {boolean} [value] - true=on, false=off. Omit to toggle.
|
|
2221
|
+
* @returns {boolean} The new state
|
|
2222
|
+
*/
|
|
2223
|
+
setSourceMode(value) {
|
|
2224
|
+
const newValue = value !== undefined ? !!value : !this._sourceMode;
|
|
2225
|
+
this._sourceMode = newValue;
|
|
2226
|
+
if (newValue) this._wysiwygMode = false;
|
|
2227
|
+
view.dispatch({
|
|
2228
|
+
effects: [
|
|
2229
|
+
sourceModeCompartment.reconfigure(sourceModeFacet.of(newValue)),
|
|
2230
|
+
wysiwygModeCompartment.reconfigure(wysiwygModeFacet.of(false)),
|
|
2231
|
+
],
|
|
2232
|
+
});
|
|
2233
|
+
return newValue;
|
|
2234
|
+
},
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* Get current source mode state.
|
|
2238
|
+
* @returns {boolean}
|
|
2239
|
+
*/
|
|
2240
|
+
getSourceMode() {
|
|
2241
|
+
return this._sourceMode;
|
|
2242
|
+
},
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* Toggle WYSIWYG mode (fully rendered, syntax-protected editing).
|
|
2246
|
+
* Mutually exclusive with source mode.
|
|
2247
|
+
*
|
|
2248
|
+
* @param {boolean} [value] - true=on, false=off. Omit to toggle.
|
|
2249
|
+
* @returns {boolean} The new state
|
|
2250
|
+
*/
|
|
2251
|
+
setWysiwygMode(value) {
|
|
2252
|
+
const newValue = value !== undefined ? !!value : !this._wysiwygMode;
|
|
2253
|
+
this._wysiwygMode = newValue;
|
|
2254
|
+
if (newValue) this._sourceMode = false;
|
|
2255
|
+
view.dispatch({
|
|
2256
|
+
effects: [
|
|
2257
|
+
wysiwygModeCompartment.reconfigure(wysiwygModeFacet.of(newValue)),
|
|
2258
|
+
sourceModeCompartment.reconfigure(sourceModeFacet.of(false)),
|
|
2259
|
+
],
|
|
2260
|
+
});
|
|
2261
|
+
return newValue;
|
|
2262
|
+
},
|
|
2263
|
+
|
|
2264
|
+
/**
|
|
2265
|
+
* Get current WYSIWYG mode state.
|
|
2266
|
+
* @returns {boolean}
|
|
2267
|
+
*/
|
|
2268
|
+
getWysiwygMode() {
|
|
2269
|
+
return this._wysiwygMode;
|
|
2270
|
+
},
|
|
2271
|
+
|
|
2272
|
+
/**
|
|
2273
|
+
* Toggle invisible characters (spaces, tabs, newlines).
|
|
2274
|
+
* When enabled, spaces are shown as dots, tabs as arrows, and
|
|
2275
|
+
* newlines as ¶ symbols.
|
|
2276
|
+
*
|
|
2277
|
+
* @param {boolean} [value] - true=on, false=off. Omit to toggle.
|
|
2278
|
+
* @returns {boolean} The new state
|
|
2279
|
+
*/
|
|
2280
|
+
setShowInvisibles(value) {
|
|
2281
|
+
const newValue = value !== undefined ? !!value : !this._showInvisibles;
|
|
2282
|
+
this._showInvisibles = newValue;
|
|
2283
|
+
view.dispatch({
|
|
2284
|
+
effects: invisiblesCompartment.reconfigure(
|
|
2285
|
+
newValue ? createInvisiblesExtension() : []
|
|
2286
|
+
),
|
|
2287
|
+
});
|
|
2288
|
+
return newValue;
|
|
2289
|
+
},
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* Get current invisible characters state.
|
|
2293
|
+
* @returns {boolean}
|
|
2294
|
+
*/
|
|
2295
|
+
getShowInvisibles() {
|
|
2296
|
+
return this._showInvisibles;
|
|
2297
|
+
},
|
|
2298
|
+
|
|
1384
2299
|
// ===========================================================================
|
|
1385
2300
|
// Wiki-link completion
|
|
1386
2301
|
// ===========================================================================
|
|
@@ -2025,14 +2940,13 @@ function create(target, options = {}) {
|
|
|
2025
2940
|
},
|
|
2026
2941
|
|
|
2027
2942
|
/**
|
|
2028
|
-
*
|
|
2943
|
+
* Get source code for symbol at cursor position, without emitting UI callbacks.
|
|
2029
2944
|
* Calls inspect with detail=2 to get full source code.
|
|
2030
|
-
* Triggers registered onViewSource callbacks.
|
|
2031
2945
|
*
|
|
2032
2946
|
* @param {number} [pos] - Position (defaults to cursor)
|
|
2033
2947
|
* @returns {Promise<{found: boolean, name?: string, sourceCode?: string, file?: string, ...}|null>}
|
|
2034
2948
|
*/
|
|
2035
|
-
async
|
|
2949
|
+
async getSourceInfo(pos) {
|
|
2036
2950
|
const position = pos ?? view.state.selection.main.head;
|
|
2037
2951
|
const content = this.getContent();
|
|
2038
2952
|
const cell = getCellAtCursor(content, position);
|
|
@@ -2047,7 +2961,19 @@ function create(target, options = {}) {
|
|
|
2047
2961
|
if (!provider) return null;
|
|
2048
2962
|
|
|
2049
2963
|
const offset = position - cell.codeStart;
|
|
2050
|
-
|
|
2964
|
+
return provider.inspect(cell.code, offset, cell.language, { detail: 2 });
|
|
2965
|
+
},
|
|
2966
|
+
|
|
2967
|
+
/**
|
|
2968
|
+
* View source code for symbol at cursor position.
|
|
2969
|
+
* Calls inspect with detail=2 to get full source code.
|
|
2970
|
+
* Triggers registered onViewSource callbacks.
|
|
2971
|
+
*
|
|
2972
|
+
* @param {number} [pos] - Position (defaults to cursor)
|
|
2973
|
+
* @returns {Promise<{found: boolean, name?: string, sourceCode?: string, file?: string, ...}|null>}
|
|
2974
|
+
*/
|
|
2975
|
+
async viewSource(pos) {
|
|
2976
|
+
const result = await this.getSourceInfo(pos);
|
|
2051
2977
|
|
|
2052
2978
|
// Trigger callbacks if we got a result
|
|
2053
2979
|
if (result && result.found) {
|
|
@@ -2248,6 +3174,9 @@ function create(target, options = {}) {
|
|
|
2248
3174
|
|
|
2249
3175
|
destroy() {
|
|
2250
3176
|
this.execution.cancelAll();
|
|
3177
|
+
if (this.linkedTables?.destroy) {
|
|
3178
|
+
this.linkedTables.destroy();
|
|
3179
|
+
}
|
|
2251
3180
|
if (cellControls) {
|
|
2252
3181
|
cellControls.destroy();
|
|
2253
3182
|
}
|
|
@@ -2312,6 +3241,13 @@ function create(target, options = {}) {
|
|
|
2312
3241
|
// Create execution manager
|
|
2313
3242
|
api.execution = createExecutionManager(api, registry);
|
|
2314
3243
|
|
|
3244
|
+
// Create linked-table action/job controller
|
|
3245
|
+
api.linkedTables = createLinkedTableController({
|
|
3246
|
+
editor: api,
|
|
3247
|
+
projectRoot: linkedTableHostContext.projectRoot,
|
|
3248
|
+
documentPath: linkedTableHostContext.documentPath,
|
|
3249
|
+
});
|
|
3250
|
+
|
|
2315
3251
|
// Configure keymap now that api is ready
|
|
2316
3252
|
// Merge user keybindings with defaults
|
|
2317
3253
|
const userKeybindings = options.keymap || {};
|
|
@@ -2347,6 +3283,16 @@ function create(target, options = {}) {
|
|
|
2347
3283
|
return { ...currentKeybindings };
|
|
2348
3284
|
};
|
|
2349
3285
|
|
|
3286
|
+
// Initialize section controls now that API exists
|
|
3287
|
+
const applySectionControlsConfig = () => {
|
|
3288
|
+
const options = reactiveConfig.sectionControls || {};
|
|
3289
|
+
const extension = options.enabled === false ? [] : sectionControls(api, options);
|
|
3290
|
+
view.dispatch({
|
|
3291
|
+
effects: sectionControlsCompartment.reconfigure(extension),
|
|
3292
|
+
});
|
|
3293
|
+
};
|
|
3294
|
+
applySectionControlsConfig();
|
|
3295
|
+
|
|
2350
3296
|
// Wire execution events to awareness (so execution badges work automatically)
|
|
2351
3297
|
// This makes the runtime appear as a collaborator executing code
|
|
2352
3298
|
if (awarenessSystem) {
|
|
@@ -2502,6 +3448,11 @@ function create(target, options = {}) {
|
|
|
2502
3448
|
});
|
|
2503
3449
|
|
|
2504
3450
|
reactiveConfig._subscribe(configHandler);
|
|
3451
|
+
reactiveConfig._subscribe((event) => {
|
|
3452
|
+
if (event.path[0] === 'sectionControls') {
|
|
3453
|
+
applySectionControlsConfig();
|
|
3454
|
+
}
|
|
3455
|
+
});
|
|
2505
3456
|
|
|
2506
3457
|
// =========================================================================
|
|
2507
3458
|
// UPDATE DOCUMENT STATE
|
|
@@ -2534,6 +3485,21 @@ function create(target, options = {}) {
|
|
|
2534
3485
|
stateManager.setDirty(true);
|
|
2535
3486
|
updateDocumentState();
|
|
2536
3487
|
}
|
|
3488
|
+
|
|
3489
|
+
if (update.docChanged || update.selectionSet) {
|
|
3490
|
+
const payload = {
|
|
3491
|
+
selection: update.state.selection.main,
|
|
3492
|
+
block: getCurrentBlockTypeInfo(update.view),
|
|
3493
|
+
formatting: getSelectionFormattingInfo(update.view),
|
|
3494
|
+
};
|
|
3495
|
+
selectionHandlers.forEach(fn => {
|
|
3496
|
+
try {
|
|
3497
|
+
fn(payload);
|
|
3498
|
+
} catch (err) {
|
|
3499
|
+
console.warn('[mrmd] selection change handler failed:', err);
|
|
3500
|
+
}
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
2537
3503
|
});
|
|
2538
3504
|
|
|
2539
3505
|
// Add update listener extension
|
|
@@ -3040,22 +4006,6 @@ const cellControlsExports = {
|
|
|
3040
4006
|
};
|
|
3041
4007
|
// #endregion CELL_CONTROLS_EXPORTS
|
|
3042
4008
|
|
|
3043
|
-
// #region RUNTIME_CODELENS_EXPORTS
|
|
3044
|
-
const runtimeCodeLensExports = {
|
|
3045
|
-
createExtensions: createRuntimeCodeLensExtensions,
|
|
3046
|
-
rebuild: rebuildRuntimeCodeLens,
|
|
3047
|
-
facet: runtimeCodeLensFacet,
|
|
3048
|
-
plugin: runtimeCodeLensPlugin,
|
|
3049
|
-
rebuildEffect: rebuildRuntimeCodeLensEffect,
|
|
3050
|
-
injectStyles: injectRuntimeCodeLensStyles,
|
|
3051
|
-
removeStyles: removeRuntimeCodeLensStyles,
|
|
3052
|
-
findBlocks: findRuntimeBlocks,
|
|
3053
|
-
findYamlConfigBlocks,
|
|
3054
|
-
findSessionFrontmatter,
|
|
3055
|
-
Widget: RuntimeCodeLensWidget,
|
|
3056
|
-
};
|
|
3057
|
-
// #endregion RUNTIME_CODELENS_EXPORTS
|
|
3058
|
-
|
|
3059
4009
|
// #region RUNTIME_LSP_EXPORTS
|
|
3060
4010
|
const runtimeLspExports = {
|
|
3061
4011
|
// Adapters
|
|
@@ -3083,6 +4033,16 @@ const markdownExports = {
|
|
|
3083
4033
|
// Asset resolver facet (for Electron/desktop apps)
|
|
3084
4034
|
assetResolverFacet,
|
|
3085
4035
|
|
|
4036
|
+
// Mode facets
|
|
4037
|
+
sourceModeFacet,
|
|
4038
|
+
wysiwygModeFacet,
|
|
4039
|
+
createInlineEditingExtensions,
|
|
4040
|
+
createWysiwygExtensions,
|
|
4041
|
+
toggleInlineFormat,
|
|
4042
|
+
toggleInlineMark,
|
|
4043
|
+
getSelectionFormattingState,
|
|
4044
|
+
findFencedCodeAt,
|
|
4045
|
+
|
|
3086
4046
|
// Styles
|
|
3087
4047
|
markdownStyles,
|
|
3088
4048
|
injectMarkdownStyles,
|
|
@@ -3098,6 +4058,28 @@ const markdownExports = {
|
|
|
3098
4058
|
isTableDelimiter,
|
|
3099
4059
|
generateTableId,
|
|
3100
4060
|
AlertTitleWidget,
|
|
4061
|
+
|
|
4062
|
+
// Page view pagination
|
|
4063
|
+
pageViewPagination,
|
|
4064
|
+
};
|
|
4065
|
+
|
|
4066
|
+
const documentTemplateExports = {
|
|
4067
|
+
defaultDocumentTemplate,
|
|
4068
|
+
documentTemplatePresets,
|
|
4069
|
+
normalizeDocumentTemplate,
|
|
4070
|
+
cloneDocumentTemplate,
|
|
4071
|
+
createDocumentTemplateExtension,
|
|
4072
|
+
compileDocumentTemplateCSS,
|
|
4073
|
+
serializeDocumentTemplateToCss,
|
|
4074
|
+
findDocumentTemplatePreset,
|
|
4075
|
+
// Multi-format export
|
|
4076
|
+
resolveFontForExport,
|
|
4077
|
+
serializeDocumentTemplateToPandocMeta,
|
|
4078
|
+
serializeDocumentTemplateToPandocYaml,
|
|
4079
|
+
serializeDocumentTemplateToLatexPreamble,
|
|
4080
|
+
serializeDocumentTemplateToHtml,
|
|
4081
|
+
serializeDocumentTemplateToWordStyleMap,
|
|
4082
|
+
buildPandocCommand,
|
|
3101
4083
|
};
|
|
3102
4084
|
// #endregion MARKDOWN_EXPORTS
|
|
3103
4085
|
|
|
@@ -3132,14 +4114,22 @@ const mrmd = {
|
|
|
3132
4114
|
stateUtils: stateExports,
|
|
3133
4115
|
// Cell controls (run buttons, queue, status)
|
|
3134
4116
|
cellControls: cellControlsExports,
|
|
3135
|
-
// Runtime CodeLens (inline session controls above yaml config blocks)
|
|
3136
|
-
runtimeCodeLens: runtimeCodeLensExports,
|
|
3137
4117
|
// Runtime LSP (hover, completions, variables)
|
|
3138
4118
|
runtimeLsp: runtimeLspExports,
|
|
3139
4119
|
// Wiki-link completion ([[internal-links]])
|
|
3140
4120
|
wikiLink: wikiLinkExports,
|
|
4121
|
+
// Linked tables
|
|
4122
|
+
linkedTables,
|
|
3141
4123
|
// Markdown rendering (blur→render, focus→source)
|
|
3142
4124
|
markdown: markdownExports,
|
|
4125
|
+
// Frontmatter utilities
|
|
4126
|
+
frontmatter: {
|
|
4127
|
+
parseFrontmatter,
|
|
4128
|
+
readFrontmatterValue,
|
|
4129
|
+
updateFrontmatterField,
|
|
4130
|
+
},
|
|
4131
|
+
// Document templates (semantic content styling)
|
|
4132
|
+
documentTemplates: documentTemplateExports,
|
|
3143
4133
|
// Shell (status bar, file management, studio layout)
|
|
3144
4134
|
shell: shellModule,
|
|
3145
4135
|
// AI Integration (decorations, state, widgets)
|
|
@@ -3184,7 +4174,6 @@ export {
|
|
|
3184
4174
|
codemirror,
|
|
3185
4175
|
terminal,
|
|
3186
4176
|
awarenessExports as awareness,
|
|
3187
|
-
runtimeCodeLensExports as runtimeCodeLens,
|
|
3188
4177
|
RuntimeRegistry,
|
|
3189
4178
|
createRuntimeRegistry,
|
|
3190
4179
|
createJavaScriptRuntime,
|
|
@@ -3268,23 +4257,26 @@ export {
|
|
|
3268
4257
|
cellControlsExports,
|
|
3269
4258
|
createCellControls,
|
|
3270
4259
|
CellControlsSystem,
|
|
3271
|
-
// Runtime CodeLens exports
|
|
3272
|
-
runtimeCodeLensExports,
|
|
3273
|
-
createRuntimeCodeLensExtensions,
|
|
3274
|
-
rebuildRuntimeCodeLens,
|
|
3275
|
-
runtimeCodeLensFacet,
|
|
3276
|
-
runtimeCodeLensPlugin,
|
|
3277
|
-
rebuildRuntimeCodeLensEffect,
|
|
3278
|
-
injectRuntimeCodeLensStyles,
|
|
3279
|
-
removeRuntimeCodeLensStyles,
|
|
3280
|
-
findRuntimeBlocks,
|
|
3281
|
-
findYamlConfigBlocks,
|
|
3282
|
-
findSessionFrontmatter,
|
|
3283
|
-
RuntimeCodeLensWidget,
|
|
3284
4260
|
// Monitor coordination exports
|
|
3285
4261
|
MonitorCoordination,
|
|
3286
4262
|
EXECUTION_STATUS,
|
|
3287
4263
|
createMonitorCoordination,
|
|
4264
|
+
// Linked table exports
|
|
4265
|
+
linkedTables as linkedTablesModule,
|
|
4266
|
+
LINKED_TABLE_EVENT,
|
|
4267
|
+
dispatchLinkedTableAction,
|
|
4268
|
+
openLinkedTableWorkspace,
|
|
4269
|
+
canImportLinkedTableFromHost,
|
|
4270
|
+
normalizeLinkedTableBlockInsertion,
|
|
4271
|
+
insertLinkedTableBlock,
|
|
4272
|
+
importLinkedTableFromHost,
|
|
4273
|
+
TableJobsClient,
|
|
4274
|
+
TABLE_JOB_STATUS,
|
|
4275
|
+
createTableJobsClient,
|
|
4276
|
+
LinkedTableController,
|
|
4277
|
+
createLinkedTableController,
|
|
4278
|
+
createLinkedTableBlockAnchor,
|
|
4279
|
+
resolveLinkedTableBlockAnchor,
|
|
3288
4280
|
// Markdown rendering exports
|
|
3289
4281
|
markdownExports,
|
|
3290
4282
|
markdownRendering as markdown,
|
|
@@ -3302,6 +4294,33 @@ export {
|
|
|
3302
4294
|
isTableDelimiter,
|
|
3303
4295
|
generateTableId,
|
|
3304
4296
|
AlertTitleWidget,
|
|
4297
|
+
// Document template exports
|
|
4298
|
+
documentTemplateExports,
|
|
4299
|
+
defaultDocumentTemplate,
|
|
4300
|
+
documentTemplatePresets,
|
|
4301
|
+
normalizeDocumentTemplate,
|
|
4302
|
+
cloneDocumentTemplate,
|
|
4303
|
+
createDocumentTemplateExtension,
|
|
4304
|
+
compileDocumentTemplateCSS,
|
|
4305
|
+
serializeDocumentTemplateToCss,
|
|
4306
|
+
findDocumentTemplatePreset,
|
|
4307
|
+
resolveFontForExport,
|
|
4308
|
+
serializeDocumentTemplateToPandocMeta,
|
|
4309
|
+
serializeDocumentTemplateToPandocYaml,
|
|
4310
|
+
serializeDocumentTemplateToLatexPreamble,
|
|
4311
|
+
serializeDocumentTemplateToHtml,
|
|
4312
|
+
serializeDocumentTemplateToWordStyleMap,
|
|
4313
|
+
buildPandocCommand,
|
|
4314
|
+
// Spellcheck exports
|
|
4315
|
+
createSpellcheckExtensions,
|
|
4316
|
+
// Grammar exports
|
|
4317
|
+
createLanguageToolDiagnosticsExtension,
|
|
4318
|
+
collectVisibleProseFragments,
|
|
4319
|
+
forceLanguageToolRefresh,
|
|
4320
|
+
refreshLanguageToolDiagnostics,
|
|
4321
|
+
applyFirstLanguageToolSuggestion,
|
|
4322
|
+
getLanguageToolSuggestionMenu,
|
|
4323
|
+
applyLanguageToolSuggestionAt,
|
|
3305
4324
|
// Wiki-link completion exports
|
|
3306
4325
|
wikiLinkExports,
|
|
3307
4326
|
projectFilesFacet,
|
|
@@ -3318,5 +4337,5 @@ export const { createStudio, OrchestratorClient, Drive, createDrive, ShellStateM
|
|
|
3318
4337
|
|
|
3319
4338
|
// Document language detection and frontmatter updater
|
|
3320
4339
|
export { getDocumentLanguages, getLanguageDisplay, isExecutableLanguage } from './document-languages.js';
|
|
3321
|
-
export { parseFrontmatter,
|
|
4340
|
+
export { parseFrontmatter, readFrontmatterSession, getEffectiveSessionConfig, readFrontmatterValue, updateFrontmatterField } from './frontmatter-updater.js';
|
|
3322
4341
|
// #endregion EXPORTS
|