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
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mrmd-js",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MRP-compliant browser JavaScript runtime for mrmd notebooks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rollup -c",
|
|
20
|
+
"dev": "rollup -c -w",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"serve": "node test-app/server.js",
|
|
23
|
+
"demo": "npm run build && npm run serve"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
27
|
+
"jsdom": "^27.4.0",
|
|
28
|
+
"rollup": "^4.9.0",
|
|
29
|
+
"vitest": "^1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mrmd",
|
|
33
|
+
"mrp",
|
|
34
|
+
"notebook",
|
|
35
|
+
"javascript",
|
|
36
|
+
"runtime",
|
|
37
|
+
"repl",
|
|
38
|
+
"lsp",
|
|
39
|
+
"completion",
|
|
40
|
+
"browser"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/anthropics/mrmd-packages"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Formatting
|
|
3
|
+
*
|
|
4
|
+
* Formats JavaScript code. Can integrate with prettier if available,
|
|
5
|
+
* otherwise provides basic formatting.
|
|
6
|
+
*
|
|
7
|
+
* @module analysis/format
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('../types/analysis.js').FormatResult} FormatResult
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} FormatOptions
|
|
16
|
+
* @property {number} [tabWidth=2] - Number of spaces per tab
|
|
17
|
+
* @property {boolean} [useTabs=false] - Use tabs instead of spaces
|
|
18
|
+
* @property {boolean} [semi=true] - Add semicolons
|
|
19
|
+
* @property {boolean} [singleQuote=false] - Use single quotes
|
|
20
|
+
* @property {number} [printWidth=80] - Line width
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** @type {any} */
|
|
24
|
+
let prettierInstance = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set prettier instance for formatting
|
|
28
|
+
* This allows external prettier to be provided
|
|
29
|
+
*
|
|
30
|
+
* @param {any} prettier - Prettier instance
|
|
31
|
+
*/
|
|
32
|
+
export function setPrettier(prettier) {
|
|
33
|
+
prettierInstance = prettier;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if prettier is available
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
export function hasPrettier() {
|
|
41
|
+
return prettierInstance !== null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format JavaScript code
|
|
46
|
+
*
|
|
47
|
+
* @param {string} code - Code to format
|
|
48
|
+
* @param {FormatOptions} [options]
|
|
49
|
+
* @returns {Promise<FormatResult>}
|
|
50
|
+
*/
|
|
51
|
+
export async function formatCode(code, options = {}) {
|
|
52
|
+
// Try prettier first
|
|
53
|
+
if (prettierInstance) {
|
|
54
|
+
try {
|
|
55
|
+
const formatted = await formatWithPrettier(code, options);
|
|
56
|
+
return {
|
|
57
|
+
formatted,
|
|
58
|
+
changed: formatted !== code,
|
|
59
|
+
};
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// Prettier failed, fall back to basic formatting
|
|
62
|
+
console.warn('Prettier formatting failed:', e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fall back to basic formatting
|
|
67
|
+
const formatted = basicFormat(code, options);
|
|
68
|
+
return {
|
|
69
|
+
formatted,
|
|
70
|
+
changed: formatted !== code,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Format with prettier
|
|
76
|
+
* @param {string} code
|
|
77
|
+
* @param {FormatOptions} options
|
|
78
|
+
* @returns {Promise<string>}
|
|
79
|
+
*/
|
|
80
|
+
async function formatWithPrettier(code, options) {
|
|
81
|
+
const prettierOptions = {
|
|
82
|
+
parser: 'babel',
|
|
83
|
+
tabWidth: options.tabWidth ?? 2,
|
|
84
|
+
useTabs: options.useTabs ?? false,
|
|
85
|
+
semi: options.semi ?? true,
|
|
86
|
+
singleQuote: options.singleQuote ?? false,
|
|
87
|
+
printWidth: options.printWidth ?? 80,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// prettier might be async or sync depending on version
|
|
91
|
+
const result = prettierInstance.format(code, prettierOptions);
|
|
92
|
+
return result instanceof Promise ? await result : result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Basic code formatting (no external dependencies)
|
|
97
|
+
*
|
|
98
|
+
* @param {string} code
|
|
99
|
+
* @param {FormatOptions} options
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
export function basicFormat(code, options = {}) {
|
|
103
|
+
const tabWidth = options.tabWidth ?? 2;
|
|
104
|
+
const useTabs = options.useTabs ?? false;
|
|
105
|
+
const semi = options.semi ?? true;
|
|
106
|
+
const indent = useTabs ? '\t' : ' '.repeat(tabWidth);
|
|
107
|
+
|
|
108
|
+
let result = code;
|
|
109
|
+
|
|
110
|
+
// Normalize line endings
|
|
111
|
+
result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
112
|
+
|
|
113
|
+
// Normalize whitespace around operators
|
|
114
|
+
result = normalizeOperatorSpacing(result);
|
|
115
|
+
|
|
116
|
+
// Normalize comma spacing
|
|
117
|
+
result = result.replace(/,\s*/g, ', ');
|
|
118
|
+
|
|
119
|
+
// Normalize colon spacing in objects
|
|
120
|
+
result = normalizeColonSpacing(result);
|
|
121
|
+
|
|
122
|
+
// Fix indentation
|
|
123
|
+
result = fixIndentation(result, indent);
|
|
124
|
+
|
|
125
|
+
// Add/remove trailing semicolons
|
|
126
|
+
if (semi) {
|
|
127
|
+
result = addSemicolons(result);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Remove trailing whitespace
|
|
131
|
+
result = result.split('\n').map(line => line.trimEnd()).join('\n');
|
|
132
|
+
|
|
133
|
+
// Ensure single trailing newline
|
|
134
|
+
result = result.trimEnd() + '\n';
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Normalize spacing around operators
|
|
141
|
+
* @param {string} code
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function normalizeOperatorSpacing(code) {
|
|
145
|
+
// This is tricky because we need to handle strings and regex
|
|
146
|
+
// For now, do a simple replacement that might not be perfect
|
|
147
|
+
|
|
148
|
+
// Binary operators (add spaces around)
|
|
149
|
+
const binaryOps = [
|
|
150
|
+
'===', '!==', '==', '!=',
|
|
151
|
+
'<=', '>=', '<', '>',
|
|
152
|
+
'&&', '||', '??',
|
|
153
|
+
'+=', '-=', '*=', '/=', '%=',
|
|
154
|
+
'**=', '&=', '|=', '^=',
|
|
155
|
+
'<<=', '>>=', '>>>=',
|
|
156
|
+
'=>',
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
let result = code;
|
|
160
|
+
|
|
161
|
+
// Process each operator (order matters - longer first)
|
|
162
|
+
for (const op of binaryOps) {
|
|
163
|
+
const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
164
|
+
// Only if not already properly spaced
|
|
165
|
+
result = result.replace(
|
|
166
|
+
new RegExp(`(\\S)${escaped}(\\S)`, 'g'),
|
|
167
|
+
`$1 ${op} $2`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Single = assignment (but not ==, ===, =>, etc)
|
|
172
|
+
result = result.replace(/(\w)=(?![=>])(\S)/g, '$1 = $2');
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Normalize colon spacing in objects
|
|
179
|
+
* @param {string} code
|
|
180
|
+
* @returns {string}
|
|
181
|
+
*/
|
|
182
|
+
function normalizeColonSpacing(code) {
|
|
183
|
+
// Object property colons: add space after but not before
|
|
184
|
+
// This is imperfect but handles common cases
|
|
185
|
+
return code.replace(/(\w+)\s*:\s*/g, '$1: ');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fix indentation based on bracket depth
|
|
190
|
+
* @param {string} code
|
|
191
|
+
* @param {string} indent
|
|
192
|
+
* @returns {string}
|
|
193
|
+
*/
|
|
194
|
+
function fixIndentation(code, indent) {
|
|
195
|
+
const lines = code.split('\n');
|
|
196
|
+
const result = [];
|
|
197
|
+
let depth = 0;
|
|
198
|
+
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
const trimmed = line.trim();
|
|
201
|
+
|
|
202
|
+
if (!trimmed) {
|
|
203
|
+
result.push('');
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check if line starts with closing bracket
|
|
208
|
+
const startsWithClose = /^[}\])]/.test(trimmed);
|
|
209
|
+
if (startsWithClose && depth > 0) {
|
|
210
|
+
depth--;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Add indentation
|
|
214
|
+
result.push(indent.repeat(depth) + trimmed);
|
|
215
|
+
|
|
216
|
+
// Count bracket changes for next line
|
|
217
|
+
const opens = (trimmed.match(/[{[(]/g) || []).length;
|
|
218
|
+
const closes = (trimmed.match(/[}\])]/g) || []).length;
|
|
219
|
+
depth += opens - closes;
|
|
220
|
+
|
|
221
|
+
// Ensure depth doesn't go negative
|
|
222
|
+
if (depth < 0) depth = 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return result.join('\n');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add semicolons to statements that need them
|
|
230
|
+
* @param {string} code
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
function addSemicolons(code) {
|
|
234
|
+
const lines = code.split('\n');
|
|
235
|
+
const result = [];
|
|
236
|
+
|
|
237
|
+
for (let i = 0; i < lines.length; i++) {
|
|
238
|
+
let line = lines[i];
|
|
239
|
+
const trimmed = line.trim();
|
|
240
|
+
|
|
241
|
+
// Skip empty lines
|
|
242
|
+
if (!trimmed) {
|
|
243
|
+
result.push(line);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Skip lines that don't need semicolons
|
|
248
|
+
const skipPatterns = [
|
|
249
|
+
/^\/\//, // Comment
|
|
250
|
+
/^\/\*/, // Block comment start
|
|
251
|
+
/\*\/$/, // Block comment end
|
|
252
|
+
/^\*/, // Block comment middle
|
|
253
|
+
/^import\s/, // Import (might need semi, but complex)
|
|
254
|
+
/^export\s/, // Export
|
|
255
|
+
/^if\s*\(/, // If
|
|
256
|
+
/^else/, // Else
|
|
257
|
+
/^for\s*\(/, // For
|
|
258
|
+
/^while\s*\(/, // While
|
|
259
|
+
/^do\s*{?$/, // Do
|
|
260
|
+
/^switch\s*\(/, // Switch
|
|
261
|
+
/^try\s*{?$/, // Try
|
|
262
|
+
/^catch\s*\(/, // Catch
|
|
263
|
+
/^finally\s*{?$/, // Finally
|
|
264
|
+
/^function\s/, // Function declaration
|
|
265
|
+
/^class\s/, // Class
|
|
266
|
+
/^async\s+function/, // Async function
|
|
267
|
+
/[{,]\s*$/, // Ends with { or ,
|
|
268
|
+
/^\s*[}\])]/, // Starts with closing bracket
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
let needsSemi = true;
|
|
272
|
+
for (const pattern of skipPatterns) {
|
|
273
|
+
if (pattern.test(trimmed)) {
|
|
274
|
+
needsSemi = false;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Already has semicolon
|
|
280
|
+
if (trimmed.endsWith(';')) {
|
|
281
|
+
needsSemi = false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check if next non-empty line suggests continuation
|
|
285
|
+
if (needsSemi) {
|
|
286
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
287
|
+
const nextTrimmed = lines[j].trim();
|
|
288
|
+
if (!nextTrimmed) continue;
|
|
289
|
+
if (/^[.?[]/.test(nextTrimmed)) {
|
|
290
|
+
// Next line is continuation
|
|
291
|
+
needsSemi = false;
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (needsSemi) {
|
|
298
|
+
// Find where to insert semicolon (before trailing comment)
|
|
299
|
+
const commentMatch = line.match(/^(.*?)(\s*\/\/.*)$/);
|
|
300
|
+
if (commentMatch) {
|
|
301
|
+
line = commentMatch[1] + ';' + commentMatch[2];
|
|
302
|
+
} else {
|
|
303
|
+
line = line.trimEnd() + ';';
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
result.push(line);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return result.join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Format HTML code (basic)
|
|
315
|
+
* @param {string} code
|
|
316
|
+
* @returns {string}
|
|
317
|
+
*/
|
|
318
|
+
export function formatHtml(code) {
|
|
319
|
+
// Very basic HTML formatting
|
|
320
|
+
let result = code;
|
|
321
|
+
|
|
322
|
+
// Normalize line endings
|
|
323
|
+
result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
324
|
+
|
|
325
|
+
// Add newlines after block elements
|
|
326
|
+
result = result.replace(/(<\/(?:div|p|ul|ol|li|h[1-6]|header|footer|section|article|nav|aside|main|table|tr|thead|tbody|form)>)/gi, '$1\n');
|
|
327
|
+
|
|
328
|
+
// Add newlines before block elements
|
|
329
|
+
result = result.replace(/(<(?:div|p|ul|ol|li|h[1-6]|header|footer|section|article|nav|aside|main|table|tr|thead|tbody|form)(?:\s[^>]*)?>)/gi, '\n$1');
|
|
330
|
+
|
|
331
|
+
// Remove multiple blank lines
|
|
332
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
333
|
+
|
|
334
|
+
return result.trim() + '\n';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Format CSS code (basic)
|
|
339
|
+
* @param {string} code
|
|
340
|
+
* @returns {string}
|
|
341
|
+
*/
|
|
342
|
+
export function formatCss(code) {
|
|
343
|
+
let result = code;
|
|
344
|
+
|
|
345
|
+
// Normalize line endings
|
|
346
|
+
result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
347
|
+
|
|
348
|
+
// Add newlines after { and ;
|
|
349
|
+
result = result.replace(/\{/g, ' {\n');
|
|
350
|
+
result = result.replace(/;/g, ';\n');
|
|
351
|
+
result = result.replace(/\}/g, '\n}\n');
|
|
352
|
+
|
|
353
|
+
// Fix property spacing
|
|
354
|
+
result = result.replace(/:\s*/g, ': ');
|
|
355
|
+
|
|
356
|
+
// Fix indentation
|
|
357
|
+
const lines = result.split('\n');
|
|
358
|
+
const formatted = [];
|
|
359
|
+
let depth = 0;
|
|
360
|
+
|
|
361
|
+
for (const line of lines) {
|
|
362
|
+
const trimmed = line.trim();
|
|
363
|
+
if (!trimmed) continue;
|
|
364
|
+
|
|
365
|
+
if (trimmed === '}') depth--;
|
|
366
|
+
formatted.push(' '.repeat(Math.max(0, depth)) + trimmed);
|
|
367
|
+
if (trimmed.endsWith('{')) depth++;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return formatted.join('\n') + '\n';
|
|
371
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Analysis
|
|
3
|
+
*
|
|
4
|
+
* Utilities for analyzing JavaScript code including completeness
|
|
5
|
+
* checking and formatting.
|
|
6
|
+
*
|
|
7
|
+
* @module analysis
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { isComplete, getSuggestedIndent } from './is-complete.js';
|
|
11
|
+
export {
|
|
12
|
+
formatCode,
|
|
13
|
+
basicFormat,
|
|
14
|
+
formatHtml,
|
|
15
|
+
formatCss,
|
|
16
|
+
setPrettier,
|
|
17
|
+
hasPrettier,
|
|
18
|
+
} from './format.js';
|