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,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
|
+
}
|