rapydscript-ns 0.8.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 (144) hide show
  1. package/.agignore +1 -0
  2. package/.gitattributes +4 -0
  3. package/.github/workflows/ci.yml +38 -0
  4. package/.github/workflows/web-repl-page-deploy.yml +42 -0
  5. package/=template.pyj +5 -0
  6. package/CHANGELOG.md +456 -0
  7. package/CONTRIBUTORS +13 -0
  8. package/HACKING.md +103 -0
  9. package/LICENSE +24 -0
  10. package/README.md +2512 -0
  11. package/TODO.md +327 -0
  12. package/add-toc-to-readme +2 -0
  13. package/bin/export +75 -0
  14. package/bin/rapydscript +70 -0
  15. package/bin/web-repl-export +102 -0
  16. package/build +3 -0
  17. package/package.json +46 -0
  18. package/publish.py +37 -0
  19. package/release/baselib-plain-pretty.js +4370 -0
  20. package/release/baselib-plain-ugly.js +3 -0
  21. package/release/compiler.js +18394 -0
  22. package/release/signatures.json +31 -0
  23. package/session.vim +4 -0
  24. package/setup.cfg +2 -0
  25. package/src/ast.pyj +1356 -0
  26. package/src/baselib-builtins.pyj +279 -0
  27. package/src/baselib-containers.pyj +723 -0
  28. package/src/baselib-errors.pyj +37 -0
  29. package/src/baselib-internal.pyj +421 -0
  30. package/src/baselib-itertools.pyj +97 -0
  31. package/src/baselib-str.pyj +798 -0
  32. package/src/compiler.pyj +36 -0
  33. package/src/errors.pyj +30 -0
  34. package/src/lib/aes.pyj +646 -0
  35. package/src/lib/collections.pyj +695 -0
  36. package/src/lib/elementmaker.pyj +83 -0
  37. package/src/lib/encodings.pyj +126 -0
  38. package/src/lib/functools.pyj +148 -0
  39. package/src/lib/gettext.pyj +569 -0
  40. package/src/lib/itertools.pyj +580 -0
  41. package/src/lib/math.pyj +193 -0
  42. package/src/lib/numpy.pyj +2101 -0
  43. package/src/lib/operator.pyj +11 -0
  44. package/src/lib/pythonize.pyj +20 -0
  45. package/src/lib/random.pyj +118 -0
  46. package/src/lib/re.pyj +470 -0
  47. package/src/lib/traceback.pyj +63 -0
  48. package/src/lib/uuid.pyj +77 -0
  49. package/src/monaco-language-service/analyzer.js +526 -0
  50. package/src/monaco-language-service/builtins.js +543 -0
  51. package/src/monaco-language-service/completions.js +498 -0
  52. package/src/monaco-language-service/diagnostics.js +643 -0
  53. package/src/monaco-language-service/dts.js +550 -0
  54. package/src/monaco-language-service/hover.js +121 -0
  55. package/src/monaco-language-service/index.js +386 -0
  56. package/src/monaco-language-service/scope.js +162 -0
  57. package/src/monaco-language-service/signature.js +144 -0
  58. package/src/output/__init__.pyj +0 -0
  59. package/src/output/classes.pyj +296 -0
  60. package/src/output/codegen.pyj +492 -0
  61. package/src/output/comments.pyj +45 -0
  62. package/src/output/exceptions.pyj +105 -0
  63. package/src/output/functions.pyj +491 -0
  64. package/src/output/literals.pyj +109 -0
  65. package/src/output/loops.pyj +444 -0
  66. package/src/output/modules.pyj +329 -0
  67. package/src/output/operators.pyj +429 -0
  68. package/src/output/statements.pyj +463 -0
  69. package/src/output/stream.pyj +309 -0
  70. package/src/output/treeshake.pyj +182 -0
  71. package/src/output/utils.pyj +72 -0
  72. package/src/parse.pyj +3106 -0
  73. package/src/string_interpolation.pyj +72 -0
  74. package/src/tokenizer.pyj +702 -0
  75. package/src/unicode_aliases.pyj +576 -0
  76. package/src/utils.pyj +192 -0
  77. package/test/_import_one.pyj +37 -0
  78. package/test/_import_two/__init__.pyj +11 -0
  79. package/test/_import_two/level2/__init__.pyj +0 -0
  80. package/test/_import_two/level2/deep.pyj +4 -0
  81. package/test/_import_two/other.pyj +6 -0
  82. package/test/_import_two/sub.pyj +13 -0
  83. package/test/aes_vectors.pyj +421 -0
  84. package/test/annotations.pyj +80 -0
  85. package/test/baselib.pyj +319 -0
  86. package/test/classes.pyj +452 -0
  87. package/test/collections.pyj +152 -0
  88. package/test/decorators.pyj +77 -0
  89. package/test/dict_spread.pyj +76 -0
  90. package/test/docstrings.pyj +39 -0
  91. package/test/elementmaker_test.pyj +45 -0
  92. package/test/ellipsis.pyj +49 -0
  93. package/test/functions.pyj +151 -0
  94. package/test/generators.pyj +41 -0
  95. package/test/generic.pyj +370 -0
  96. package/test/imports.pyj +72 -0
  97. package/test/internationalization.pyj +73 -0
  98. package/test/lint.pyj +164 -0
  99. package/test/loops.pyj +85 -0
  100. package/test/numpy.pyj +734 -0
  101. package/test/omit_function_metadata.pyj +20 -0
  102. package/test/regexp.pyj +55 -0
  103. package/test/repl.pyj +121 -0
  104. package/test/scoped_flags.pyj +76 -0
  105. package/test/starargs.pyj +506 -0
  106. package/test/starred_assign.pyj +104 -0
  107. package/test/str.pyj +198 -0
  108. package/test/subscript_tuple.pyj +53 -0
  109. package/test/unit/fixtures/fibonacci_expected.js +46 -0
  110. package/test/unit/index.js +2989 -0
  111. package/test/unit/language-service-builtins.js +815 -0
  112. package/test/unit/language-service-completions.js +1067 -0
  113. package/test/unit/language-service-dts.js +543 -0
  114. package/test/unit/language-service-hover.js +455 -0
  115. package/test/unit/language-service-scope.js +833 -0
  116. package/test/unit/language-service-signature.js +458 -0
  117. package/test/unit/language-service.js +705 -0
  118. package/test/unit/run-language-service.js +41 -0
  119. package/test/unit/web-repl.js +484 -0
  120. package/tools/build-language-service.js +190 -0
  121. package/tools/cli.js +547 -0
  122. package/tools/compile.js +219 -0
  123. package/tools/compiler.js +108 -0
  124. package/tools/completer.js +131 -0
  125. package/tools/embedded_compiler.js +251 -0
  126. package/tools/export.js +316 -0
  127. package/tools/gettext.js +185 -0
  128. package/tools/ini.js +65 -0
  129. package/tools/lint.js +705 -0
  130. package/tools/msgfmt.js +187 -0
  131. package/tools/repl.js +223 -0
  132. package/tools/self.js +162 -0
  133. package/tools/test.js +118 -0
  134. package/tools/utils.js +128 -0
  135. package/tools/web_repl.js +95 -0
  136. package/try +41 -0
  137. package/web-repl/env.js +74 -0
  138. package/web-repl/index.html +163 -0
  139. package/web-repl/language-service.js +4084 -0
  140. package/web-repl/main.js +254 -0
  141. package/web-repl/prism.css +139 -0
  142. package/web-repl/prism.js +113 -0
  143. package/web-repl/rapydscript.js +435 -0
  144. package/web-repl/sha1.js +25 -0
