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.
Files changed (58) hide show
  1. package/package.json +3 -1
  2. package/src/commands.js +112 -4
  3. package/src/comment-syntax.js +364 -39
  4. package/src/config/handlers.js +1 -2
  5. package/src/config/schema.js +46 -4
  6. package/src/document-template.js +2236 -0
  7. package/src/frontmatter-updater.js +204 -74
  8. package/src/grammar.js +758 -0
  9. package/src/index.js +1074 -55
  10. package/src/keymap.js +11 -2
  11. package/src/markdown/block-decorations.js +108 -5
  12. package/src/markdown/facets.js +37 -0
  13. package/src/markdown/html-inline.js +9 -5
  14. package/src/markdown/index.js +13 -3
  15. package/src/markdown/inline-commands.js +256 -0
  16. package/src/markdown/inline-model.js +578 -0
  17. package/src/markdown/inline-state.js +103 -0
  18. package/src/markdown/renderer.js +219 -12
  19. package/src/markdown/styles.js +290 -3
  20. package/src/markdown/widgets/alert-title.js +10 -8
  21. package/src/markdown/widgets/frontmatter.js +0 -6
  22. package/src/markdown/widgets/index.js +1 -0
  23. package/src/markdown/widgets/list-marker.js +29 -0
  24. package/src/markdown/wysiwyg.js +1158 -0
  25. package/src/mrp-types.js +2 -0
  26. package/src/output-widget.js +532 -18
  27. package/src/page-view-pagination.js +127 -0
  28. package/src/runtime-lsp.js +1757 -150
  29. package/src/section-controls/commands.js +617 -0
  30. package/src/section-controls/index.js +63 -0
  31. package/src/section-controls/plugin.js +165 -0
  32. package/src/section-controls/widgets.js +936 -0
  33. package/src/shell/ai-menu.js +11 -0
  34. package/src/shell/components/context-panel.js +572 -0
  35. package/src/shell/components/status-bar.js +10 -2
  36. package/src/shell/layouts/studio.js +206 -14
  37. package/src/shell/orchestrator-client.js +69 -0
  38. package/src/spellcheck.js +166 -0
  39. package/src/tables/README.md +97 -0
  40. package/src/tables/commands/insert-linked-table.js +122 -0
  41. package/src/tables/commands/open-table-workspace.js +43 -0
  42. package/src/tables/index.js +24 -0
  43. package/src/tables/jobs/client.js +158 -0
  44. package/src/tables/parsing/anchors.js +82 -0
  45. package/src/tables/parsing/linked-table-blocks.js +61 -0
  46. package/src/tables/state/linked-table-state.js +68 -0
  47. package/src/tables/widgets/linked-table-source-banner.js +77 -0
  48. package/src/tables/widgets/linked-table-widget.js +256 -0
  49. package/src/tables/workspace/controller.js +616 -0
  50. package/src/term-pty-client.js +51 -2
  51. package/src/term-widget.js +43 -3
  52. package/src/widgets/theme-utils.js +24 -16
  53. package/src/widgets/theme.js +1015 -1
  54. package/src/runtime-codelens/detector.js +0 -279
  55. package/src/runtime-codelens/index.js +0 -76
  56. package/src/runtime-codelens/plugin.js +0 -142
  57. package/src/runtime-codelens/styles.js +0 -184
  58. package/src/runtime-codelens/widgets.js +0 -216
@@ -15,6 +15,7 @@
15
15
  * @property {string} [cwd] - Working directory
16
16
  * @property {string} [venv] - Virtual environment path
17
17
  * @property {string} [filePath] - Associated file path
18
+ * @property {string} [shell] - Preferred shell (e.g. powershell, wsl, cmd)
18
19
  * @property {Function} [onData] - Callback for data from PTY
19
20
  * @property {Function} [onConnect] - Callback when connected
20
21
  * @property {Function} [onDisconnect] - Callback when disconnected
