mrmd-editor 0.6.0 → 0.7.1
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 +1 -1
- package/src/cell-controls/widgets.js +30 -0
- package/src/cells.js +9 -9
- package/src/ctrl-k-modal.js +190 -14
- package/src/document-languages.js +105 -0
- package/src/execution.js +73 -25
- package/src/frontmatter-updater.js +224 -0
- package/src/index.js +173 -86
- package/src/markdown/renderer.js +52 -3
- package/src/markdown/styles.js +126 -0
- package/src/monitor-coordination.js +1 -3
- package/src/mrp-client.js +36 -169
- package/src/mrp-types.js +1 -37
- package/src/output-widget.js +54 -0
- package/src/runtime-codelens/index.js +3 -3
- package/src/shell/ai-menu.js +70 -0
- package/src/shell/components/menu.js +39 -1
- package/src/shell/components/status-bar.js +213 -11
- package/src/shell/dialogs/file-picker.js +378 -6
- package/src/shell/layouts/studio.js +31 -9
- package/src/shell/orchestrator-client.js +105 -18
- package/src/shell/state.js +63 -42
- package/src/shell/styles.js +328 -0
- package/src/term-pty-client.js +62 -7
- package/src/widgets/theme-utils.js +12 -12
- package/src/widgets/theme.js +520 -0
package/src/index.js
CHANGED
|
@@ -304,55 +304,55 @@ function parseProgress(output) {
|
|
|
304
304
|
function createJavaScriptRuntime(options = {}) {
|
|
305
305
|
const rt = createMrmdJsRuntime(options);
|
|
306
306
|
|
|
307
|
-
// Track named
|
|
308
|
-
//
|
|
309
|
-
// null/undefined/'default'/'main'/'none' →
|
|
307
|
+
// Track named execution contexts - each can have different isolation
|
|
308
|
+
// Context naming:
|
|
309
|
+
// null/undefined/'default'/'main'/'none' → configured default isolation
|
|
310
310
|
// 'sandbox'/'iframe' → sandboxed iframe
|
|
311
311
|
// other names → sandboxed iframe with separate scope
|
|
312
|
-
const
|
|
312
|
+
const contexts = new Map();
|
|
313
313
|
const defaultIsolation = options.defaultIsolation || 'iframe';
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
|
-
* Get or create a
|
|
317
|
-
* @param {string|null}
|
|
316
|
+
* Get or create a context by name
|
|
317
|
+
* @param {string|null} contextName
|
|
318
318
|
* @returns {Session}
|
|
319
319
|
*/
|
|
320
|
-
function
|
|
321
|
-
// Normalize
|
|
322
|
-
const name =
|
|
320
|
+
function getOrCreateContext(contextName) {
|
|
321
|
+
// Normalize context name
|
|
322
|
+
const name = contextName || 'default';
|
|
323
323
|
|
|
324
|
-
// Return existing
|
|
325
|
-
if (
|
|
326
|
-
return
|
|
324
|
+
// Return existing context
|
|
325
|
+
if (contexts.has(name)) {
|
|
326
|
+
return contexts.get(name);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
// Determine isolation mode based on
|
|
329
|
+
// Determine isolation mode based on context name
|
|
330
330
|
let isolation;
|
|
331
|
-
if (!
|
|
332
|
-
// Default
|
|
331
|
+
if (!contextName || contextName === 'default' || contextName === 'main' || contextName === 'none') {
|
|
332
|
+
// Default context uses the configured default isolation
|
|
333
333
|
isolation = defaultIsolation;
|
|
334
|
-
} else if (
|
|
334
|
+
} else if (contextName === 'sandbox' || contextName === 'iframe') {
|
|
335
335
|
// Explicit sandbox request
|
|
336
336
|
isolation = 'iframe';
|
|
337
337
|
} else {
|
|
338
|
-
// Named
|
|
338
|
+
// Named contexts are sandboxed by default (separate scope)
|
|
339
339
|
isolation = 'iframe';
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
// Create new
|
|
343
|
-
const
|
|
342
|
+
// Create new execution context with appropriate isolation
|
|
343
|
+
const context = rt.createSession({
|
|
344
344
|
language: 'javascript',
|
|
345
345
|
isolation,
|
|
346
346
|
id: name,
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
console.log(`[JS Runtime] Created
|
|
351
|
-
return
|
|
349
|
+
contexts.set(name, context);
|
|
350
|
+
console.log(`[JS Runtime] Created context '${name}' with isolation: ${isolation}`);
|
|
351
|
+
return context;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
// Create default
|
|
355
|
-
const
|
|
354
|
+
// Create default context eagerly
|
|
355
|
+
const defaultContext = getOrCreateContext('default');
|
|
356
356
|
|
|
357
357
|
// Languages supported by mrmd-js
|
|
358
358
|
const supportedLanguages = {
|
|
@@ -379,8 +379,9 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
379
379
|
/** Execute code (non-streaming) */
|
|
380
380
|
async execute(code, language, execOptions = {}) {
|
|
381
381
|
const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
|
|
382
|
-
const
|
|
383
|
-
const
|
|
382
|
+
const contextName = execOptions.context ?? execOptions.session;
|
|
383
|
+
const context = getOrCreateContext(contextName);
|
|
384
|
+
const result = await context.execute(code, { language: lang });
|
|
384
385
|
return {
|
|
385
386
|
success: result.success,
|
|
386
387
|
stdout: result.stdout || '',
|
|
@@ -394,8 +395,9 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
394
395
|
/** Execute code with streaming output */
|
|
395
396
|
async executeStreaming(code, language, onChunk, onStdinRequest, execOptions = {}) {
|
|
396
397
|
const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
|
|
397
|
-
const
|
|
398
|
-
const
|
|
398
|
+
const contextName = execOptions.context ?? execOptions.session;
|
|
399
|
+
const context = getOrCreateContext(contextName);
|
|
400
|
+
const result = await context.execute(code, { language: lang });
|
|
399
401
|
|
|
400
402
|
// Handle different output types
|
|
401
403
|
let output = result.stdout || '';
|
|
@@ -434,18 +436,18 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
434
436
|
};
|
|
435
437
|
},
|
|
436
438
|
|
|
437
|
-
/** Reset a
|
|
438
|
-
reset(
|
|
439
|
-
const
|
|
440
|
-
if (
|
|
441
|
-
|
|
439
|
+
/** Reset a context (clear all variables) */
|
|
440
|
+
reset(contextName) {
|
|
441
|
+
const context = contexts.get(contextName || 'default');
|
|
442
|
+
if (context) {
|
|
443
|
+
context.reset();
|
|
442
444
|
}
|
|
443
445
|
},
|
|
444
446
|
|
|
445
|
-
/** Reset all
|
|
447
|
+
/** Reset all contexts */
|
|
446
448
|
resetAll() {
|
|
447
|
-
for (const
|
|
448
|
-
|
|
449
|
+
for (const context of contexts.values()) {
|
|
450
|
+
context.reset();
|
|
449
451
|
}
|
|
450
452
|
},
|
|
451
453
|
|
|
@@ -454,23 +456,32 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
454
456
|
return rt;
|
|
455
457
|
},
|
|
456
458
|
|
|
457
|
-
/** Get a
|
|
458
|
-
|
|
459
|
-
return
|
|
459
|
+
/** Get a context by name (default if not specified) */
|
|
460
|
+
getContext(contextName) {
|
|
461
|
+
return getOrCreateContext(contextName);
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
/** List all context names */
|
|
465
|
+
listContexts() {
|
|
466
|
+
return Array.from(contexts.keys());
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
// Legacy aliases (kept for compatibility inside monorepo)
|
|
470
|
+
getSession(contextName) {
|
|
471
|
+
return getOrCreateContext(contextName);
|
|
460
472
|
},
|
|
461
473
|
|
|
462
|
-
/** List all session names */
|
|
463
474
|
listSessions() {
|
|
464
|
-
return Array.from(
|
|
475
|
+
return Array.from(contexts.keys());
|
|
465
476
|
},
|
|
466
477
|
|
|
467
|
-
/** Destroy the runtime and all
|
|
478
|
+
/** Destroy the runtime and all contexts */
|
|
468
479
|
destroy() {
|
|
469
480
|
rt.destroy();
|
|
470
481
|
},
|
|
471
482
|
|
|
472
483
|
// =========================================================================
|
|
473
|
-
// LSP Features (powered by mrmd-js
|
|
484
|
+
// LSP Features (powered by mrmd-js default context)
|
|
474
485
|
// =========================================================================
|
|
475
486
|
|
|
476
487
|
/**
|
|
@@ -482,7 +493,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
482
493
|
* @returns {{found: boolean, name?: string, type?: string, value?: string, signature?: string}|null}
|
|
483
494
|
*/
|
|
484
495
|
hover(code, cursor) {
|
|
485
|
-
return
|
|
496
|
+
return defaultContext.hover(code, cursor);
|
|
486
497
|
},
|
|
487
498
|
|
|
488
499
|
/**
|
|
@@ -494,7 +505,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
494
505
|
* @returns {{matches: Array, cursorStart: number, cursorEnd: number}}
|
|
495
506
|
*/
|
|
496
507
|
complete(code, cursor) {
|
|
497
|
-
return
|
|
508
|
+
return defaultContext.complete(code, cursor);
|
|
498
509
|
},
|
|
499
510
|
|
|
500
511
|
/**
|
|
@@ -506,18 +517,18 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
506
517
|
* @returns {Object|null}
|
|
507
518
|
*/
|
|
508
519
|
inspect(code, cursor, options = {}) {
|
|
509
|
-
return
|
|
520
|
+
return defaultContext.inspect(code, cursor, options);
|
|
510
521
|
},
|
|
511
522
|
|
|
512
523
|
/**
|
|
513
|
-
* List all variables in a
|
|
524
|
+
* List all variables in a context namespace.
|
|
514
525
|
*
|
|
515
526
|
* @param {Object} [filter]
|
|
516
|
-
* @param {string} [
|
|
527
|
+
* @param {string} [contextName='default']
|
|
517
528
|
* @returns {Array<{name: string, type: string, value: string, expandable?: boolean}>}
|
|
518
529
|
*/
|
|
519
|
-
listVariables(filter = {},
|
|
520
|
-
return
|
|
530
|
+
listVariables(filter = {}, contextName = 'default') {
|
|
531
|
+
return getOrCreateContext(contextName).listVariables(filter);
|
|
521
532
|
},
|
|
522
533
|
|
|
523
534
|
/**
|
|
@@ -528,7 +539,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
528
539
|
* @returns {Object}
|
|
529
540
|
*/
|
|
530
541
|
getVariable(name, options = {}) {
|
|
531
|
-
return
|
|
542
|
+
return defaultContext.getVariable(name, options);
|
|
532
543
|
},
|
|
533
544
|
|
|
534
545
|
/**
|
|
@@ -538,7 +549,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
538
549
|
* @returns {{status: 'complete'|'incomplete'|'invalid'|'unknown', indent?: string}}
|
|
539
550
|
*/
|
|
540
551
|
isComplete(code) {
|
|
541
|
-
return
|
|
552
|
+
return defaultContext.isComplete(code);
|
|
542
553
|
},
|
|
543
554
|
|
|
544
555
|
/**
|
|
@@ -548,7 +559,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
548
559
|
* @returns {Promise<{formatted: string, changed: boolean}>}
|
|
549
560
|
*/
|
|
550
561
|
format(code) {
|
|
551
|
-
return
|
|
562
|
+
return defaultContext.format(code);
|
|
552
563
|
},
|
|
553
564
|
|
|
554
565
|
/**
|
|
@@ -556,7 +567,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
556
567
|
* @returns {import('./runtime-lsp.js').RuntimeLSPProvider}
|
|
557
568
|
*/
|
|
558
569
|
getLSPProvider() {
|
|
559
|
-
return adaptMrmdJsSession(
|
|
570
|
+
return adaptMrmdJsSession(defaultContext);
|
|
560
571
|
},
|
|
561
572
|
};
|
|
562
573
|
}
|
|
@@ -731,6 +742,15 @@ const codeBlockStyles = EditorView.theme({
|
|
|
731
742
|
'.cm-codeblock-fence::selection, .cm-codeblock-fence *::selection': {
|
|
732
743
|
backgroundColor: 'var(--editor-selection, #264f78) !important',
|
|
733
744
|
},
|
|
745
|
+
// Mobile: code blocks need to be larger and scroll horizontally
|
|
746
|
+
'@media (max-width: 768px)': {
|
|
747
|
+
'.cm-codeblock-line': {
|
|
748
|
+
fontSize: 'max(var(--code-font-size, 0.8em), 13px)',
|
|
749
|
+
},
|
|
750
|
+
'.cm-codeblock-fence': {
|
|
751
|
+
fontSize: '0.6em', // Slightly larger than desktop's 0.5em for visibility
|
|
752
|
+
},
|
|
753
|
+
},
|
|
734
754
|
});
|
|
735
755
|
// #endregion CODE_BLOCK_BACKGROUND
|
|
736
756
|
|
|
@@ -771,6 +791,50 @@ class Writer {
|
|
|
771
791
|
}
|
|
772
792
|
// #endregion WRITER
|
|
773
793
|
|
|
794
|
+
// #region INITIAL_CURSOR
|
|
795
|
+
/**
|
|
796
|
+
* Find the ideal initial cursor position for a markdown document.
|
|
797
|
+
*
|
|
798
|
+
* When opening a file, placing the cursor at position 0 shows raw frontmatter
|
|
799
|
+
* YAML which looks ugly. Instead, we find the first empty line after any
|
|
800
|
+
* frontmatter block — this causes the frontmatter to render as a nice widget
|
|
801
|
+
* and gives a clean first impression.
|
|
802
|
+
*
|
|
803
|
+
* @param {string} content - Document content
|
|
804
|
+
* @returns {number} Character position for the cursor
|
|
805
|
+
*/
|
|
806
|
+
function findInitialCursorPosition(content) {
|
|
807
|
+
if (!content) return 0;
|
|
808
|
+
|
|
809
|
+
const lines = content.split('\n');
|
|
810
|
+
let i = 0;
|
|
811
|
+
|
|
812
|
+
// Skip YAML frontmatter if present (--- ... ---)
|
|
813
|
+
if (lines[0]?.trim() === '---') {
|
|
814
|
+
i = 1;
|
|
815
|
+
while (i < lines.length && lines[i]?.trim() !== '---') {
|
|
816
|
+
i++;
|
|
817
|
+
}
|
|
818
|
+
if (i < lines.length) i++; // skip closing ---
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Find first empty line from current position
|
|
822
|
+
while (i < lines.length) {
|
|
823
|
+
if (lines[i]?.trim() === '') {
|
|
824
|
+
// Calculate character position (start of this empty line)
|
|
825
|
+
let pos = 0;
|
|
826
|
+
for (let j = 0; j < i; j++) {
|
|
827
|
+
pos += lines[j].length + 1; // +1 for \n
|
|
828
|
+
}
|
|
829
|
+
return pos;
|
|
830
|
+
}
|
|
831
|
+
i++;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return 0; // fallback to start
|
|
835
|
+
}
|
|
836
|
+
// #endregion INITIAL_CURSOR
|
|
837
|
+
|
|
774
838
|
// #region CREATE
|
|
775
839
|
/**
|
|
776
840
|
* Create a standalone markdown editor
|
|
@@ -896,6 +960,11 @@ function create(target, options = {}) {
|
|
|
896
960
|
'.cm-gutters': { display: 'none' },
|
|
897
961
|
'.cm-activeLineGutter': { backgroundColor: 'transparent' },
|
|
898
962
|
'&.cm-focused': { outline: 'none' },
|
|
963
|
+
// Mobile: slightly larger text, comfortable line-height
|
|
964
|
+
'@media (max-width: 768px)': {
|
|
965
|
+
'&': { fontSize: '17px' },
|
|
966
|
+
'.cm-scroller': { lineHeight: '1.7' },
|
|
967
|
+
},
|
|
899
968
|
});
|
|
900
969
|
|
|
901
970
|
// Inject CSS styles
|
|
@@ -2046,17 +2115,16 @@ function create(target, options = {}) {
|
|
|
2046
2115
|
* Refresh variables from all MRP runtimes
|
|
2047
2116
|
* Fetches current variable state and updates state.variables
|
|
2048
2117
|
*
|
|
2049
|
-
* @param {string} [sessionId] - Specific session to refresh (optional)
|
|
2050
2118
|
* @returns {Promise<void>}
|
|
2051
2119
|
*/
|
|
2052
|
-
async refreshVariables(
|
|
2120
|
+
async refreshVariables() {
|
|
2053
2121
|
for (const [name, runtime] of registry.runtimes) {
|
|
2054
2122
|
// Check if runtime is an MRP client (has getVariables method)
|
|
2055
2123
|
if (typeof runtime.getVariables === 'function') {
|
|
2056
2124
|
try {
|
|
2057
|
-
const result = await runtime.getVariables(
|
|
2125
|
+
const result = await runtime.getVariables();
|
|
2058
2126
|
if (result && result.variables) {
|
|
2059
|
-
const session =
|
|
2127
|
+
const session = 'default';
|
|
2060
2128
|
const variables = {};
|
|
2061
2129
|
for (const v of result.variables) {
|
|
2062
2130
|
variables[v.name] = {
|
|
@@ -2546,12 +2614,12 @@ function create(target, options = {}) {
|
|
|
2546
2614
|
}
|
|
2547
2615
|
// #endregion CREATE
|
|
2548
2616
|
|
|
2549
|
-
// #region
|
|
2617
|
+
// #region RUNTIME
|
|
2550
2618
|
/**
|
|
2551
|
-
* Create an editor
|
|
2619
|
+
* Create an editor runtime attachment via orchestrator.
|
|
2552
2620
|
*
|
|
2553
2621
|
* This is the simplest way to use mrmd with full features:
|
|
2554
|
-
* - Automatically creates
|
|
2622
|
+
* - Automatically creates/attaches runtime with orchestrator
|
|
2555
2623
|
* - Connects to sync server
|
|
2556
2624
|
* - Sets up Python runtime (shared or dedicated)
|
|
2557
2625
|
* - Enables monitor mode for long-running executions
|
|
@@ -2562,28 +2630,28 @@ function create(target, options = {}) {
|
|
|
2562
2630
|
* @param {string} options.doc - Document name (required)
|
|
2563
2631
|
* @param {string} [options.python='shared'] - 'shared' or 'dedicated'
|
|
2564
2632
|
* @param {Object} [options.editor] - Additional editor options
|
|
2565
|
-
* @returns {Promise<Object>} Editor instance with
|
|
2633
|
+
* @returns {Promise<Object>} Editor instance with destroyRuntime() method
|
|
2566
2634
|
*
|
|
2567
2635
|
* @example
|
|
2568
2636
|
* // Basic usage
|
|
2569
|
-
* const editor = await mrmd.
|
|
2637
|
+
* const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
|
|
2570
2638
|
* doc: 'my-notebook',
|
|
2571
2639
|
* });
|
|
2572
2640
|
*
|
|
2573
2641
|
* // With dedicated Python runtime
|
|
2574
|
-
* const editor = await mrmd.
|
|
2642
|
+
* const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
|
|
2575
2643
|
* doc: 'my-notebook',
|
|
2576
2644
|
* python: 'dedicated',
|
|
2577
2645
|
* });
|
|
2578
2646
|
*
|
|
2579
2647
|
* // Clean up when done
|
|
2580
|
-
* await editor.
|
|
2648
|
+
* await editor.destroyRuntime();
|
|
2581
2649
|
*/
|
|
2582
|
-
async function
|
|
2650
|
+
async function runtime(orchestratorUrl, target, options = {}) {
|
|
2583
2651
|
const { doc, python = 'shared', editor: editorOptions = {} } = options;
|
|
2584
2652
|
|
|
2585
2653
|
if (!doc) {
|
|
2586
|
-
throw new Error('mrmd.
|
|
2654
|
+
throw new Error('mrmd.runtime: doc option is required');
|
|
2587
2655
|
}
|
|
2588
2656
|
|
|
2589
2657
|
// Normalize orchestrator URL
|
|
@@ -2592,24 +2660,33 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2592
2660
|
baseUrl = baseUrl.slice(0, -1);
|
|
2593
2661
|
}
|
|
2594
2662
|
|
|
2595
|
-
// Create
|
|
2596
|
-
console.log(`[mrmd.
|
|
2663
|
+
// Create runtime attachment with orchestrator
|
|
2664
|
+
console.log(`[mrmd.runtime] Creating runtime for '${doc}' (python=${python})`);
|
|
2597
2665
|
|
|
2598
|
-
|
|
2666
|
+
// Prefer /api/runtimes, fall back to /api/sessions for legacy orchestrators
|
|
2667
|
+
let response = await fetch(`${baseUrl}/api/runtimes`, {
|
|
2599
2668
|
method: 'POST',
|
|
2600
2669
|
headers: { 'Content-Type': 'application/json' },
|
|
2601
2670
|
body: JSON.stringify({ doc, python }),
|
|
2602
2671
|
});
|
|
2603
2672
|
|
|
2673
|
+
if (!response.ok && response.status === 404) {
|
|
2674
|
+
response = await fetch(`${baseUrl}/api/sessions`, {
|
|
2675
|
+
method: 'POST',
|
|
2676
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2677
|
+
body: JSON.stringify({ doc, python }),
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2604
2681
|
if (!response.ok) {
|
|
2605
2682
|
const error = await response.text();
|
|
2606
|
-
throw new Error(`Failed to create
|
|
2683
|
+
throw new Error(`Failed to create runtime attachment: ${error}`);
|
|
2607
2684
|
}
|
|
2608
2685
|
|
|
2609
2686
|
const sessionInfo = await response.json();
|
|
2610
|
-
console.log('[mrmd.
|
|
2687
|
+
console.log('[mrmd.runtime] Runtime created:', sessionInfo);
|
|
2611
2688
|
|
|
2612
|
-
// Extract URLs from
|
|
2689
|
+
// Extract URLs from response
|
|
2613
2690
|
const syncUrl = sessionInfo.sync;
|
|
2614
2691
|
const runtimeUrl = sessionInfo.runtimes?.python?.url;
|
|
2615
2692
|
|
|
@@ -2634,17 +2711,16 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2634
2711
|
ydoc: editor.ydoc,
|
|
2635
2712
|
runtimeUrl,
|
|
2636
2713
|
awareness: editor.awareness,
|
|
2637
|
-
session: doc,
|
|
2638
2714
|
});
|
|
2639
2715
|
}
|
|
2640
2716
|
|
|
2641
|
-
// Store
|
|
2717
|
+
// Store runtime info on editor
|
|
2642
2718
|
editor._sessionInfo = sessionInfo;
|
|
2643
2719
|
editor._orchestratorUrl = baseUrl;
|
|
2644
2720
|
|
|
2645
|
-
// Add
|
|
2646
|
-
editor.
|
|
2647
|
-
console.log(`[mrmd.
|
|
2721
|
+
// Add destroyRuntime method
|
|
2722
|
+
editor.destroyRuntime = async function() {
|
|
2723
|
+
console.log(`[mrmd.runtime] Destroying runtime for '${doc}'`);
|
|
2648
2724
|
|
|
2649
2725
|
// Disconnect from sync
|
|
2650
2726
|
if (editor.disconnect) {
|
|
@@ -2653,28 +2729,33 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2653
2729
|
|
|
2654
2730
|
// Call orchestrator to clean up
|
|
2655
2731
|
try {
|
|
2656
|
-
|
|
2732
|
+
let resp = await fetch(`${baseUrl}/api/runtimes/${encodeURIComponent(doc)}`, {
|
|
2657
2733
|
method: 'DELETE',
|
|
2658
2734
|
});
|
|
2735
|
+
if (!resp.ok && resp.status === 404) {
|
|
2736
|
+
resp = await fetch(`${baseUrl}/api/sessions/${encodeURIComponent(doc)}`, {
|
|
2737
|
+
method: 'DELETE',
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2659
2740
|
if (!resp.ok) {
|
|
2660
|
-
console.warn(`[mrmd.
|
|
2741
|
+
console.warn(`[mrmd.runtime] Failed to destroy runtime: ${resp.statusText}`);
|
|
2661
2742
|
}
|
|
2662
2743
|
} catch (err) {
|
|
2663
|
-
console.warn(`[mrmd.
|
|
2744
|
+
console.warn(`[mrmd.runtime] Failed to destroy runtime:`, err);
|
|
2664
2745
|
}
|
|
2665
2746
|
|
|
2666
2747
|
// Destroy editor
|
|
2667
2748
|
editor.destroy();
|
|
2668
2749
|
};
|
|
2669
2750
|
|
|
2670
|
-
// Add method to get
|
|
2671
|
-
editor.
|
|
2751
|
+
// Add method to get runtime info
|
|
2752
|
+
editor.getRuntimeInfo = function() {
|
|
2672
2753
|
return this._sessionInfo;
|
|
2673
2754
|
};
|
|
2674
2755
|
|
|
2675
2756
|
return editor;
|
|
2676
2757
|
}
|
|
2677
|
-
// #endregion
|
|
2758
|
+
// #endregion RUNTIME
|
|
2678
2759
|
|
|
2679
2760
|
// #region DRIVE
|
|
2680
2761
|
/**
|
|
@@ -3038,7 +3119,8 @@ const mrmd = {
|
|
|
3038
3119
|
version: VERSION,
|
|
3039
3120
|
create,
|
|
3040
3121
|
drive,
|
|
3041
|
-
|
|
3122
|
+
runtime,
|
|
3123
|
+
findInitialCursorPosition,
|
|
3042
3124
|
yjs,
|
|
3043
3125
|
codemirror,
|
|
3044
3126
|
terminal,
|
|
@@ -3096,7 +3178,8 @@ export default mrmd;
|
|
|
3096
3178
|
export {
|
|
3097
3179
|
create,
|
|
3098
3180
|
drive,
|
|
3099
|
-
|
|
3181
|
+
runtime,
|
|
3182
|
+
findInitialCursorPosition,
|
|
3100
3183
|
yjs,
|
|
3101
3184
|
codemirror,
|
|
3102
3185
|
terminal,
|
|
@@ -3232,4 +3315,8 @@ export {
|
|
|
3232
3315
|
|
|
3233
3316
|
// Re-export shell components for direct imports
|
|
3234
3317
|
export const { createStudio, OrchestratorClient, Drive, createDrive, ShellStateManager, injectShellStyles } = shellModule;
|
|
3318
|
+
|
|
3319
|
+
// Document language detection and frontmatter updater
|
|
3320
|
+
export { getDocumentLanguages, getLanguageDisplay, isExecutableLanguage } from './document-languages.js';
|
|
3321
|
+
export { parseFrontmatter, updateFrontmatterSession, readFrontmatterSession, getEffectiveSessionConfig } from './frontmatter-updater.js';
|
|
3235
3322
|
// #endregion EXPORTS
|
package/src/markdown/renderer.js
CHANGED
|
@@ -121,6 +121,33 @@ function extractWikiLinks(text) {
|
|
|
121
121
|
return results;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Find YAML frontmatter at the top of the document (--- ... ---).
|
|
126
|
+
*
|
|
127
|
+
* We use this to suppress markdown rendering inside frontmatter since the
|
|
128
|
+
* opening/closing `---` lines are parsed as HorizontalRule nodes by markdown.
|
|
129
|
+
*/
|
|
130
|
+
function findFrontmatterRange(doc) {
|
|
131
|
+
if (doc.lines < 2) return null;
|
|
132
|
+
|
|
133
|
+
const firstLine = doc.line(1);
|
|
134
|
+
if (firstLine.text.trim() !== '---') return null;
|
|
135
|
+
|
|
136
|
+
for (let i = 2; i <= doc.lines; i++) {
|
|
137
|
+
const line = doc.line(i);
|
|
138
|
+
if (line.text.trim() === '---') {
|
|
139
|
+
return {
|
|
140
|
+
from: firstLine.from,
|
|
141
|
+
to: line.to,
|
|
142
|
+
startLine: 1,
|
|
143
|
+
endLine: i,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
/**
|
|
125
152
|
* BlockImageWidget wrapper that caches its rendered height for stable layout.
|
|
126
153
|
*/
|
|
@@ -223,6 +250,7 @@ function buildDecorations(view) {
|
|
|
223
250
|
const doc = view.state.doc;
|
|
224
251
|
const cursorPos = view.state.selection.main.head;
|
|
225
252
|
const cursorLine = doc.lineAt(cursorPos).number;
|
|
253
|
+
const frontmatterRange = findFrontmatterRange(doc);
|
|
226
254
|
|
|
227
255
|
// Get asset resolver from facet (may be null)
|
|
228
256
|
const assetResolver = view.state.facet(assetResolverFacet);
|
|
@@ -249,6 +277,21 @@ function buildDecorations(view) {
|
|
|
249
277
|
to: view.viewport.to,
|
|
250
278
|
enter: (node) => {
|
|
251
279
|
const lineNum = doc.lineAt(node.from).number;
|
|
280
|
+
|
|
281
|
+
// Never apply markdown rendering/styling inside YAML frontmatter.
|
|
282
|
+
// Frontmatter is rendered separately by block-decorations.
|
|
283
|
+
// Use line-based start detection so we still skip nodes that may extend
|
|
284
|
+
// past the line boundary (e.g., HorizontalRule tokens including newline).
|
|
285
|
+
if (frontmatterRange && node.name !== 'Document') {
|
|
286
|
+
const nodeStartLine = doc.lineAt(node.from).number;
|
|
287
|
+
if (
|
|
288
|
+
nodeStartLine >= frontmatterRange.startLine &&
|
|
289
|
+
nodeStartLine <= frontmatterRange.endLine
|
|
290
|
+
) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
252
295
|
const isActiveLine = lineNum === cursorLine;
|
|
253
296
|
|
|
254
297
|
// Marker class: hidden on blur, muted on focus
|
|
@@ -735,6 +778,9 @@ function buildDecorations(view) {
|
|
|
735
778
|
const line = doc.line(i);
|
|
736
779
|
const isActiveLine = i === cursorLine;
|
|
737
780
|
|
|
781
|
+
// Skip frontmatter lines
|
|
782
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
783
|
+
|
|
738
784
|
// Skip lines inside code blocks
|
|
739
785
|
if (codeBlockLines.has(i)) continue;
|
|
740
786
|
|
|
@@ -770,6 +816,9 @@ function buildDecorations(view) {
|
|
|
770
816
|
const line = doc.line(i);
|
|
771
817
|
const isActiveLine = i === cursorLine;
|
|
772
818
|
|
|
819
|
+
// Skip frontmatter lines
|
|
820
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
821
|
+
|
|
773
822
|
// Skip lines inside code blocks (using syntax tree detection)
|
|
774
823
|
if (codeBlockLines.has(i)) continue;
|
|
775
824
|
|
|
@@ -810,12 +859,12 @@ function buildDecorations(view) {
|
|
|
810
859
|
const line = doc.line(i);
|
|
811
860
|
const isActiveLine = i === cursorLine;
|
|
812
861
|
|
|
862
|
+
// Skip frontmatter lines
|
|
863
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
864
|
+
|
|
813
865
|
// Skip lines inside code blocks (using syntax tree detection)
|
|
814
866
|
if (codeBlockLines.has(i)) continue;
|
|
815
867
|
|
|
816
|
-
// Skip frontmatter (YAML between ---)
|
|
817
|
-
// This is a simple check - a full solution would track state
|
|
818
|
-
|
|
819
868
|
const htmlElements = extractHtmlElements(line.text);
|
|
820
869
|
|
|
821
870
|
for (const el of htmlElements) {
|