mrmd-js 2.0.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/README.md +842 -0
- package/dist/index.cjs +7613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +7530 -0
- package/dist/index.js.map +1 -0
- package/dist/mrmd-js.iife.js +7618 -0
- package/dist/mrmd-js.iife.js.map +1 -0
- package/package.json +47 -0
- package/src/analysis/format.js +371 -0
- package/src/analysis/index.js +18 -0
- package/src/analysis/is-complete.js +394 -0
- package/src/constants.js +44 -0
- package/src/execute/css.js +205 -0
- package/src/execute/html.js +162 -0
- package/src/execute/index.js +41 -0
- package/src/execute/interface.js +144 -0
- package/src/execute/javascript.js +197 -0
- package/src/execute/registry.js +245 -0
- package/src/index.js +136 -0
- package/src/lsp/complete.js +353 -0
- package/src/lsp/format.js +310 -0
- package/src/lsp/hover.js +126 -0
- package/src/lsp/index.js +55 -0
- package/src/lsp/inspect.js +466 -0
- package/src/lsp/parse.js +455 -0
- package/src/lsp/variables.js +283 -0
- package/src/runtime.js +518 -0
- package/src/session/console-capture.js +181 -0
- package/src/session/context/iframe.js +407 -0
- package/src/session/context/index.js +12 -0
- package/src/session/context/interface.js +38 -0
- package/src/session/context/main.js +357 -0
- package/src/session/index.js +16 -0
- package/src/session/manager.js +327 -0
- package/src/session/session.js +678 -0
- package/src/transform/async.js +133 -0
- package/src/transform/extract.js +251 -0
- package/src/transform/index.js +10 -0
- package/src/transform/persistence.js +176 -0
- package/src/types/analysis.js +24 -0
- package/src/types/capabilities.js +44 -0
- package/src/types/completion.js +47 -0
- package/src/types/execution.js +62 -0
- package/src/types/index.js +16 -0
- package/src/types/inspection.js +39 -0
- package/src/types/session.js +32 -0
- package/src/types/streaming.js +74 -0
- package/src/types/variables.js +54 -0
- package/src/utils/ansi-renderer.js +301 -0
- package/src/utils/css-applicator.js +149 -0
- package/src/utils/html-renderer.js +355 -0
- package/src/utils/index.js +25 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iframe Execution Context
|
|
3
|
+
*
|
|
4
|
+
* Executes JavaScript in an isolated iframe environment.
|
|
5
|
+
* Variables persist in the iframe's global scope between executions.
|
|
6
|
+
*
|
|
7
|
+
* @module session/context/iframe
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ConsoleCapture } from '../console-capture.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('./interface.js').ExecutionContext} ExecutionContext
|
|
14
|
+
* @typedef {import('./interface.js').RawExecutionResult} RawExecutionResult
|
|
15
|
+
* @typedef {import('./interface.js').LogEntry} LogEntry
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} StdinRequest
|
|
20
|
+
* @property {string} prompt - Prompt text to display
|
|
21
|
+
* @property {boolean} password - Whether to hide input
|
|
22
|
+
* @property {string} execId - Execution ID for this request
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @callback OnStdinRequestCallback
|
|
27
|
+
* @param {StdinRequest} request - The stdin request
|
|
28
|
+
* @returns {Promise<string>} - Resolves with user input
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} IframeContextOptions
|
|
33
|
+
* @property {boolean} [visible=false] - Whether iframe is visible
|
|
34
|
+
* @property {HTMLElement} [target] - Target element for visible iframe
|
|
35
|
+
* @property {boolean} [allowMainAccess=true] - Allow access to main document
|
|
36
|
+
* @property {Record<string, *>} [utilities] - Custom utilities to inject
|
|
37
|
+
* @property {Partial<CSSStyleDeclaration>} [styles] - Styles for visible iframe
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Iframe-based execution context
|
|
42
|
+
* @implements {ExecutionContext}
|
|
43
|
+
*/
|
|
44
|
+
export class IframeContext {
|
|
45
|
+
/** @type {HTMLIFrameElement | null} */
|
|
46
|
+
#iframe = null;
|
|
47
|
+
|
|
48
|
+
/** @type {Window | null} */
|
|
49
|
+
#ctx = null;
|
|
50
|
+
|
|
51
|
+
/** @type {Set<string>} */
|
|
52
|
+
#trackedVars = new Set();
|
|
53
|
+
|
|
54
|
+
/** @type {ConsoleCapture | null} */
|
|
55
|
+
#consoleCapture = null;
|
|
56
|
+
|
|
57
|
+
/** @type {IframeContextOptions} */
|
|
58
|
+
#options;
|
|
59
|
+
|
|
60
|
+
/** @type {boolean} */
|
|
61
|
+
#initialized = false;
|
|
62
|
+
|
|
63
|
+
/** @type {OnStdinRequestCallback | null} */
|
|
64
|
+
#onStdinRequest = null;
|
|
65
|
+
|
|
66
|
+
/** @type {string} */
|
|
67
|
+
#currentExecId = '';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {IframeContextOptions} [options]
|
|
71
|
+
*/
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.#options = {
|
|
74
|
+
visible: false,
|
|
75
|
+
allowMainAccess: true,
|
|
76
|
+
...options,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the iframe
|
|
82
|
+
*/
|
|
83
|
+
#initialize() {
|
|
84
|
+
if (this.#initialized) return;
|
|
85
|
+
|
|
86
|
+
// Create iframe
|
|
87
|
+
this.#iframe = document.createElement('iframe');
|
|
88
|
+
this.#iframe.sandbox.add('allow-scripts');
|
|
89
|
+
this.#iframe.sandbox.add('allow-same-origin');
|
|
90
|
+
|
|
91
|
+
if (this.#options.visible && this.#options.target) {
|
|
92
|
+
// Visible mode
|
|
93
|
+
const styles = this.#options.styles || {};
|
|
94
|
+
this.#iframe.style.width = styles.width || '100%';
|
|
95
|
+
this.#iframe.style.height = styles.height || '100%';
|
|
96
|
+
this.#iframe.style.border = styles.border || 'none';
|
|
97
|
+
this.#iframe.style.display = 'block';
|
|
98
|
+
|
|
99
|
+
// Apply additional styles
|
|
100
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
101
|
+
if (value && typeof value === 'string') {
|
|
102
|
+
this.#iframe.style.setProperty(key, value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.#options.target.appendChild(this.#iframe);
|
|
107
|
+
} else {
|
|
108
|
+
// Hidden mode
|
|
109
|
+
this.#iframe.style.display = 'none';
|
|
110
|
+
document.body.appendChild(this.#iframe);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get context
|
|
114
|
+
this.#ctx = /** @type {Window} */ (this.#iframe.contentWindow);
|
|
115
|
+
|
|
116
|
+
// Set up utilities
|
|
117
|
+
this.#setupUtilities();
|
|
118
|
+
|
|
119
|
+
// Set up console capture
|
|
120
|
+
this.#consoleCapture = new ConsoleCapture(this.#ctx);
|
|
121
|
+
this.#consoleCapture.start();
|
|
122
|
+
|
|
123
|
+
// Initialize tracking set in iframe
|
|
124
|
+
this.#ctx.__userVars__ = this.#trackedVars;
|
|
125
|
+
|
|
126
|
+
this.#initialized = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Set up utility functions in the context
|
|
131
|
+
*/
|
|
132
|
+
#setupUtilities() {
|
|
133
|
+
if (!this.#ctx) return;
|
|
134
|
+
|
|
135
|
+
// Access to main document
|
|
136
|
+
if (this.#options.allowMainAccess) {
|
|
137
|
+
this.#ctx.mainDocument = document;
|
|
138
|
+
this.#ctx.mainWindow = window;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sleep helper
|
|
142
|
+
this.#ctx.sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
143
|
+
|
|
144
|
+
// Print helper
|
|
145
|
+
this.#ctx.print = (...args) => {
|
|
146
|
+
this.#ctx?.console.log(...args);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Input helper - prompts for user input (like Python's input())
|
|
150
|
+
// Returns a Promise that resolves when user provides input
|
|
151
|
+
const self = this;
|
|
152
|
+
this.#ctx.input = async (prompt = '', options = {}) => {
|
|
153
|
+
// Print prompt to console (like Python does)
|
|
154
|
+
if (prompt) {
|
|
155
|
+
self.#ctx?.console.log(prompt);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If no stdin handler is set, fall back to browser prompt()
|
|
159
|
+
if (!self.#onStdinRequest) {
|
|
160
|
+
const result = self.#ctx?.prompt(prompt) ?? '';
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Request input from the external handler
|
|
165
|
+
const request = {
|
|
166
|
+
prompt: prompt,
|
|
167
|
+
password: options.password ?? false,
|
|
168
|
+
execId: self.#currentExecId,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const response = await self.#onStdinRequest(request);
|
|
173
|
+
// Remove trailing newline if present (input() in Python strips it)
|
|
174
|
+
return response.replace(/\n$/, '');
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// If cancelled, throw an error like Python's KeyboardInterrupt
|
|
177
|
+
throw new Error('Input cancelled');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Display helper for rich output
|
|
182
|
+
this.#ctx.display = (data, mimeType = 'text/plain') => {
|
|
183
|
+
// Store for retrieval
|
|
184
|
+
if (!this.#ctx.__displayQueue__) {
|
|
185
|
+
this.#ctx.__displayQueue__ = [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let content;
|
|
189
|
+
if (typeof data === 'string') {
|
|
190
|
+
content = data;
|
|
191
|
+
} else if (data instanceof HTMLElement) {
|
|
192
|
+
content = data.outerHTML;
|
|
193
|
+
mimeType = 'text/html';
|
|
194
|
+
} else {
|
|
195
|
+
try {
|
|
196
|
+
content = JSON.stringify(data, null, 2);
|
|
197
|
+
mimeType = 'application/json';
|
|
198
|
+
} catch {
|
|
199
|
+
content = String(data);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.#ctx.__displayQueue__.push({ data: { [mimeType]: content }, metadata: {} });
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Inject custom utilities
|
|
207
|
+
if (this.#options.utilities) {
|
|
208
|
+
for (const [key, value] of Object.entries(this.#options.utilities)) {
|
|
209
|
+
this.#ctx[key] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Set the stdin request handler
|
|
216
|
+
* @param {OnStdinRequestCallback | null} handler
|
|
217
|
+
*/
|
|
218
|
+
setStdinHandler(handler) {
|
|
219
|
+
this.#onStdinRequest = handler;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the current stdin request handler
|
|
224
|
+
* @returns {OnStdinRequestCallback | null}
|
|
225
|
+
*/
|
|
226
|
+
getStdinHandler() {
|
|
227
|
+
return this.#onStdinRequest;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Execute code in the iframe
|
|
232
|
+
* @param {string} code - Already transformed/wrapped code from executor
|
|
233
|
+
* @param {{ execId?: string }} [options] - Execution options
|
|
234
|
+
* @returns {Promise<RawExecutionResult>}
|
|
235
|
+
*/
|
|
236
|
+
async execute(code, options = {}) {
|
|
237
|
+
this.#initialize();
|
|
238
|
+
|
|
239
|
+
if (!this.#ctx) {
|
|
240
|
+
throw new Error('Context not initialized');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Set current execution ID for input() calls
|
|
244
|
+
this.#currentExecId = options.execId || '';
|
|
245
|
+
|
|
246
|
+
// Clear display queue
|
|
247
|
+
this.#ctx.__displayQueue__ = [];
|
|
248
|
+
|
|
249
|
+
// Clear console capture
|
|
250
|
+
this.#consoleCapture?.clear();
|
|
251
|
+
|
|
252
|
+
const startTime = performance.now();
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Execute - code is already transformed/wrapped by the executor
|
|
256
|
+
const result = await this.#ctx.eval(code);
|
|
257
|
+
const duration = performance.now() - startTime;
|
|
258
|
+
|
|
259
|
+
// Get logs
|
|
260
|
+
const logs = this.#consoleCapture?.flush() || [];
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
result,
|
|
264
|
+
logs,
|
|
265
|
+
duration,
|
|
266
|
+
};
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const duration = performance.now() - startTime;
|
|
269
|
+
const logs = this.#consoleCapture?.flush() || [];
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
result: undefined,
|
|
273
|
+
logs,
|
|
274
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
275
|
+
duration,
|
|
276
|
+
};
|
|
277
|
+
} finally {
|
|
278
|
+
// Clear current exec ID
|
|
279
|
+
this.#currentExecId = '';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get all user-defined variables
|
|
285
|
+
* @returns {Record<string, *>}
|
|
286
|
+
*/
|
|
287
|
+
getVariables() {
|
|
288
|
+
if (!this.#ctx) return {};
|
|
289
|
+
|
|
290
|
+
const vars = {};
|
|
291
|
+
for (const name of this.#trackedVars) {
|
|
292
|
+
try {
|
|
293
|
+
vars[name] = this.#ctx[name];
|
|
294
|
+
} catch {
|
|
295
|
+
// Skip inaccessible
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return vars;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get a specific variable
|
|
303
|
+
* @param {string} name
|
|
304
|
+
* @returns {*}
|
|
305
|
+
*/
|
|
306
|
+
getVariable(name) {
|
|
307
|
+
if (!this.#ctx) return undefined;
|
|
308
|
+
return this.#ctx[name];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if variable exists
|
|
313
|
+
* @param {string} name
|
|
314
|
+
* @returns {boolean}
|
|
315
|
+
*/
|
|
316
|
+
hasVariable(name) {
|
|
317
|
+
if (!this.#ctx) return false;
|
|
318
|
+
return name in this.#ctx;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get the global object
|
|
323
|
+
* @returns {Window}
|
|
324
|
+
*/
|
|
325
|
+
getGlobal() {
|
|
326
|
+
this.#initialize();
|
|
327
|
+
return /** @type {Window} */ (this.#ctx);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Track a declared variable
|
|
332
|
+
* @param {string} name
|
|
333
|
+
*/
|
|
334
|
+
trackVariable(name) {
|
|
335
|
+
this.#trackedVars.add(name);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get tracked variable names
|
|
340
|
+
* @returns {Set<string>}
|
|
341
|
+
*/
|
|
342
|
+
getTrackedVariables() {
|
|
343
|
+
return this.#trackedVars;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Reset the context
|
|
348
|
+
*/
|
|
349
|
+
reset() {
|
|
350
|
+
if (!this.#initialized) return;
|
|
351
|
+
|
|
352
|
+
// Destroy and reinitialize
|
|
353
|
+
this.destroy();
|
|
354
|
+
this.#initialized = false;
|
|
355
|
+
this.#trackedVars = new Set();
|
|
356
|
+
this.#initialize();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Destroy the context
|
|
361
|
+
*/
|
|
362
|
+
destroy() {
|
|
363
|
+
this.#consoleCapture?.stop();
|
|
364
|
+
this.#consoleCapture = null;
|
|
365
|
+
|
|
366
|
+
if (this.#iframe) {
|
|
367
|
+
this.#iframe.parentElement?.removeChild(this.#iframe);
|
|
368
|
+
this.#iframe = null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.#ctx = null;
|
|
372
|
+
this.#initialized = false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Check if this is main context
|
|
377
|
+
* @returns {boolean}
|
|
378
|
+
*/
|
|
379
|
+
isMainContext() {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get the iframe element
|
|
385
|
+
* @returns {HTMLIFrameElement | null}
|
|
386
|
+
*/
|
|
387
|
+
getIframe() {
|
|
388
|
+
return this.#iframe;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get display data queue
|
|
393
|
+
* @returns {Array<{data: Record<string, string>, metadata: Record<string, *>}>}
|
|
394
|
+
*/
|
|
395
|
+
getDisplayQueue() {
|
|
396
|
+
return this.#ctx?.__displayQueue__ || [];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Create an iframe context
|
|
402
|
+
* @param {IframeContextOptions} [options]
|
|
403
|
+
* @returns {IframeContext}
|
|
404
|
+
*/
|
|
405
|
+
export function createIframeContext(options) {
|
|
406
|
+
return new IframeContext(options);
|
|
407
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Contexts
|
|
3
|
+
*
|
|
4
|
+
* Different execution environments for JavaScript code.
|
|
5
|
+
* @module session/context
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { IframeContext, createIframeContext } from './iframe.js';
|
|
9
|
+
export { MainContext, createMainContext } from './main.js';
|
|
10
|
+
|
|
11
|
+
// Re-export interface types
|
|
12
|
+
export * from './interface.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Context Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for execution contexts (iframe, worker, main).
|
|
5
|
+
* @module session/context/interface
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} LogEntry
|
|
10
|
+
* @property {'log' | 'info' | 'warn' | 'error'} type
|
|
11
|
+
* @property {Array<*>} args
|
|
12
|
+
* @property {number} timestamp
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} RawExecutionResult
|
|
17
|
+
* @property {*} result - Return value
|
|
18
|
+
* @property {LogEntry[]} logs - Captured log entries
|
|
19
|
+
* @property {Error} [error] - Error if execution failed
|
|
20
|
+
* @property {number} duration - Duration in milliseconds
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ExecutionContext
|
|
25
|
+
* @property {(code: string) => Promise<RawExecutionResult>} execute - Execute code
|
|
26
|
+
* @property {() => Record<string, *>} getVariables - Get all user-defined variables
|
|
27
|
+
* @property {(name: string) => *} getVariable - Get a specific variable
|
|
28
|
+
* @property {(name: string) => boolean} hasVariable - Check if variable exists
|
|
29
|
+
* @property {() => Window} getGlobal - Get the global object
|
|
30
|
+
* @property {(name: string) => void} trackVariable - Track a declared variable
|
|
31
|
+
* @property {() => Set<string>} getTrackedVariables - Get tracked variable names
|
|
32
|
+
* @property {() => void} reset - Clear all variables and state
|
|
33
|
+
* @property {() => void} destroy - Cleanup and release resources
|
|
34
|
+
* @property {() => boolean} isMainContext - Whether this is main window
|
|
35
|
+
* @property {() => HTMLIFrameElement | null} getIframe - Get iframe if applicable
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export {};
|