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,133 @@
1
+ /**
2
+ * Async Transform
3
+ *
4
+ * Wraps code to support top-level await.
5
+ * @module transform/async
6
+ */
7
+
8
+ /**
9
+ * Check if code contains top-level await
10
+ * @param {string} code
11
+ * @returns {boolean}
12
+ */
13
+ function hasTopLevelAwait(code) {
14
+ // Simple check - look for await outside of async function/arrow
15
+ // This is a heuristic; a proper check would need AST parsing
16
+
17
+ // Remove strings, comments, and regex to avoid false positives
18
+ const cleaned = code
19
+ // Remove template literals (simple version)
20
+ .replace(/`[^`]*`/g, '')
21
+ // Remove strings
22
+ .replace(/"(?:[^"\\]|\\.)*"/g, '')
23
+ .replace(/'(?:[^'\\]|\\.)*'/g, '')
24
+ // Remove single-line comments
25
+ .replace(/\/\/[^\n]*/g, '')
26
+ // Remove multi-line comments
27
+ .replace(/\/\*[\s\S]*?\*\//g, '');
28
+
29
+ // Track nesting depth of async contexts
30
+ // This is simplified - real implementation would use AST
31
+ let depth = 0;
32
+ let i = 0;
33
+
34
+ while (i < cleaned.length) {
35
+ // Check for async function or async arrow
36
+ if (cleaned.slice(i, i + 5) === 'async') {
37
+ // Look ahead for function or arrow
38
+ let j = i + 5;
39
+ while (j < cleaned.length && /\s/.test(cleaned[j])) j++;
40
+
41
+ if (
42
+ cleaned.slice(j, j + 8) === 'function' ||
43
+ cleaned[j] === '('
44
+ ) {
45
+ // Found async context start
46
+ depth++;
47
+ }
48
+ }
49
+
50
+ // Track braces for context depth (simplified)
51
+ if (cleaned[i] === '{') {
52
+ // Already in async context, depth increases
53
+ }
54
+ if (cleaned[i] === '}') {
55
+ // Could be end of async context
56
+ if (depth > 0) depth--;
57
+ }
58
+
59
+ // Check for await at top level
60
+ if (cleaned.slice(i, i + 5) === 'await') {
61
+ const before = i > 0 ? cleaned[i - 1] : ' ';
62
+ const after = i + 5 < cleaned.length ? cleaned[i + 5] : ' ';
63
+
64
+ // Check it's a word boundary
65
+ if (!/[a-zA-Z0-9_$]/.test(before) && !/[a-zA-Z0-9_$]/.test(after)) {
66
+ // Found await - check if we're at top level
67
+ // For simplicity, assume any await not deep in braces is top-level
68
+ // A proper implementation would track async function scopes
69
+ return true;
70
+ }
71
+ }
72
+
73
+ i++;
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * Wrap code for top-level await support
81
+ *
82
+ * Transforms code to run in an async IIFE that captures the last expression.
83
+ *
84
+ * @param {string} code - Source code
85
+ * @returns {string} Wrapped code
86
+ */
87
+ export function wrapForAsync(code) {
88
+ const needsAsync = hasTopLevelAwait(code);
89
+
90
+ // We always wrap to capture the return value
91
+ // The wrapper captures the last expression value
92
+
93
+ if (needsAsync) {
94
+ return `(async () => {
95
+ ${code}
96
+ })()`;
97
+ }
98
+
99
+ return `(() => {
100
+ ${code}
101
+ })()`;
102
+ }
103
+
104
+ /**
105
+ * Wrap code and capture the last expression value
106
+ *
107
+ * @param {string} code - Source code
108
+ * @returns {string} Wrapped code that returns last expression
109
+ */
110
+ export function wrapWithLastExpression(code) {
111
+ const needsAsync = hasTopLevelAwait(code);
112
+
113
+ // Find the last expression and make it a return value
114
+ // This is tricky without AST - we use eval trick instead
115
+ const wrapped = `
116
+ ;(${needsAsync ? 'async ' : ''}function() {
117
+ let __result__;
118
+ try {
119
+ __result__ = eval(${JSON.stringify(code)});
120
+ } catch (e) {
121
+ if (e instanceof SyntaxError) {
122
+ // Code might be statements, not expression
123
+ eval(${JSON.stringify(code)});
124
+ __result__ = undefined;
125
+ } else {
126
+ throw e;
127
+ }
128
+ }
129
+ return __result__;
130
+ })()`;
131
+
132
+ return wrapped.trim();
133
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Extract Transform
3
+ *
4
+ * Extracts declared variable names from code.
5
+ * @module transform/extract
6
+ */
7
+
8
+ /**
9
+ * Extract all variable names that will be declared by the code.
10
+ * Handles var, let, const, function, and class declarations.
11
+ *
12
+ * @param {string} code - Source code
13
+ * @returns {string[]} Array of declared variable names
14
+ *
15
+ * @example
16
+ * extractDeclaredVariables('const x = 1; let { a, b } = obj; function foo() {}')
17
+ * // Returns: ['x', 'a', 'b', 'foo']
18
+ */
19
+ export function extractDeclaredVariables(code) {
20
+ const variables = new Set();
21
+
22
+ // Remove strings, comments to avoid false matches
23
+ const cleaned = removeStringsAndComments(code);
24
+
25
+ // Match var/let/const declarations
26
+ // Handles: const x = 1, let x = 1, var x = 1
27
+ // Handles: const { a, b } = obj, const [a, b] = arr
28
+ const varPattern = /\b(?:var|let|const)\s+([^=;]+?)(?:\s*=|\s*;|\s*$)/g;
29
+
30
+ let match;
31
+ while ((match = varPattern.exec(cleaned)) !== null) {
32
+ const declaration = match[1].trim();
33
+ extractNamesFromPattern(declaration, variables);
34
+ }
35
+
36
+ // Match function declarations
37
+ const funcPattern = /\bfunction\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
38
+ while ((match = funcPattern.exec(cleaned)) !== null) {
39
+ variables.add(match[1]);
40
+ }
41
+
42
+ // Match class declarations
43
+ const classPattern = /\bclass\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
44
+ while ((match = classPattern.exec(cleaned)) !== null) {
45
+ variables.add(match[1]);
46
+ }
47
+
48
+ return Array.from(variables);
49
+ }
50
+
51
+ /**
52
+ * Extract variable names from a destructuring pattern or simple identifier
53
+ * @param {string} pattern
54
+ * @param {Set<string>} variables
55
+ */
56
+ function extractNamesFromPattern(pattern, variables) {
57
+ // Simple identifier
58
+ const simpleMatch = pattern.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)$/);
59
+ if (simpleMatch) {
60
+ variables.add(simpleMatch[1]);
61
+ return;
62
+ }
63
+
64
+ // Object destructuring { a, b: c, ...rest }
65
+ if (pattern.startsWith('{')) {
66
+ const inner = pattern.slice(1, -1);
67
+ // Split by comma, handling nested braces
68
+ const parts = splitByComma(inner);
69
+ for (const part of parts) {
70
+ const trimmed = part.trim();
71
+ if (!trimmed) continue;
72
+
73
+ // Handle rest: ...rest
74
+ if (trimmed.startsWith('...')) {
75
+ const name = trimmed.slice(3).trim();
76
+ if (isValidIdentifier(name)) {
77
+ variables.add(name);
78
+ }
79
+ continue;
80
+ }
81
+
82
+ // Handle rename: key: name or key: pattern
83
+ const colonIdx = trimmed.indexOf(':');
84
+ if (colonIdx !== -1) {
85
+ const value = trimmed.slice(colonIdx + 1).trim();
86
+ extractNamesFromPattern(value, variables);
87
+ } else {
88
+ // Simple: key (which is also the variable name)
89
+ const name = trimmed.split('=')[0].trim(); // Handle default values
90
+ if (isValidIdentifier(name)) {
91
+ variables.add(name);
92
+ }
93
+ }
94
+ }
95
+ return;
96
+ }
97
+
98
+ // Array destructuring [a, b, ...rest]
99
+ if (pattern.startsWith('[')) {
100
+ const inner = pattern.slice(1, -1);
101
+ const parts = splitByComma(inner);
102
+ for (const part of parts) {
103
+ const trimmed = part.trim();
104
+ if (!trimmed) continue;
105
+
106
+ // Handle rest: ...rest
107
+ if (trimmed.startsWith('...')) {
108
+ const name = trimmed.slice(3).trim();
109
+ if (isValidIdentifier(name)) {
110
+ variables.add(name);
111
+ }
112
+ continue;
113
+ }
114
+
115
+ // Handle nested destructuring or simple name
116
+ const nameOrPattern = trimmed.split('=')[0].trim();
117
+ extractNamesFromPattern(nameOrPattern, variables);
118
+ }
119
+ return;
120
+ }
121
+
122
+ // Multiple declarations: a, b, c (from var a, b, c)
123
+ if (pattern.includes(',')) {
124
+ const parts = splitByComma(pattern);
125
+ for (const part of parts) {
126
+ const trimmed = part.trim().split('=')[0].trim();
127
+ if (isValidIdentifier(trimmed)) {
128
+ variables.add(trimmed);
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Split string by commas, respecting nested brackets
136
+ * @param {string} str
137
+ * @returns {string[]}
138
+ */
139
+ function splitByComma(str) {
140
+ const parts = [];
141
+ let current = '';
142
+ let depth = 0;
143
+
144
+ for (const char of str) {
145
+ if ((char === '{' || char === '[' || char === '(')) {
146
+ depth++;
147
+ current += char;
148
+ } else if ((char === '}' || char === ']' || char === ')')) {
149
+ depth--;
150
+ current += char;
151
+ } else if (char === ',' && depth === 0) {
152
+ parts.push(current);
153
+ current = '';
154
+ } else {
155
+ current += char;
156
+ }
157
+ }
158
+
159
+ if (current) {
160
+ parts.push(current);
161
+ }
162
+
163
+ return parts;
164
+ }
165
+
166
+ /**
167
+ * Check if string is a valid JavaScript identifier
168
+ * @param {string} name
169
+ * @returns {boolean}
170
+ */
171
+ function isValidIdentifier(name) {
172
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
173
+ }
174
+
175
+ /**
176
+ * Remove strings and comments from code
177
+ * @param {string} code
178
+ * @returns {string}
179
+ */
180
+ function removeStringsAndComments(code) {
181
+ let result = '';
182
+ let i = 0;
183
+
184
+ while (i < code.length) {
185
+ // Single-line comment
186
+ if (code[i] === '/' && code[i + 1] === '/') {
187
+ while (i < code.length && code[i] !== '\n') i++;
188
+ continue;
189
+ }
190
+
191
+ // Multi-line comment
192
+ if (code[i] === '/' && code[i + 1] === '*') {
193
+ i += 2;
194
+ while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) i++;
195
+ i += 2;
196
+ continue;
197
+ }
198
+
199
+ // Template literal
200
+ if (code[i] === '`') {
201
+ result += ' ';
202
+ i++;
203
+ while (i < code.length) {
204
+ if (code[i] === '\\') {
205
+ i += 2;
206
+ continue;
207
+ }
208
+ if (code[i] === '`') {
209
+ i++;
210
+ break;
211
+ }
212
+ if (code[i] === '$' && code[i + 1] === '{') {
213
+ i += 2;
214
+ let depth = 1;
215
+ while (i < code.length && depth > 0) {
216
+ if (code[i] === '{') depth++;
217
+ else if (code[i] === '}') depth--;
218
+ i++;
219
+ }
220
+ continue;
221
+ }
222
+ i++;
223
+ }
224
+ continue;
225
+ }
226
+
227
+ // String
228
+ if (code[i] === '"' || code[i] === "'") {
229
+ const quote = code[i];
230
+ result += ' ';
231
+ i++;
232
+ while (i < code.length) {
233
+ if (code[i] === '\\') {
234
+ i += 2;
235
+ continue;
236
+ }
237
+ if (code[i] === quote) {
238
+ i++;
239
+ break;
240
+ }
241
+ i++;
242
+ }
243
+ continue;
244
+ }
245
+
246
+ result += code[i];
247
+ i++;
248
+ }
249
+
250
+ return result;
251
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Code Transformations
3
+ *
4
+ * Utilities for transforming code for REPL execution.
5
+ * @module transform
6
+ */
7
+
8
+ export { transformForPersistence } from './persistence.js';
9
+ export { wrapForAsync, wrapWithLastExpression } from './async.js';
10
+ export { extractDeclaredVariables } from './extract.js';
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Persistence Transform
3
+ *
4
+ * Transforms const/let declarations to var for persistence across executions.
5
+ * In a REPL, we want variables to persist between cells. const/let are
6
+ * block-scoped and would be lost; var attaches to the global scope.
7
+ *
8
+ * @module transform/persistence
9
+ */
10
+
11
+ /**
12
+ * Transform const/let declarations to var for persistence.
13
+ *
14
+ * @param {string} code - Source code
15
+ * @returns {string} Transformed code
16
+ *
17
+ * @example
18
+ * transformForPersistence('const x = 1; let y = 2;')
19
+ * // Returns: 'var x = 1; var y = 2;'
20
+ */
21
+ export function transformForPersistence(code) {
22
+ // Use a state machine approach to avoid transforming inside strings/comments
23
+ let result = '';
24
+ let i = 0;
25
+ const len = code.length;
26
+
27
+ while (i < len) {
28
+ // Check for single-line comment
29
+ if (code[i] === '/' && code[i + 1] === '/') {
30
+ const start = i;
31
+ i += 2;
32
+ while (i < len && code[i] !== '\n') i++;
33
+ result += code.slice(start, i);
34
+ continue;
35
+ }
36
+
37
+ // Check for multi-line comment
38
+ if (code[i] === '/' && code[i + 1] === '*') {
39
+ const start = i;
40
+ i += 2;
41
+ while (i < len && !(code[i] === '*' && code[i + 1] === '/')) i++;
42
+ i += 2;
43
+ result += code.slice(start, i);
44
+ continue;
45
+ }
46
+
47
+ // Check for template literal
48
+ if (code[i] === '`') {
49
+ const start = i;
50
+ i++;
51
+ while (i < len) {
52
+ if (code[i] === '\\') {
53
+ i += 2;
54
+ continue;
55
+ }
56
+ if (code[i] === '`') {
57
+ i++;
58
+ break;
59
+ }
60
+ // Handle ${...} - need to track nested braces
61
+ if (code[i] === '$' && code[i + 1] === '{') {
62
+ i += 2;
63
+ let braceDepth = 1;
64
+ while (i < len && braceDepth > 0) {
65
+ if (code[i] === '{') braceDepth++;
66
+ else if (code[i] === '}') braceDepth--;
67
+ i++;
68
+ }
69
+ continue;
70
+ }
71
+ i++;
72
+ }
73
+ result += code.slice(start, i);
74
+ continue;
75
+ }
76
+
77
+ // Check for string (single or double quote)
78
+ if (code[i] === '"' || code[i] === "'") {
79
+ const quote = code[i];
80
+ const start = i;
81
+ i++;
82
+ while (i < len) {
83
+ if (code[i] === '\\') {
84
+ i += 2;
85
+ continue;
86
+ }
87
+ if (code[i] === quote) {
88
+ i++;
89
+ break;
90
+ }
91
+ i++;
92
+ }
93
+ result += code.slice(start, i);
94
+ continue;
95
+ }
96
+
97
+ // Check for regex (simple heuristic)
98
+ if (code[i] === '/' && i > 0) {
99
+ const prev = code[i - 1];
100
+ // Regex can follow: ( = : [ ! & | ? { } ; , \n
101
+ if ('(=:[!&|?{};,\n'.includes(prev) || /\s/.test(prev)) {
102
+ const start = i;
103
+ i++;
104
+ while (i < len) {
105
+ if (code[i] === '\\') {
106
+ i += 2;
107
+ continue;
108
+ }
109
+ if (code[i] === '/') {
110
+ i++;
111
+ // Skip flags
112
+ while (i < len && /[gimsuy]/.test(code[i])) i++;
113
+ break;
114
+ }
115
+ if (code[i] === '\n') break; // Invalid regex
116
+ i++;
117
+ }
118
+ result += code.slice(start, i);
119
+ continue;
120
+ }
121
+ }
122
+
123
+ // Check for const/let keywords
124
+ if (isWordBoundary(code, i)) {
125
+ if (code.slice(i, i + 5) === 'const' && isWordBoundary(code, i + 5)) {
126
+ result += 'var';
127
+ i += 5;
128
+ continue;
129
+ }
130
+ if (code.slice(i, i + 3) === 'let' && isWordBoundary(code, i + 3)) {
131
+ result += 'var';
132
+ i += 3;
133
+ continue;
134
+ }
135
+ }
136
+
137
+ result += code[i];
138
+ i++;
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Check if position is at a word boundary
146
+ * @param {string} code
147
+ * @param {number} pos
148
+ * @returns {boolean}
149
+ */
150
+ function isWordBoundary(code, pos) {
151
+ if (pos === 0) return true;
152
+ if (pos >= code.length) return true;
153
+
154
+ const before = code[pos - 1];
155
+ const after = code[pos];
156
+
157
+ const isWordChar = (c) => /[a-zA-Z0-9_$]/.test(c);
158
+
159
+ // Boundary if previous char is not a word char
160
+ if (pos > 0 && isWordChar(before)) return false;
161
+ // Or if position is at end and next char is not word char
162
+ if (pos < code.length && !isWordChar(after)) return true;
163
+
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Check if position after keyword is a word boundary
169
+ * @param {string} code
170
+ * @param {number} pos - Position after the keyword
171
+ * @returns {boolean}
172
+ */
173
+ function isWordBoundaryAfter(code, pos) {
174
+ if (pos >= code.length) return true;
175
+ return !/[a-zA-Z0-9_$]/.test(code[pos]);
176
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Analysis Types
3
+ *
4
+ * Types for code analysis (MRP /is_complete and /format endpoints).
5
+ * @module types/analysis
6
+ */
7
+
8
+ /**
9
+ * @typedef {'complete' | 'incomplete' | 'invalid' | 'unknown'} CompletenessStatus
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} IsCompleteResult
14
+ * @property {CompletenessStatus} status - Completeness status
15
+ * @property {string} [indent] - Suggested indent for continuation
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} FormatResult
20
+ * @property {string} formatted - Formatted code
21
+ * @property {boolean} changed - Whether code was changed
22
+ */
23
+
24
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Capabilities Types
3
+ *
4
+ * Types for runtime capability discovery (MRP /capabilities endpoint).
5
+ * @module types/capabilities
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} Capabilities
10
+ * @property {string} runtime - Runtime identifier
11
+ * @property {string} version - Runtime version
12
+ * @property {string[]} languages - Supported language identifiers
13
+ * @property {Features} features - Feature support flags
14
+ * @property {string} [lspFallback] - LSP fallback WebSocket URL
15
+ * @property {string} defaultSession - Default session ID
16
+ * @property {number} maxSessions - Maximum concurrent sessions
17
+ * @property {BrowserEnvironment} environment - Environment information
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} Features
22
+ * @property {boolean} execute - Execute code and return result
23
+ * @property {boolean} executeStream - Stream execution output
24
+ * @property {boolean} interrupt - Interrupt running execution
25
+ * @property {boolean} complete - Tab completion from live session
26
+ * @property {boolean} inspect - Get symbol info (signature, docs, source)
27
+ * @property {boolean} hover - Quick value/type preview
28
+ * @property {boolean} variables - List variables in namespace
29
+ * @property {boolean} variableExpand - Drill into objects
30
+ * @property {boolean} reset - Clear namespace without destroying session
31
+ * @property {boolean} isComplete - Check if code is complete statement
32
+ * @property {boolean} format - Format/prettify code
33
+ * @property {boolean} assets - Asset support (blob URLs)
34
+ */
35
+
36
+ /**
37
+ * @typedef {Object} BrowserEnvironment
38
+ * @property {string} userAgent - User agent string
39
+ * @property {string} language - Browser language
40
+ * @property {string} platform - Platform
41
+ * @property {boolean} isSecureContext - Is secure context (HTTPS)
42
+ */
43
+
44
+ export {};
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Completion Types
3
+ *
4
+ * Types for code completion (MRP /complete endpoint).
5
+ * @module types/completion
6
+ */
7
+
8
+ /**
9
+ * @typedef {'invoked' | 'character' | 'incomplete'} TriggerKind
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} CompleteOptions
14
+ * @property {string} [session] - Session ID
15
+ * @property {TriggerKind} [triggerKind='invoked'] - What triggered completion
16
+ * @property {string} [triggerCharacter] - Character that triggered
17
+ */
18
+
19
+ /**
20
+ * @typedef {'runtime' | 'lsp' | 'static'} CompletionSource
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} CompletionResult
25
+ * @property {CompletionItem[]} matches - Completion items
26
+ * @property {number} cursorStart - Start of text to replace
27
+ * @property {number} cursorEnd - End of text to replace
28
+ * @property {CompletionSource} source - Where completions came from
29
+ */
30
+
31
+ /**
32
+ * @typedef {'variable' | 'function' | 'method' | 'property' | 'class' | 'module' | 'keyword' | 'constant' | 'field' | 'value' | 'snippet'} CompletionKind
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} CompletionItem
37
+ * @property {string} label - Display label
38
+ * @property {string} [insertText] - Text to insert (if different from label)
39
+ * @property {CompletionKind} kind - Item kind for icon
40
+ * @property {string} [detail] - Short description
41
+ * @property {string} [documentation] - Documentation (markdown)
42
+ * @property {string} [valuePreview] - Live value preview (from runtime)
43
+ * @property {string} [type] - Type string
44
+ * @property {number} [sortPriority] - Sort priority (lower = higher)
45
+ */
46
+
47
+ export {};