mrmd-editor 0.7.0 → 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/execution.js +69 -15
- package/src/frontmatter-updater.js +204 -74
- package/src/grammar.js +758 -0
- package/src/index.js +1120 -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 +218 -8
- package/src/shell/dialogs/file-picker.js +211 -0
- package/src/shell/layouts/studio.js +229 -14
- package/src/shell/orchestrator-client.js +114 -0
- package/src/shell/styles.js +62 -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 +111 -7
- package/src/term-widget.js +43 -3
- package/src/widgets/theme-utils.js +24 -16
- package/src/widgets/theme.js +1535 -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/term-pty-client.js
CHANGED
|
@@ -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
|
}
|
|
@@ -124,7 +133,20 @@ export class PtyClient {
|
|
|
124
133
|
};
|
|
125
134
|
|
|
126
135
|
this.ws.onmessage = (event) => {
|
|
127
|
-
|
|
136
|
+
// Handle both text and binary messages. Text is the normal case
|
|
137
|
+
// (PTY sends terminal output as text). Binary may arrive if the
|
|
138
|
+
// tunnel proxy incorrectly marks a frame as binary — convert to
|
|
139
|
+
// string so xterm.js can render it (xterm doesn't handle Blobs).
|
|
140
|
+
const data = event.data;
|
|
141
|
+
if (typeof data === 'string') {
|
|
142
|
+
this.config.onData(data);
|
|
143
|
+
} else if (data instanceof Blob) {
|
|
144
|
+
data.text().then(text => this.config.onData(text));
|
|
145
|
+
} else if (data instanceof ArrayBuffer) {
|
|
146
|
+
this.config.onData(new TextDecoder().decode(data));
|
|
147
|
+
} else {
|
|
148
|
+
this.config.onData(data);
|
|
149
|
+
}
|
|
128
150
|
};
|
|
129
151
|
|
|
130
152
|
this.ws.onerror = (event) => {
|
|
@@ -136,6 +158,11 @@ export class PtyClient {
|
|
|
136
158
|
console.log('[PtyClient] Disconnected, code:', event.code);
|
|
137
159
|
this.connected = false;
|
|
138
160
|
this.ws = null;
|
|
161
|
+
if (this._writeTimer) {
|
|
162
|
+
clearTimeout(this._writeTimer);
|
|
163
|
+
this._writeTimer = null;
|
|
164
|
+
}
|
|
165
|
+
this._writeBuffer = '';
|
|
139
166
|
this.config.onDisconnect(event.code, event.reason);
|
|
140
167
|
|
|
141
168
|
// Attempt reconnection if not intentionally closed
|
|
@@ -172,16 +199,87 @@ export class PtyClient {
|
|
|
172
199
|
}
|
|
173
200
|
|
|
174
201
|
/**
|
|
175
|
-
*
|
|
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
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Send data to the PTY (user input).
|
|
218
|
+
* Large payloads (pastes) are chunked to avoid overwhelming the terminal
|
|
219
|
+
* renderer and WebSocket tunnel on markco.dev.
|
|
220
|
+
*
|
|
176
221
|
* @param {string} data - Input data
|
|
177
222
|
*/
|
|
178
223
|
write(data) {
|
|
179
|
-
if (this.ws
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
224
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
225
|
+
|
|
226
|
+
// Small inputs (normal typing/control sequences): buffer briefly so
|
|
227
|
+
// rapid keypresses are batched into fewer WS frames.
|
|
228
|
+
if (data.length <= 4096) {
|
|
229
|
+
this._writeBuffer += data;
|
|
230
|
+
if (!this._writeTimer) {
|
|
231
|
+
this._writeTimer = setTimeout(() => {
|
|
232
|
+
this._writeTimer = null;
|
|
233
|
+
this._flushWriteBuffer();
|
|
234
|
+
}, 8);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
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
|
+
|
|
246
|
+
// Large paste: chunk it to prevent UI freeze.
|
|
247
|
+
// xterm.js may wrap pastes in bracketed paste markers (\x1b[200~ ... \x1b[201~).
|
|
248
|
+
// We preserve those: send a leading marker once, chunk the body, send trailing marker.
|
|
249
|
+
const BPS = '\x1b[200~';
|
|
250
|
+
const BPE = '\x1b[201~';
|
|
251
|
+
let body = data;
|
|
252
|
+
let hasBracket = false;
|
|
253
|
+
|
|
254
|
+
if (body.startsWith(BPS) && body.endsWith(BPE)) {
|
|
255
|
+
hasBracket = true;
|
|
256
|
+
body = body.slice(BPS.length, -BPE.length);
|
|
184
257
|
}
|
|
258
|
+
|
|
259
|
+
const CHUNK_SIZE = 2048;
|
|
260
|
+
const CHUNK_DELAY_MS = 6;
|
|
261
|
+
let offset = 0;
|
|
262
|
+
const ws = this.ws;
|
|
263
|
+
|
|
264
|
+
if (hasBracket) {
|
|
265
|
+
ws.send(JSON.stringify({ type: 'input', data: BPS }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const sendNext = () => {
|
|
269
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
270
|
+
if (offset >= body.length) {
|
|
271
|
+
if (hasBracket) {
|
|
272
|
+
ws.send(JSON.stringify({ type: 'input', data: BPE }));
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const chunk = body.slice(offset, offset + CHUNK_SIZE);
|
|
277
|
+
offset += CHUNK_SIZE;
|
|
278
|
+
ws.send(JSON.stringify({ type: 'input', data: chunk }));
|
|
279
|
+
setTimeout(sendNext, CHUNK_DELAY_MS);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
sendNext();
|
|
185
283
|
}
|
|
186
284
|
|
|
187
285
|
/**
|
|
@@ -211,6 +309,12 @@ export class PtyClient {
|
|
|
211
309
|
this.reconnectTimeout = null;
|
|
212
310
|
}
|
|
213
311
|
|
|
312
|
+
if (this._writeTimer) {
|
|
313
|
+
clearTimeout(this._writeTimer);
|
|
314
|
+
this._writeTimer = null;
|
|
315
|
+
}
|
|
316
|
+
this._writeBuffer = '';
|
|
317
|
+
|
|
214
318
|
if (this.ws) {
|
|
215
319
|
this.ws.close();
|
|
216
320
|
this.ws = null;
|
package/src/term-widget.js
CHANGED
|
@@ -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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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: '
|
|
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,
|
|
23
|
+
import { getTheme, getDefaultTokens } from './theme.js';
|
|
24
24
|
|
|
25
25
|
// #region DETECTION
|
|
26
26
|
|
|
@@ -31,12 +31,12 @@ import { getTheme, midnightTheme, daylightTheme } 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: '
|
|
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 ('
|
|
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 '
|
|
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 '
|
|
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 ? '
|
|
74
|
+
return prefersDark ? 'plain-dark' : 'plain-light';
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// 4. Default
|
|
78
|
-
return '
|
|
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('
|
|
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
|
|
228
|
-
return applyTheme('
|
|
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
|
-
|
|
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('
|
|
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('
|
|
351
|
+
return generateThemeCSS('plain-light', { includeFontFace });
|
|
350
352
|
}
|
|
351
353
|
|
|
352
|
-
const
|
|
353
|
-
|
|
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
|
|