@@ -36,6 +37,7 @@ export class PtyClient {
36
37
  cwd: config.cwd || null,
37
38
  venv: config.venv || null,
38
39
  filePath: config.filePath || null,
40
+ shell: config.shell || null,
39
41
  onData: config.onData || (() => {}),
40
42
  onConnect: config.onConnect || (() => {}),
41
43
  onDisconnect: config.onDisconnect || (() => {}),
@@ -56,6 +58,10 @@ export class PtyClient {
56
58
  this.maxReconnectAttempts = 10;
57
59
  this.baseReconnectDelay = 1000;
58
60
  this.reconnectTimeout = null;
61
+
62
+ // Small-input batching (typing) to reduce WS frame overhead on tunneled/mobile connections.
63
+ this._writeBuffer = '';
64
+ this._writeTimer = null;
59
65
  }
60
66
 
61
67
  /**
@@ -88,6 +94,9 @@ export class PtyClient {
88
94
  if (this.config.filePath) {
89
95
  params.set('file_path', this.config.filePath);
90
96
  }
97
+ if (this.config.shell) {
98
+ params.set('shell', this.config.shell);
99
+ }
91
100
 
92
101
  return `${protocol}//${host}/api/pty?${params.toString()}`;
93
102
  }
@@ -149,6 +158,11 @@ export class PtyClient {
149
158
  console.log('[PtyClient] Disconnected, code:', event.code);
150
159
  this.connected = false;
151
160
  this.ws = null;
161
+ if (this._writeTimer) {
162
+ clearTimeout(this._writeTimer);
163
+ this._writeTimer = null;
164
+ }
165
+ this._writeBuffer = '';
152
166
  this.config.onDisconnect(event.code, event.reason);
153
167
 
154
168
  // Attempt reconnection if not intentionally closed
@@ -184,6 +198,21 @@ export class PtyClient {
184
198
  }, delay);
185
199
  }
186
200
 
201
+ /**
202
+ * Flush any buffered small-input writes.
203
+ */
204
+ _flushWriteBuffer() {
205
+ if (!this._writeBuffer) return;
206
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
207
+ this._writeBuffer = '';
208
+ return;
209
+ }
210
+
211
+ const buffered = this._writeBuffer;
212
+ this._writeBuffer = '';
213
+ this.ws.send(JSON.stringify({ type: 'input', data: buffered }));
214
+ }
215
+
187
216
  /**
188
217
  * Send data to the PTY (user input).
189
218
  * Large payloads (pastes) are chunked to avoid overwhelming the terminal
@@ -194,12 +223,26 @@ export class PtyClient {
194
223
  write(data) {
195
224
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
196
225
 
197
- // Small inputs (normal typing, control sequences): send immediately
226
+ // Small inputs (normal typing/control sequences): buffer briefly so
227
+ // rapid keypresses are batched into fewer WS frames.
198
228
  if (data.length <= 4096) {
199
- this.ws.send(JSON.stringify({ type: 'input', data }));
229
+ this._writeBuffer += data;
230
+ if (!this._writeTimer) {
231
+ this._writeTimer = setTimeout(() => {
232
+ this._writeTimer = null;
233
+ this._flushWriteBuffer();
234
+ }, 8);
235
+ }
200
236
  return;
201
237
  }
202
238
 
239
+ // Preserve input ordering: flush any pending small-input batch first.
240
+ if (this._writeTimer) {
241
+ clearTimeout(this._writeTimer);
242
+ this._writeTimer = null;
243
+ }
244
+ this._flushWriteBuffer();
245
+
203
246
  // Large paste: chunk it to prevent UI freeze.
204
247
  // xterm.js may wrap pastes in bracketed paste markers (\x1b[200~ ... \x1b[201~).
205
248
  // We preserve those: send a leading marker once, chunk the body, send trailing marker.
@@ -266,6 +309,12 @@ export class PtyClient {
266
309
  this.reconnectTimeout = null;
267
310
  }
268
311
 
312
+ if (this._writeTimer) {
313
+ clearTimeout(this._writeTimer);
314
+ this._writeTimer = null;
315
+ }
316
+ this._writeBuffer = '';
317
+
269
318
  if (this.ws) {
270
319
  this.ws.close();
271
320
  this.ws = null;
@@ -146,15 +146,23 @@ class TerminalWidget extends WidgetType {
146
146
 
147
147
  // Get theme from CSS variables
148
148
  const theme = this._getThemeFromCSS();
149
+ const isMobile = /iPhone|iPad|Android|Mobile/i.test(navigator.userAgent);
149
150
 
150
151
  // Create terminal
151
152
  const term = new Terminal({
152
153
  cursorBlink: true,
153
- fontSize: 14,
154
- fontFamily: '"SF Mono", "Fira Code", "Monaco", "Inconsolata", monospace',
155
- scrollback: 10000,
154
+ cursorStyle: 'block',
155
+ cursorInactiveStyle: 'outline',
156
+ fontSize: isMobile ? 13 : 14,
157
+ fontFamily: '"Monaspace Neon Var", "SF Mono", "Fira Code", "Monaco", "Inconsolata", monospace',
158
+ scrollback: isMobile ? 5000 : 50000,
156
159
  convertEol: true,
157
160
  theme: theme,
161
+ drawBoldTextInBrightColors: !isMobile,
162
+ allowTransparency: false,
163
+ smoothScrollDuration: 0,
164
+ fastScrollModifier: 'alt',
165
+ fastScrollSensitivity: isMobile ? 3 : 5,
158
166
  });
159
167
 
160
168
  this.xtermInstance = term;
@@ -176,6 +184,37 @@ class TerminalWidget extends WidgetType {
176
184
  // Open terminal
177
185
  term.open(container);
178
186
 
187
+ // Renderer addons: prefer WebGL on desktop, fallback to Canvas, then DOM.
188
+ let rendererLoaded = false;
189
+ if (!isMobile && typeof WebglAddon !== 'undefined') {
190
+ try {
191
+ const webgl = new WebglAddon.WebglAddon();
192
+ webgl.onContextLoss(() => {
193
+ try { webgl.dispose(); } catch (e) {}
194
+ console.warn('[term] WebGL context lost, falling back to canvas/DOM');
195
+ if (typeof CanvasAddon !== 'undefined') {
196
+ try {
197
+ term.loadAddon(new CanvasAddon.CanvasAddon());
198
+ } catch (canvasErr) {
199
+ console.warn('[term] Canvas addon failed, using DOM fallback:', canvasErr);
200
+ }
201
+ }
202
+ });
203
+ term.loadAddon(webgl);
204
+ rendererLoaded = true;
205
+ } catch (e) {
206
+ console.warn('[term] WebGL failed:', e);
207
+ }
208
+ }
209
+
210
+ if (!rendererLoaded && typeof CanvasAddon !== 'undefined') {
211
+ try {
212
+ term.loadAddon(new CanvasAddon.CanvasAddon());
213
+ } catch (e) {
214
+ console.warn('[term] Canvas addon failed, using DOM fallback:', e);
215
+ }
216
+ }
217
+
179
218
  // Fit to container
180
219
  if (this.fitAddon) {
181
220
  setTimeout(() => {
@@ -266,6 +305,7 @@ class TerminalWidget extends WidgetType {
266
305
  cwd: this.config.cwd,
267
306
  venv: this.config.venv,
268
307
  filePath: this.block.filePath,
308
+ shell: this.config.shell,
269
309
  onData: (data) => {
270
310
  term.write(data);
271
311
  },
@@ -9,7 +9,7 @@
9
9
  * 1. Explicit config (config.appearance.widgetTheme)
10
10
  * 2. CodeMirror theme class (.cm-theme-dark)
11
11
  * 3. System preference (prefers-color-scheme)
12
- * 4. Default: 'wizard-study-dark' (dark)
12
+ * 4. Default: 'plain-light'
13
13
  *
14
14
  * ## Watching for Changes
15
15
  *
@@ -20,7 +20,7 @@
20
20
  * @module widgets/theme-utils
21
21
  */