@@ -0,0 +1,498 @@
1
+ // completions.js — Completion item provider for the RapydScript language service.
2
+ //
3
+ // Usage:
4
+ // import { CompletionEngine } from './completions.js';
5
+ // const engine = new CompletionEngine(analyzer, { virtualFiles, builtinNames });
6
+ // const items = engine.getCompletions(scopeMap, position, linePrefix, monacoKind);
7
+
8
+ import { SourceAnalyzer } from './analyzer.js';
9
+ import { resolve_first_type } from './dts.js';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Context detection
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /**
16
+ * Inspect the text before the cursor and classify what kind of completion
17
+ * is appropriate.
18
+ *
19
+ * Returns one of:
20
+ * { type: 'dot', objectName: string, prefix: string }
21
+ * { type: 'from_import', moduleName: string, prefix: string }
22
+ * { type: 'import', prefix: string }
23
+ * { type: 'identifier', prefix: string }
24
+ *
25
+ * @param {string} linePrefix text from column 1 up to (but not including) the cursor
26
+ * @returns {{ type: string, objectName?: string, moduleName?: string, prefix: string }}
27
+ */
28
+ export function detect_context(linePrefix) {
29
+ // `obj.attr` or `obj.sub.attr` — dot access (captures multi-level path)
30
+ const dot_match = linePrefix.match(/([\w.]+)\.([\w]*)$/);
31
+ if (dot_match) {
32
+ return { type: 'dot', objectName: dot_match[1], prefix: dot_match[2] };
33
+ }
34
+
35
+ // `from modname import partial`
36
+ const from_match = linePrefix.match(/^\s*from\s+([\w.]+)\s+import\s+([\w]*)$/);
37
+ if (from_match) {
38
+ return { type: 'from_import', moduleName: from_match[1], prefix: from_match[2] };
39
+ }
40
+
41
+ // `import partial` (bare import at start of line)
42
+ const import_match = linePrefix.match(/^\s*import\s+([\w.]*)$/);
43
+ if (import_match) {
44
+ return { type: 'import', prefix: import_match[1] };
45
+ }
46
+
47
+ // Default: identifier completion
48
+ const ident_match = linePrefix.match(/(\w*)$/);
49
+ return { type: 'identifier', prefix: ident_match ? ident_match[1] : '' };
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Monaco item helpers
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Build a Monaco IRange covering the word currently being typed.
58
+ * @param {{lineNumber:number,column:number}} position Monaco 1-indexed position
59
+ * @param {string} prefix the portion of the word already typed
60
+ * @returns {{startLineNumber,startColumn,endLineNumber,endColumn}}
61
+ */
62
+ function word_range(position, prefix) {
63
+ return {
64
+ startLineNumber: position.lineNumber,
65
+ startColumn: position.column - prefix.length,
66
+ endLineNumber: position.lineNumber,
67
+ endColumn: position.column,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Map a SymbolInfo kind to a Monaco CompletionItemKind value.
73
+ * monacoKind is the Monaco.languages.CompletionItemKind enum object.
74
+ * @param {string} kind
75
+ * @param {object} monacoKind
76
+ * @returns {number}
77
+ */
78
+ function kind_to_monaco(kind, monacoKind) {
79
+ switch (kind) {
80
+ case 'function': return monacoKind.Function;
81
+ case 'method': return monacoKind.Method;
82
+ case 'class': return monacoKind.Class;
83
+ case 'parameter': return monacoKind.Variable;
84
+ case 'import': return monacoKind.Module;
85
+ default: return monacoKind.Variable;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Build a Monaco CompletionItem from a SymbolInfo.
91
+ * @param {import('./scope.js').SymbolInfo} sym
92
+ * @param {object} range Monaco IRange
93
+ * @param {object} monacoKind Monaco CompletionItemKind enum
94
+ * @param {string} sortPrefix e.g. '0', '1', '2' for ranking
95
+ * @returns {object} Monaco CompletionItem
96
+ */
97
+ function symbol_to_item(sym, range, monacoKind, sortPrefix) {
98
+ let detail = sym.kind;
99
+ if (sym.params) {
100
+ const param_str = sym.params.map(p => {
101
+ if (p.is_separator) return p.name; // '/' or '*'
102
+ if (p.is_kwargs) return '**' + p.name;
103
+ if (p.is_rest) return '*' + p.name;
104
+ return p.name;
105
+ }).join(', ');
106
+ detail = '(' + param_str + ')';
107
+ }
108
+ return {
109
+ label: sym.name,
110
+ kind: kind_to_monaco(sym.kind, monacoKind),
111
+ detail,
112
+ documentation: sym.doc || undefined,
113
+ sortText: sortPrefix + '_' + sym.name,
114
+ insertText: sym.name,
115
+ range,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Build a simple completion item from a plain name string (e.g. a builtin with no stub).
121
+ * @param {string} name
122
+ * @param {object} range
123
+ * @param {object} monacoKind
124
+ * @returns {object}
125
+ */
126
+ function name_to_item(name, range, monacoKind) {
127
+ return {
128
+ label: name,
129
+ kind: monacoKind.Variable,
130
+ sortText: '2_' + name,
131
+ insertText: name,
132
+ range,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Build a Monaco CompletionItem from a BuiltinInfo stub (richer than name_to_item).
138
+ * @param {import('./builtins.js').BuiltinInfo} stub
139
+ * @param {object} range
140
+ * @param {object} monacoKind
141
+ * @returns {object}
142
+ */
143
+ function _builtin_to_item(stub, range, monacoKind) {
144
+ const kind = stub.kind === 'function' ? monacoKind.Function :
145
+ stub.kind === 'class' ? monacoKind.Class : monacoKind.Variable;
146
+ let detail = stub.kind;
147
+ if (stub.params) {
148
+ const ps = stub.params.map(function (p) {
149
+ let s = p.label;
150
+ if (p.type && p.type !== 'any') s += ': ' + p.type;
151
+ return s;
152
+ }).join(', ');
153
+ detail = '(' + ps + ')';
154
+ if (stub.return_type && stub.return_type !== 'None') {
155
+ detail += ' → ' + stub.return_type;
156
+ }
157
+ } else if (stub.return_type) {
158
+ detail = stub.return_type;
159
+ }
160
+ return {
161
+ label: stub.name,
162
+ kind,
163
+ detail,
164
+ documentation: stub.doc || undefined,
165
+ sortText: '2_' + stub.name,
166
+ insertText: stub.name,
167
+ range,
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Build a Monaco CompletionItem from a DTS TypeInfo member (method or property).
173
+ * @param {import('./dts.js').TypeInfo} member
174
+ * @param {object} range
175
+ * @param {object} monacoKind
176
+ * @returns {object}
177
+ */
178
+ function _dts_member_to_item(member, range, monacoKind) {
179
+ const kind = member.kind === 'method' ? monacoKind.Method : monacoKind.Property;
180
+ let detail = member.kind;
181
+ if (member.kind === 'method' && member.params) {
182
+ const ps = member.params.map(function (p) {
183
+ let s = p.rest ? '...' : '';
184
+ s += p.name;
185
+ if (p.optional) s += '?';
186
+ return s;
187
+ }).join(', ');
188
+ detail = '(' + ps + ')';
189
+ if (member.return_type && member.return_type !== 'void') {
190
+ detail += ': ' + member.return_type;
191
+ }
192
+ } else if (member.return_type) {
193
+ detail = member.return_type;
194
+ }
195
+ return {
196
+ label: member.name,
197
+ kind,
198
+ detail,
199
+ documentation: member.doc || undefined,
200
+ sortText: '0_' + member.name,
201
+ insertText: member.name,
202
+ range,
203
+ };
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // CompletionEngine
208
+ // ---------------------------------------------------------------------------
209
+
210
+ export class CompletionEngine {
211
+ /**
212
+ * @param {import('./analyzer.js').SourceAnalyzer} analyzer
213
+ * @param {object} opts
214
+ * @param {object} [opts.virtualFiles] module-name → source
215
+ * @param {object} [opts.stdlibFiles] stdlib module-name → source (fallback for from X import)
216
+ * @param {string[]} [opts.builtinNames] names always available (BASE_BUILTINS + extras)
217
+ * @param {import('./dts.js').DtsRegistry|null} [opts.dtsRegistry] DTS globals for dot completion
218
+ * @param {import('./builtins.js').BuiltinsRegistry|null} [opts.builtinsRegistry] stubs for rich builtin items
219
+ */
220
+ constructor(analyzer, opts) {
221
+ this._analyzer = analyzer;
222
+ this._virtualFiles = opts.virtualFiles || {};
223
+ this._stdlibFiles = opts.stdlibFiles || {};
224
+ this._builtinNames = opts.builtinNames || [];
225
+ this._dts = opts.dtsRegistry || null;
226
+ this._builtins = opts.builtinsRegistry || null;
227
+ }
228
+
229
+ /**
230
+ * Update the virtual files available to import analysis.
231
+ * @param {object} virtualFiles
232
+ */
233
+ setVirtualFiles(virtualFiles) {
234
+ this._virtualFiles = virtualFiles;
235
+ }
236
+
237
+ /**
238
+ * Produce Monaco completion items for the given position.
239
+ *
240
+ * @param {import('./scope.js').ScopeMap|null} scopeMap
241
+ * @param {{lineNumber:number,column:number}} position 1-indexed Monaco position
242
+ * @param {string} linePrefix text on the current line up to the cursor
243
+ * @param {object} monacoKind Monaco.languages.CompletionItemKind enum
244
+ * @returns {{ suggestions: object[] }} Monaco CompletionList
245
+ */
246
+ getCompletions(scopeMap, position, linePrefix, monacoKind) {
247
+ const ctx = detect_context(linePrefix);
248
+
249
+ if (ctx.type === 'dot') {
250
+ return { suggestions: this._dot_completions(scopeMap, position, ctx, monacoKind) };
251
+ }
252
+ if (ctx.type === 'from_import') {
253
+ return { suggestions: this._from_import_completions(position, ctx, monacoKind) };
254
+ }
255
+ if (ctx.type === 'import') {
256
+ return { suggestions: this._module_name_completions(position, ctx, monacoKind) };
257
+ }
258
+ // Default: identifier completions from scope + builtins
259
+ return { suggestions: this._scope_completions(scopeMap, position, ctx, monacoKind) };
260
+ }
261
+
262
+ // ---- Identifier completions (scope + builtins) -------------------------
263
+
264
+ _scope_completions(scopeMap, position, ctx, monacoKind) {
265
+ const range = word_range(position, ctx.prefix);
266
+ const items = [];
267
+ const seen = new Set();
268
+
269
+ if (scopeMap) {
270
+ const symbols = scopeMap.getSymbolsAtPosition(position.lineNumber, position.column);
271
+ for (const sym of symbols) {
272
+ if (!ctx.prefix || sym.name.startsWith(ctx.prefix)) {
273
+ if (!seen.has(sym.name)) {
274
+ seen.add(sym.name);
275
+ const sort = sym.scope_depth > 0 ? '0' : '1';
276
+ items.push(symbol_to_item(sym, range, monacoKind, sort));
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ // Builtins (lowest priority) — use rich stub item when available
283
+ for (const name of this._builtinNames) {
284
+ if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
285
+ seen.add(name);
286
+ const stub = this._builtins ? this._builtins.get(name) : null;
287
+ items.push(stub
288
+ ? _builtin_to_item(stub, range, monacoKind)
289
+ : name_to_item(name, range, monacoKind)
290
+ );
291
+ }
292
+ }
293
+
294
+ return items;
295
+ }
296
+
297
+ // ---- Dot completions ---------------------------------------------------
298
+
299
+ _dot_completions(scopeMap, position, ctx, monacoKind) {
300
+ const range = word_range(position, ctx.prefix);
301
+ const items = [];
302
+ const seen = new Set();
303
+ let scope_matched = false;
304
+ let obj_sym = null;
305
+
306
+ // 0. Multi-level path (e.g. 'ns.hacknet') — resolve via DTS type chain only.
307
+ if (ctx.objectName.includes('.') && this._dts) {
308
+ const parts = ctx.objectName.split('.');
309
+ // Resolve root symbol
310
+ let ti = this._dts.getGlobal(parts[0]);
311
+ // Follow first var → type reference (e.g. var ns: NS → NS interface)
312
+ if (ti && !ti.members && ti.return_type) {
313
+ ti = this._dts.getGlobal(resolve_first_type(ti.return_type));
314
+ }
315
+ // Walk remaining path segments through member types
316
+ for (let i = 1; i < parts.length && ti; i++) {
317
+ const member = ti.members ? ti.members.get(parts[i]) : null;
318
+ if (!member) { ti = null; break; }
319
+ if (member.members) {
320
+ ti = member;
321
+ } else if (member.return_type) {
322
+ ti = this._dts.getGlobal(resolve_first_type(member.return_type));
323
+ } else {
324
+ ti = null;
325
+ }
326
+ }
327
+ if (ti && ti.members) {
328
+ for (const [name, member] of ti.members) {
329
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
330
+ items.push(_dts_member_to_item(member, range, monacoKind));
331
+ }
332
+ }
333
+ }
334
+ return items;
335
+ }
336
+
337
+ // 1. ScopeMap lookup — user-defined classes and inferred instances.
338
+ if (scopeMap) {
339
+ obj_sym = scopeMap.getSymbol(
340
+ ctx.objectName,
341
+ position.lineNumber,
342
+ position.column
343
+ );
344
+ // Fallback: the cursor may be past the end of all parsed scope ranges
345
+ // (e.g. the user is typing on a new line that isn't in the last debounced
346
+ // parse yet). Search all frames innermost-first so we still find the symbol.
347
+ if (!obj_sym) {
348
+ const all = scopeMap.frames.slice().sort((a, b) => b.depth - a.depth);
349
+ for (const frame of all) {
350
+ const sym = frame.getSymbol(ctx.objectName);
351
+ if (sym) { obj_sym = sym; break; }
352
+ }
353
+ }
354
+
355
+ let class_name = null;
356
+ if (obj_sym) {
357
+ if (obj_sym.kind === 'class') {
358
+ class_name = obj_sym.name;
359
+ } else if (obj_sym.inferred_class) {
360
+ class_name = obj_sym.inferred_class;
361
+ }
362
+ }
363
+
364
+ if (class_name) {
365
+ for (const frame of scopeMap.frames) {
366
+ if (frame.kind === 'class' && frame.name === class_name) {
367
+ scope_matched = true;
368
+ for (const [name, sym] of frame.symbols) {
369
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
370
+ if (!seen.has(name)) {
371
+ seen.add(name);
372
+ items.push(symbol_to_item(sym, range, monacoKind, '0'));
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ // 1.5. Built-in type members — list, str, dict, number.
382
+ // Used when inferred_class names a built-in type, not a user class.
383
+ if (!scope_matched && this._builtins && obj_sym && obj_sym.inferred_class) {
384
+ const members = this._builtins.getTypeMembers(obj_sym.inferred_class);
385
+ if (members) {
386
+ scope_matched = true;
387
+ for (const [name, member] of members) {
388
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
389
+ if (!seen.has(name)) {
390
+ seen.add(name);
391
+ items.push(_dts_member_to_item(member, range, monacoKind));
392
+ }
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ // 1.75. DTS return-type resolution — variables assigned from DTS method calls.
399
+ // e.g. `server = ns.getServer(...)` → resolve return type of ns.getServer from DTS.
400
+ if (!scope_matched && this._dts && obj_sym && obj_sym.dts_call_path) {
401
+ const call_path = obj_sym.dts_call_path;
402
+ const last_dot = call_path.lastIndexOf('.');
403
+ if (last_dot > 0) {
404
+ const object_path = call_path.slice(0, last_dot);
405
+ const method_name = call_path.slice(last_dot + 1);
406
+ const member_ti = this._dts.getMemberInfo(object_path, method_name);
407
+ if (member_ti && member_ti.return_type) {
408
+ const return_ti = this._dts.getGlobal(resolve_first_type(member_ti.return_type));
409
+ if (return_ti && return_ti.members) {
410
+ scope_matched = true;
411
+ for (const [name, member] of return_ti.members) {
412
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
413
+ if (!seen.has(name)) {
414
+ seen.add(name);
415
+ items.push(_dts_member_to_item(member, range, monacoKind));
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ // 2. DTS registry fallback — namespaces / interfaces / classes from .d.ts.
425
+ // Skipped when ScopeMap already matched a class (ScopeMap wins).
426
+ if (!scope_matched && this._dts) {
427
+ let ti = this._dts.getGlobal(ctx.objectName);
428
+ // Follow type reference: `var console: Console` → look up `Console`
429
+ if (ti && !ti.members && ti.return_type) {
430
+ ti = this._dts.getGlobal(resolve_first_type(ti.return_type));
431
+ }
432
+ if (ti && ti.members) {
433
+ for (const [name, member] of ti.members) {
434
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
435
+ if (!seen.has(name)) {
436
+ seen.add(name);
437
+ items.push(_dts_member_to_item(member, range, monacoKind));
438
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+
444
+ return items;
445
+ }
446
+
447
+ // ---- from X import completions -----------------------------------------
448
+
449
+ _from_import_completions(position, ctx, monacoKind) {
450
+ const range = word_range(position, ctx.prefix);
451
+ const items = [];
452
+
453
+ const src = this._virtualFiles[ctx.moduleName] || this._stdlibFiles[ctx.moduleName];
454
+ if (!src) return items;
455
+
456
+ let scopeMap;
457
+ try {
458
+ scopeMap = this._analyzer.analyze(src, {});
459
+ } catch (_e) {
460
+ return items;
461
+ }
462
+
463
+ // Return all module-level symbols
464
+ const module_frame = scopeMap.frames.find(f => f.kind === 'module');
465
+ if (!module_frame) return items;
466
+
467
+ for (const [name, sym] of module_frame.symbols) {
468
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
469
+ items.push(symbol_to_item(sym, range, monacoKind, '0'));
470
+ }
471
+ }
472
+ return items;
473
+ }
474
+
475
+ // ---- import module name completions ------------------------------------
476
+
477
+ _module_name_completions(position, ctx, monacoKind) {
478
+ const range = word_range(position, ctx.prefix);
479
+ const items = [];
480
+ const seen = new Set();
481
+
482
+ for (const modname of [...Object.keys(this._virtualFiles), ...Object.keys(this._stdlibFiles)]) {
483
+ if (!ctx.prefix || modname.startsWith(ctx.prefix)) {
484
+ if (!seen.has(modname)) {
485
+ seen.add(modname);
486
+ items.push({
487
+ label: modname,
488
+ kind: monacoKind.Module,
489
+ sortText: '0_' + modname,
490
+ insertText: modname,
491
+ range,
492
+ });
493
+ }
494
+ }
495
+ }
496
+ return items;
497
+ }
498
+ }