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.
Files changed (52) hide show
  1. package/README.md +842 -0
  2. package/dist/index.cjs +7613 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +7530 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/mrmd-js.iife.js +7618 -0
  7. package/dist/mrmd-js.iife.js.map +1 -0
  8. package/package.json +47 -0
  9. package/src/analysis/format.js +371 -0
  10. package/src/analysis/index.js +18 -0
  11. package/src/analysis/is-complete.js +394 -0
  12. package/src/constants.js +44 -0
  13. package/src/execute/css.js +205 -0
  14. package/src/execute/html.js +162 -0
  15. package/src/execute/index.js +41 -0
  16. package/src/execute/interface.js +144 -0
  17. package/src/execute/javascript.js +197 -0
  18. package/src/execute/registry.js +245 -0
  19. package/src/index.js +136 -0
  20. package/src/lsp/complete.js +353 -0
  21. package/src/lsp/format.js +310 -0
  22. package/src/lsp/hover.js +126 -0
  23. package/src/lsp/index.js +55 -0
  24. package/src/lsp/inspect.js +466 -0
  25. package/src/lsp/parse.js +455 -0
  26. package/src/lsp/variables.js +283 -0
  27. package/src/runtime.js +518 -0
  28. package/src/session/console-capture.js +181 -0
  29. package/src/session/context/iframe.js +407 -0
  30. package/src/session/context/index.js +12 -0
  31. package/src/session/context/interface.js +38 -0
  32. package/src/session/context/main.js +357 -0
  33. package/src/session/index.js +16 -0
  34. package/src/session/manager.js +327 -0
  35. package/src/session/session.js +678 -0
  36. package/src/transform/async.js +133 -0
  37. package/src/transform/extract.js +251 -0
  38. package/src/transform/index.js +10 -0
  39. package/src/transform/persistence.js +176 -0
  40. package/src/types/analysis.js +24 -0
  41. package/src/types/capabilities.js +44 -0
  42. package/src/types/completion.js +47 -0
  43. package/src/types/execution.js +62 -0
  44. package/src/types/index.js +16 -0
  45. package/src/types/inspection.js +39 -0
  46. package/src/types/session.js +32 -0
  47. package/src/types/streaming.js +74 -0
  48. package/src/types/variables.js +54 -0
  49. package/src/utils/ansi-renderer.js +301 -0
  50. package/src/utils/css-applicator.js +149 -0
  51. package/src/utils/html-renderer.js +355 -0
  52. package/src/utils/index.js +25 -0
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Code Completion
3
+ *
4
+ * Provides runtime-aware code completions by introspecting live values
5
+ * in the execution context.
6
+ *
7
+ * @module lsp/complete
8
+ */
9
+
10
+ import {
11
+ parseCompletionContext,
12
+ splitObjectPath,
13
+ isKeyword,
14
+ getKeywords,
15
+ getCommonGlobals,
16
+ } from './parse.js';
17
+
18
+ import {
19
+ formatValueShort,
20
+ getTypeName,
21
+ getCompletionKind,
22
+ getFunctionSignature,
23
+ } from './format.js';
24
+
25
+ /**
26
+ * @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
27
+ * @typedef {import('../types/completion.js').CompletionResult} CompletionResult
28
+ * @typedef {import('../types/completion.js').CompletionItem} CompletionItem
29
+ * @typedef {import('../types/completion.js').CompleteOptions} CompleteOptions
30
+ */
31
+
32
+ /**
33
+ * Get completions at cursor position
34
+ *
35
+ * @param {string} code - The code being edited
36
+ * @param {number} cursor - Cursor position (0-indexed)
37
+ * @param {ExecutionContext} context - Execution context for live values
38
+ * @param {CompleteOptions} [options]
39
+ * @returns {CompletionResult}
40
+ */
41
+ export function getCompletions(code, cursor, context, options = {}) {
42
+ const ctx = parseCompletionContext(code, cursor);
43
+
44
+ // Don't complete inside strings or comments
45
+ if (ctx.type === 'string' || ctx.type === 'comment') {
46
+ return {
47
+ matches: [],
48
+ cursorStart: cursor,
49
+ cursorEnd: cursor,
50
+ source: 'runtime',
51
+ };
52
+ }
53
+
54
+ /** @type {CompletionItem[]} */
55
+ let matches = [];
56
+
57
+ switch (ctx.type) {
58
+ case 'member':
59
+ matches = getMemberCompletions(ctx.object || '', ctx.prefix, context);
60
+ break;
61
+
62
+ case 'bracket':
63
+ matches = getBracketCompletions(ctx.object || '', context);
64
+ break;
65
+
66
+ case 'global':
67
+ default:
68
+ matches = getGlobalCompletions(ctx.prefix, context);
69
+ break;
70
+ }
71
+
72
+ // Filter by prefix
73
+ if (ctx.prefix) {
74
+ const lowerPrefix = ctx.prefix.toLowerCase();
75
+ matches = matches.filter(item =>
76
+ item.label.toLowerCase().startsWith(lowerPrefix)
77
+ );
78
+ }
79
+
80
+ // Sort by priority and name
81
+ matches.sort((a, b) => {
82
+ const priorityDiff = (a.sortPriority ?? 50) - (b.sortPriority ?? 50);
83
+ if (priorityDiff !== 0) return priorityDiff;
84
+ return a.label.localeCompare(b.label);
85
+ });
86
+
87
+ return {
88
+ matches,
89
+ cursorStart: ctx.start,
90
+ cursorEnd: ctx.end,
91
+ source: 'runtime',
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Get completions for member access (dot notation)
97
+ * @param {string} objectPath
98
+ * @param {string} prefix
99
+ * @param {ExecutionContext} context
100
+ * @returns {CompletionItem[]}
101
+ */
102
+ function getMemberCompletions(objectPath, prefix, context) {
103
+ if (!objectPath) return [];
104
+
105
+ // Resolve the object in context
106
+ const value = resolveValue(objectPath, context);
107
+ if (value === undefined && !objectPath.includes('.')) {
108
+ // Check if it's a global
109
+ const global = context.getGlobal();
110
+ if (global && objectPath in global) {
111
+ // @ts-ignore
112
+ return getPropertiesOf(global[objectPath]);
113
+ }
114
+ }
115
+
116
+ if (value === undefined || value === null) {
117
+ return [];
118
+ }
119
+
120
+ return getPropertiesOf(value);
121
+ }
122
+
123
+ /**
124
+ * Get completions for bracket access
125
+ * @param {string} objectPath
126
+ * @param {ExecutionContext} context
127
+ * @returns {CompletionItem[]}
128
+ */
129
+ function getBracketCompletions(objectPath, context) {
130
+ const value = resolveValue(objectPath, context);
131
+
132
+ if (Array.isArray(value)) {
133
+ // Suggest indices
134
+ return value.slice(0, 20).map((_, i) => ({
135
+ label: String(i),
136
+ kind: /** @type {const} */ ('value'),
137
+ detail: getTypeName(value[i]),
138
+ valuePreview: formatValueShort(value[i]),
139
+ sortPriority: 10,
140
+ }));
141
+ }
142
+
143
+ if (value instanceof Map) {
144
+ // Suggest keys
145
+ return Array.from(value.keys()).slice(0, 20).map(key => ({
146
+ label: String(key),
147
+ insertText: typeof key === 'string' ? `"${key}"` : String(key),
148
+ kind: /** @type {const} */ ('property'),
149
+ detail: getTypeName(value.get(key)),
150
+ valuePreview: formatValueShort(value.get(key)),
151
+ sortPriority: 10,
152
+ }));
153
+ }
154
+
155
+ if (typeof value === 'object' && value !== null) {
156
+ // Suggest string keys
157
+ return Object.keys(value).slice(0, 50).map(key => ({
158
+ label: key,
159
+ insertText: `"${key}"`,
160
+ kind: /** @type {const} */ ('property'),
161
+ detail: getTypeName(value[key]),
162
+ valuePreview: formatValueShort(value[key]),
163
+ sortPriority: 10,
164
+ }));
165
+ }
166
+
167
+ return [];
168
+ }
169
+
170
+ /**
171
+ * Get completions for global context
172
+ * @param {string} prefix
173
+ * @param {ExecutionContext} context
174
+ * @returns {CompletionItem[]}
175
+ */
176
+ function getGlobalCompletions(prefix, context) {
177
+ /** @type {CompletionItem[]} */
178
+ const items = [];
179
+
180
+ // 1. User-defined variables (highest priority)
181
+ const userVars = context.getVariables();
182
+ for (const [name, value] of Object.entries(userVars)) {
183
+ items.push({
184
+ label: name,
185
+ kind: getCompletionKind(value),
186
+ detail: getTypeName(value),
187
+ valuePreview: formatValueShort(value),
188
+ type: getTypeName(value),
189
+ sortPriority: 10,
190
+ });
191
+ }
192
+
193
+ // 2. Keywords
194
+ for (const keyword of getKeywords()) {
195
+ items.push({
196
+ label: keyword,
197
+ kind: 'keyword',
198
+ sortPriority: 60,
199
+ });
200
+ }
201
+
202
+ // 3. Common globals
203
+ const global = context.getGlobal();
204
+ for (const name of getCommonGlobals()) {
205
+ if (name in userVars) continue; // Skip if user defined
206
+
207
+ try {
208
+ // @ts-ignore
209
+ const value = global?.[name];
210
+ if (value !== undefined) {
211
+ items.push({
212
+ label: name,
213
+ kind: getCompletionKind(value),
214
+ detail: getTypeName(value),
215
+ type: getTypeName(value),
216
+ sortPriority: 40,
217
+ });
218
+ }
219
+ } catch {
220
+ // Skip inaccessible
221
+ }
222
+ }
223
+
224
+ // 4. Add some built-in globals that might be useful
225
+ const builtinGlobals = ['globalThis', 'window', 'document', 'navigator', 'location'];
226
+ for (const name of builtinGlobals) {
227
+ if (name in userVars) continue;
228
+ try {
229
+ // @ts-ignore
230
+ const value = global?.[name];
231
+ if (value !== undefined) {
232
+ items.push({
233
+ label: name,
234
+ kind: 'variable',
235
+ detail: getTypeName(value),
236
+ sortPriority: 50,
237
+ });
238
+ }
239
+ } catch {
240
+ // Skip inaccessible
241
+ }
242
+ }
243
+
244
+ return items;
245
+ }
246
+
247
+ /**
248
+ * Get all properties of an object as completion items
249
+ * @param {*} value
250
+ * @returns {CompletionItem[]}
251
+ */
252
+ function getPropertiesOf(value) {
253
+ /** @type {CompletionItem[]} */
254
+ const items = [];
255
+ const seen = new Set();
256
+
257
+ // Walk prototype chain
258
+ let obj = value;
259
+ let depth = 0;
260
+
261
+ while (obj != null && depth < 5) {
262
+ const names = Object.getOwnPropertyNames(obj);
263
+
264
+ for (const name of names) {
265
+ if (seen.has(name)) continue;
266
+ if (name === 'constructor') continue; // Skip constructor
267
+ seen.add(name);
268
+
269
+ try {
270
+ const descriptor = Object.getOwnPropertyDescriptor(obj, name);
271
+ const propValue = descriptor?.get ? undefined : value[name];
272
+
273
+ /** @type {CompletionItem} */
274
+ const item = {
275
+ label: name,
276
+ kind: typeof propValue === 'function' ? 'method' : 'property',
277
+ sortPriority: depth === 0 ? 20 : 30 + depth,
278
+ };
279
+
280
+ if (propValue !== undefined) {
281
+ item.detail = getTypeName(propValue);
282
+ item.type = getTypeName(propValue);
283
+
284
+ if (typeof propValue === 'function') {
285
+ item.detail = getFunctionSignature(propValue);
286
+ } else {
287
+ item.valuePreview = formatValueShort(propValue);
288
+ }
289
+ } else if (descriptor?.get) {
290
+ item.detail = '(getter)';
291
+ }
292
+
293
+ items.push(item);
294
+ } catch {
295
+ // Skip inaccessible properties
296
+ items.push({
297
+ label: name,
298
+ kind: 'property',
299
+ detail: '(inaccessible)',
300
+ sortPriority: 90,
301
+ });
302
+ }
303
+ }
304
+
305
+ obj = Object.getPrototypeOf(obj);
306
+ depth++;
307
+ }
308
+
309
+ return items;
310
+ }
311
+
312
+ /**
313
+ * Resolve a value from an object path in the context
314
+ * @param {string} path
315
+ * @param {ExecutionContext} context
316
+ * @returns {*}
317
+ */
318
+ function resolveValue(path, context) {
319
+ const parts = splitObjectPath(path);
320
+ if (parts.length === 0) return undefined;
321
+
322
+ // Start with user variables or global
323
+ let value = context.getVariable(parts[0]);
324
+
325
+ if (value === undefined) {
326
+ // Try global
327
+ const global = context.getGlobal();
328
+ if (global && parts[0] in global) {
329
+ // @ts-ignore
330
+ value = global[parts[0]];
331
+ }
332
+ }
333
+
334
+ if (value === undefined) return undefined;
335
+
336
+ // Navigate path
337
+ for (let i = 1; i < parts.length; i++) {
338
+ if (value === null || value === undefined) return undefined;
339
+
340
+ try {
341
+ if (value instanceof Map) {
342
+ value = value.get(parts[i]);
343
+ } else {
344
+ // @ts-ignore
345
+ value = value[parts[i]];
346
+ }
347
+ } catch {
348
+ return undefined;
349
+ }
350
+ }
351
+
352
+ return value;
353
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Value Formatting Utilities
3
+ *
4
+ * Utilities for formatting JavaScript values for display in
5
+ * completions, hover, and variable inspection.
6
+ *
7
+ * @module lsp/format
8
+ */
9
+
10
+ /**
11
+ * Format a value for display as a string
12
+ * @param {*} value
13
+ * @param {number} [maxLength=1000]
14
+ * @returns {string | undefined}
15
+ */
16
+ export function formatValue(value, maxLength = 1000) {
17
+ if (value === undefined) return undefined;
18
+ if (value === null) return 'null';
19
+
20
+ if (typeof value === 'function') {
21
+ const name = value.name || 'anonymous';
22
+ return `[Function: ${name}]`;
23
+ }
24
+
25
+ if (typeof value === 'symbol') {
26
+ return value.toString();
27
+ }
28
+
29
+ if (value instanceof Error) {
30
+ return `${value.name}: ${value.message}`;
31
+ }
32
+
33
+ if (value instanceof RegExp) {
34
+ return value.toString();
35
+ }
36
+
37
+ if (value instanceof Date) {
38
+ return value.toISOString();
39
+ }
40
+
41
+ if (value instanceof Map) {
42
+ const preview = Array.from(value.entries())
43
+ .slice(0, 5)
44
+ .map(([k, v]) => `${formatValueShort(k)} => ${formatValueShort(v)}`)
45
+ .join(', ');
46
+ const more = value.size > 5 ? `, ... (${value.size} total)` : '';
47
+ return `Map(${value.size}) {${preview}${more}}`;
48
+ }
49
+
50
+ if (value instanceof Set) {
51
+ const preview = Array.from(value)
52
+ .slice(0, 5)
53
+ .map(formatValueShort)
54
+ .join(', ');
55
+ const more = value.size > 5 ? `, ... (${value.size} total)` : '';
56
+ return `Set(${value.size}) {${preview}${more}}`;
57
+ }
58
+
59
+ if (Array.isArray(value)) {
60
+ const preview = value.slice(0, 5).map(formatValueShort).join(', ');
61
+ const more = value.length > 5 ? `, ... (${value.length} total)` : '';
62
+ return `[${preview}${more}]`;
63
+ }
64
+
65
+ if (typeof value === 'object') {
66
+ try {
67
+ const json = JSON.stringify(value, null, 2);
68
+ if (json.length > maxLength) {
69
+ return json.slice(0, maxLength) + '...';
70
+ }
71
+ return json;
72
+ } catch {
73
+ return String(value);
74
+ }
75
+ }
76
+
77
+ const str = String(value);
78
+ if (str.length > maxLength) {
79
+ return str.slice(0, maxLength) + '...';
80
+ }
81
+ return str;
82
+ }
83
+
84
+ /**
85
+ * Format a value for short display (single line, truncated)
86
+ * @param {*} value
87
+ * @param {number} [maxLength=50]
88
+ * @returns {string}
89
+ */
90
+ export function formatValueShort(value, maxLength = 50) {
91
+ if (value === undefined) return 'undefined';
92
+ if (value === null) return 'null';
93
+
94
+ if (typeof value === 'string') {
95
+ const truncated = value.length > maxLength - 2
96
+ ? value.slice(0, maxLength - 5) + '...'
97
+ : value;
98
+ return JSON.stringify(truncated);
99
+ }
100
+
101
+ if (typeof value === 'number' || typeof value === 'boolean') {
102
+ return String(value);
103
+ }
104
+
105
+ if (typeof value === 'function') {
106
+ return `ƒ ${value.name || 'anonymous'}()`;
107
+ }
108
+
109
+ if (typeof value === 'symbol') {
110
+ return value.toString();
111
+ }
112
+
113
+ if (Array.isArray(value)) {
114
+ return `Array(${value.length})`;
115
+ }
116
+
117
+ if (value instanceof Date) {
118
+ return value.toISOString();
119
+ }
120
+
121
+ if (value instanceof RegExp) {
122
+ const str = value.toString();
123
+ return str.length > maxLength ? str.slice(0, maxLength - 3) + '...' : str;
124
+ }
125
+
126
+ if (value instanceof Map) {
127
+ return `Map(${value.size})`;
128
+ }
129
+
130
+ if (value instanceof Set) {
131
+ return `Set(${value.size})`;
132
+ }
133
+
134
+ if (value instanceof Error) {
135
+ return `${value.name}: ${value.message.slice(0, 30)}`;
136
+ }
137
+
138
+ if (typeof value === 'object') {
139
+ const constructor = value.constructor?.name;
140
+ if (constructor && constructor !== 'Object') {
141
+ return constructor;
142
+ }
143
+ const keys = Object.keys(value);
144
+ return `{${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', ...' : ''}}`;
145
+ }
146
+
147
+ return String(value).slice(0, maxLength);
148
+ }
149
+
150
+ /**
151
+ * Get type name for a value
152
+ * @param {*} value
153
+ * @returns {string}
154
+ */
155
+ export function getTypeName(value) {
156
+ if (value === null) return 'null';
157
+ if (value === undefined) return 'undefined';
158
+ if (Array.isArray(value)) return 'Array';
159
+ if (value instanceof Date) return 'Date';
160
+ if (value instanceof RegExp) return 'RegExp';
161
+ if (value instanceof Error) return value.constructor.name;
162
+ if (value instanceof Map) return 'Map';
163
+ if (value instanceof Set) return 'Set';
164
+ if (value instanceof WeakMap) return 'WeakMap';
165
+ if (value instanceof WeakSet) return 'WeakSet';
166
+ if (value instanceof Promise) return 'Promise';
167
+ if (value instanceof ArrayBuffer) return 'ArrayBuffer';
168
+
169
+ // Typed arrays
170
+ if (ArrayBuffer.isView(value)) {
171
+ return value.constructor.name;
172
+ }
173
+
174
+ const type = typeof value;
175
+ if (type === 'object') {
176
+ const constructor = value.constructor;
177
+ if (constructor && constructor.name !== 'Object') {
178
+ return constructor.name;
179
+ }
180
+ return 'Object';
181
+ }
182
+
183
+ return type;
184
+ }
185
+
186
+ /**
187
+ * Get the kind of a value for completion icons
188
+ * @param {*} value
189
+ * @returns {import('../types/completion.js').CompletionKind}
190
+ */
191
+ export function getCompletionKind(value) {
192
+ if (value === null || value === undefined) {
193
+ return 'value';
194
+ }
195
+
196
+ if (typeof value === 'function') {
197
+ // Check if it's a class (constructor)
198
+ if (/^class\s/.test(value.toString())) {
199
+ return 'class';
200
+ }
201
+ return 'function';
202
+ }
203
+
204
+ if (typeof value === 'object') {
205
+ if (Array.isArray(value)) return 'variable';
206
+ if (value instanceof Map || value instanceof Set) return 'variable';
207
+ return 'variable';
208
+ }
209
+
210
+ return 'value';
211
+ }
212
+
213
+ /**
214
+ * Check if a value is expandable (has children)
215
+ * @param {*} value
216
+ * @returns {boolean}
217
+ */
218
+ export function isExpandable(value) {
219
+ if (value === null || value === undefined) return false;
220
+ if (typeof value === 'object') return true;
221
+ if (typeof value === 'function') return true;
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * Get function signature from a function
227
+ * @param {Function} fn
228
+ * @returns {string}
229
+ */
230
+ export function getFunctionSignature(fn) {
231
+ if (typeof fn !== 'function') return '';
232
+
233
+ const str = fn.toString();
234
+
235
+ // Handle arrow functions
236
+ if (str.startsWith('(') || /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=>/.test(str)) {
237
+ const match = str.match(/^(\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
238
+ if (match) {
239
+ const params = match[1].startsWith('(') ? match[1] : `(${match[1]})`;
240
+ return `${params} => ...`;
241
+ }
242
+ }
243
+
244
+ // Handle regular functions
245
+ const funcMatch = str.match(/^(?:async\s+)?function\s*([^(]*)\(([^)]*)\)/);
246
+ if (funcMatch) {
247
+ const name = funcMatch[1].trim() || fn.name || 'anonymous';
248
+ const params = funcMatch[2];
249
+ return `function ${name}(${params})`;
250
+ }
251
+
252
+ // Handle method shorthand
253
+ const methodMatch = str.match(/^(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(([^)]*)\)/);
254
+ if (methodMatch) {
255
+ return `${methodMatch[1]}(${methodMatch[2]})`;
256
+ }
257
+
258
+ // Handle class
259
+ if (str.startsWith('class')) {
260
+ return `class ${fn.name || 'anonymous'}`;
261
+ }
262
+
263
+ // Fallback
264
+ const name = fn.name || 'anonymous';
265
+ const length = fn.length;
266
+ const params = Array(length).fill('arg').map((a, i) => `${a}${i}`).join(', ');
267
+ return `${name}(${params})`;
268
+ }
269
+
270
+ /**
271
+ * Get source code for a function (if available)
272
+ * @param {Function} fn
273
+ * @returns {string | undefined}
274
+ */
275
+ export function getFunctionSource(fn) {
276
+ if (typeof fn !== 'function') return undefined;
277
+
278
+ try {
279
+ const source = fn.toString();
280
+ // Check if it's native code
281
+ if (source.includes('[native code]')) {
282
+ return undefined;
283
+ }
284
+ return source;
285
+ } catch {
286
+ return undefined;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get size description for a value
292
+ * @param {*} value
293
+ * @returns {string | undefined}
294
+ */
295
+ export function getSizeDescription(value) {
296
+ if (Array.isArray(value)) {
297
+ return `${value.length} items`;
298
+ }
299
+ if (value instanceof Map || value instanceof Set) {
300
+ return `${value.size} items`;
301
+ }
302
+ if (typeof value === 'string') {
303
+ return `${value.length} chars`;
304
+ }
305
+ if (typeof value === 'object' && value !== null) {
306
+ const keys = Object.keys(value);
307
+ return `${keys.length} keys`;
308
+ }
309
+ return undefined;
310
+ }