22
22
 
23
- import { getTheme } from './theme.js';
23
+ import { getTheme, getDefaultTokens } from './theme.js';
24
24
 
25
25
  // #region DETECTION
26
26
 
@@ -31,12 +31,12 @@ import { getTheme } from './theme.js';
31
31
  * 1. Explicit theme name passed as parameter
32
32
  * 2. CodeMirror theme class on the editor element
33
33
  * 3. System color scheme preference
34
- * 4. Default: 'wizard-study-dark'
34
+ * 4. Default: 'plain-light'
35
35
  *
36
36
  * @param {Object} [options]
37
37
  * @param {string} [options.themeName] - Explicit theme name (highest priority)
38
38
  * @param {HTMLElement} [options.editorElement] - Editor DOM element to check for .cm-theme-dark
39
- * @returns {string} Theme name ('wizard-study-dark', 'wizard-study-light', etc.)
39
+ * @returns {string} Theme name ('plain-light', 'plain-dark', etc.)
40
40
  *
41
41
  * @example
42
42
  * // Detect based on CodeMirror and system preference
@@ -57,25 +57,25 @@ export function detectTheme({ themeName, editorElement } = {}) {
57
57
  // CodeMirror adds this class when using oneDark or similar dark themes
58
58
  const hasDarkTheme = editorElement.closest('.cm-theme-dark') !== null;
59
59
  if (hasDarkTheme) {
60
- return 'wizard-study-dark';
60
+ return 'plain-dark';
61
61
  }
62
62
 
63
63
  // If there's any cm-theme class but not dark, assume light
64
64
  const hasAnyTheme = editorElement.matches('[class*="cm-theme"]') ||
65
65
  editorElement.closest('[class*="cm-theme"]') !== null;
66
66
  if (hasAnyTheme) {
67
- return 'wizard-study-light';
67
+ return 'plain-light';
68
68
  }
69
69
  }
70
70
 
71
71
  // 3. System preference
72
72
  if (typeof window !== 'undefined' && window.matchMedia) {
73
73
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
74
- return prefersDark ? 'wizard-study-dark' : 'wizard-study-light';
74
+ return prefersDark ? 'plain-dark' : 'plain-light';
75
75
  }
76
76
 
77
77
  // 4. Default
78
- return 'wizard-study-dark';
78
+ return 'plain-light';
79
79
  }
80
80
 
81
81
  /**
@@ -208,7 +208,7 @@ const THEME_FONTS_ID = 'mrmd-widget-theme-fonts';
208
208
  *
209
209
  * @example
210
210
  * // Apply by name
211
- * applyTheme('wizard-study-dark');
211
+ * applyTheme('plain-light');
212
212
  *
213
213
  * // Apply custom theme object
214
214
  * applyTheme({
@@ -224,12 +224,14 @@ export function applyTheme(themeOrName, { target, useStyleTag = true } = {}) {
224
224
  : themeOrName;
225
225
 
226
226
  if (!theme) {
227
- console.warn(`Theme "${themeOrName}" not found, using midnight`);
228
- return applyTheme('wizard-study-dark', { target, useStyleTag });
227
+ console.warn(`Theme "${themeOrName}" not found, using plain-light`);
228
+ return applyTheme('plain-light', { target, useStyleTag });
229
229
  }
230
230
 
231
231
  // Get token values (exclude name, description, fontFace, isDark)
232
- const tokens = {};
232
+ // Merge with token defaults so newly added tokens are always present,
233
+ // even for older themes that don't define them yet.
234
+ const tokens = getDefaultTokens();
233
235
  for (const [key, value] of Object.entries(theme)) {
234
236
  if (key.startsWith('--')) {
235
237
  tokens[key] = value;
@@ -330,7 +332,7 @@ export function removeThemeStyles() {
330
332
  * @returns {string} CSS string with :root variables and optional font-face
331
333
  *
332
334
  * @example
333
- * const css = generateThemeCSS('wizard-study-dark');
335
+ * const css = generateThemeCSS('plain-light');
334
336
  * // :root {
335
337
  * // --widget-surface: rgba(0, 0, 0, 0.35);
336
338
  * // ...
@@ -346,11 +348,17 @@ export function generateThemeCSS(themeOrName, { includeFontFace = true } = {}) {
346
348
  : themeOrName;
347
349
 
348
350
  if (!theme) {
349
- return generateThemeCSS('wizard-study-dark', { includeFontFace });
351
+ return generateThemeCSS('plain-light', { includeFontFace });
350
352
  }
351
353
 
352
- const vars = Object.entries(theme)
353
- .filter(([k]) => k.startsWith('--'))
354
+ const tokens = getDefaultTokens();
355
+ for (const [key, value] of Object.entries(theme)) {
356
+ if (key.startsWith('--')) {
357
+ tokens[key] = value;
358
+ }
359
+ }
360
+
361
+ const vars = Object.entries(tokens)
354
362
  .map(([k, v]) => ` ${k}: ${v};`)
355
363
  .join('\n');
356
364