treelsp 0.0.1
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/LICENSE +21 -0
- package/dist/codegen/ast-types.d.ts +9 -0
- package/dist/codegen/grammar.d.ts +9 -0
- package/dist/codegen/highlights.d.ts +9 -0
- package/dist/codegen/index.d.ts +9 -0
- package/dist/codegen/index.js +983 -0
- package/dist/codegen/locals.d.ts +9 -0
- package/dist/codegen/server.d.ts +31 -0
- package/dist/defaults/completion.d.ts +18 -0
- package/dist/defaults/hover.d.ts +11 -0
- package/dist/defaults/index.d.ts +4 -0
- package/dist/defaults/symbols.d.ts +10 -0
- package/dist/defaults/validation.d.ts +19 -0
- package/dist/define.d.ts +25 -0
- package/dist/definition/grammar.d.ts +36 -0
- package/dist/definition/index.d.ts +33 -0
- package/dist/definition/lsp.d.ts +93 -0
- package/dist/definition/semantic.d.ts +80 -0
- package/dist/definition/validation.d.ts +62 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +148 -0
- package/dist/runtime/index.d.ts +19 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/lsp/completion.d.ts +24 -0
- package/dist/runtime/lsp/context.d.ts +59 -0
- package/dist/runtime/lsp/definition.d.ts +23 -0
- package/dist/runtime/lsp/diagnostics.d.ts +33 -0
- package/dist/runtime/lsp/documents.d.ts +55 -0
- package/dist/runtime/lsp/hover.d.ts +30 -0
- package/dist/runtime/lsp/references.d.ts +28 -0
- package/dist/runtime/lsp/rename.d.ts +50 -0
- package/dist/runtime/lsp/semantic-tokens.d.ts +32 -0
- package/dist/runtime/lsp/server.d.ts +52 -0
- package/dist/runtime/lsp/symbols.d.ts +36 -0
- package/dist/runtime/parser/index.d.ts +7 -0
- package/dist/runtime/parser/node.d.ts +185 -0
- package/dist/runtime/parser/tree.d.ts +153 -0
- package/dist/runtime/parser/wasm.d.ts +32 -0
- package/dist/runtime/scope/index.d.ts +7 -0
- package/dist/runtime/scope/resolver.d.ts +49 -0
- package/dist/runtime/scope/scope.d.ts +127 -0
- package/dist/runtime/scope/workspace.d.ts +104 -0
- package/dist/runtime-CnCx2QS4.js +1914 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +230 -0
- package/package.json +64 -0
|
@@ -0,0 +1,1914 @@
|
|
|
1
|
+
import Parser from "web-tree-sitter";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/parser/node.ts
|
|
4
|
+
/**
|
|
5
|
+
* AST node wrapper providing a friendly API over Tree-sitter's SyntaxNode
|
|
6
|
+
*
|
|
7
|
+
* This class matches the usage patterns from DESIGN.md:
|
|
8
|
+
* - node.field('name') - access named fields
|
|
9
|
+
* - node.fields('params') - access repeated fields
|
|
10
|
+
* - node.field('type')?.text - optional chaining
|
|
11
|
+
* - node.hasChild('export') - check field existence
|
|
12
|
+
*/
|
|
13
|
+
var ASTNode = class ASTNode {
|
|
14
|
+
/**
|
|
15
|
+
* Wrapped Tree-sitter SyntaxNode
|
|
16
|
+
* Kept private to force API usage
|
|
17
|
+
*/
|
|
18
|
+
syntaxNode;
|
|
19
|
+
/**
|
|
20
|
+
* Source text provider (optional - for efficient text access)
|
|
21
|
+
* If not provided, uses syntaxNode.text which is slower
|
|
22
|
+
*/
|
|
23
|
+
sourceProvider;
|
|
24
|
+
constructor(syntaxNode, sourceProvider) {
|
|
25
|
+
this.syntaxNode = syntaxNode;
|
|
26
|
+
this.sourceProvider = sourceProvider;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Internal accessor for other runtime modules
|
|
30
|
+
* Not part of the public API — prefixed with underscore
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
get _syntaxNode() {
|
|
34
|
+
return this.syntaxNode;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get single child by field name
|
|
38
|
+
* Returns null if field doesn't exist or has no value
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const name = node.field('name');
|
|
43
|
+
* if (name) {
|
|
44
|
+
* console.log(name.text);
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
field(name) {
|
|
49
|
+
const child = this.syntaxNode.childForFieldName(name);
|
|
50
|
+
if (!child) return null;
|
|
51
|
+
return new ASTNode(child, this.sourceProvider);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get all children for a field (handles repeated fields)
|
|
55
|
+
* Returns empty array if field doesn't exist
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const params = node.fields('params');
|
|
60
|
+
* for (const param of params) {
|
|
61
|
+
* console.log(param.field('name')?.text);
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
fields(name) {
|
|
66
|
+
const children = this.syntaxNode.childrenForFieldName(name);
|
|
67
|
+
return children.map((child) => new ASTNode(child, this.sourceProvider));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Alias for field() - matches Tree-sitter API
|
|
71
|
+
* Provided for users familiar with Tree-sitter
|
|
72
|
+
*/
|
|
73
|
+
childForFieldName(name) {
|
|
74
|
+
return this.field(name);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if node has a child with given field name
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* const visibility = node.hasChild('export') ? 'public' : 'private';
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
hasChild(name) {
|
|
85
|
+
return this.syntaxNode.childForFieldName(name) !== null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Stable node identity (from Tree-sitter SyntaxNode.id)
|
|
89
|
+
* Safe to use as Map key — same tree node always returns same id
|
|
90
|
+
*/
|
|
91
|
+
get id() {
|
|
92
|
+
return this.syntaxNode.id;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Node type from grammar (e.g., 'variable_decl', 'identifier')
|
|
96
|
+
*/
|
|
97
|
+
get type() {
|
|
98
|
+
return this.syntaxNode.type;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Source text for this node
|
|
102
|
+
* Uses sourceProvider if available for better performance
|
|
103
|
+
*/
|
|
104
|
+
get text() {
|
|
105
|
+
if (this.sourceProvider) {
|
|
106
|
+
const source = this.sourceProvider();
|
|
107
|
+
return source.slice(this.syntaxNode.startIndex, this.syntaxNode.endIndex);
|
|
108
|
+
}
|
|
109
|
+
return this.syntaxNode.text;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Is this an error node? (parsing failed)
|
|
113
|
+
*/
|
|
114
|
+
get isError() {
|
|
115
|
+
return this.syntaxNode.isError || this.syntaxNode.hasError;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Is this a missing node? (expected but not found)
|
|
119
|
+
*/
|
|
120
|
+
get isMissing() {
|
|
121
|
+
return this.syntaxNode.isMissing;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Is this a named node? (appears in the grammar)
|
|
125
|
+
*/
|
|
126
|
+
get isNamed() {
|
|
127
|
+
return this.syntaxNode.isNamed;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parent node (null for root)
|
|
131
|
+
*/
|
|
132
|
+
get parent() {
|
|
133
|
+
const parentNode = this.syntaxNode.parent;
|
|
134
|
+
if (!parentNode) return null;
|
|
135
|
+
return new ASTNode(parentNode, this.sourceProvider);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* All children (including unnamed nodes like punctuation)
|
|
139
|
+
*/
|
|
140
|
+
get children() {
|
|
141
|
+
return this.syntaxNode.children.map((child) => new ASTNode(child, this.sourceProvider));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Named children only (grammar rules, not literals)
|
|
145
|
+
*/
|
|
146
|
+
get namedChildren() {
|
|
147
|
+
return this.syntaxNode.namedChildren.map((child) => new ASTNode(child, this.sourceProvider));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get child by index
|
|
151
|
+
*/
|
|
152
|
+
child(index) {
|
|
153
|
+
const child = this.syntaxNode.child(index);
|
|
154
|
+
if (!child) return null;
|
|
155
|
+
return new ASTNode(child, this.sourceProvider);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get named child by index
|
|
159
|
+
*/
|
|
160
|
+
namedChild(index) {
|
|
161
|
+
const child = this.syntaxNode.namedChild(index);
|
|
162
|
+
if (!child) return null;
|
|
163
|
+
return new ASTNode(child, this.sourceProvider);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Number of children
|
|
167
|
+
*/
|
|
168
|
+
get childCount() {
|
|
169
|
+
return this.syntaxNode.childCount;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Number of named children
|
|
173
|
+
*/
|
|
174
|
+
get namedChildCount() {
|
|
175
|
+
return this.syntaxNode.namedChildCount;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Convert Tree-sitter Point to LSP Position
|
|
179
|
+
*/
|
|
180
|
+
toPosition(point) {
|
|
181
|
+
return {
|
|
182
|
+
line: point.row,
|
|
183
|
+
character: point.column
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Start position (LSP format: 0-based line and character)
|
|
188
|
+
*/
|
|
189
|
+
get startPosition() {
|
|
190
|
+
return this.toPosition(this.syntaxNode.startPosition);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* End position (LSP format: 0-based line and character)
|
|
194
|
+
*/
|
|
195
|
+
get endPosition() {
|
|
196
|
+
return this.toPosition(this.syntaxNode.endPosition);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Start byte offset in source
|
|
200
|
+
*/
|
|
201
|
+
get startIndex() {
|
|
202
|
+
return this.syntaxNode.startIndex;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* End byte offset in source
|
|
206
|
+
*/
|
|
207
|
+
get endIndex() {
|
|
208
|
+
return this.syntaxNode.endIndex;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Find smallest descendant at byte offset
|
|
212
|
+
*/
|
|
213
|
+
descendantForIndex(startIndex, endIndex) {
|
|
214
|
+
const node = endIndex !== void 0 ? this.syntaxNode.descendantForIndex(startIndex, endIndex) : this.syntaxNode.descendantForIndex(startIndex);
|
|
215
|
+
return new ASTNode(node, this.sourceProvider);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Find smallest descendant at position
|
|
219
|
+
*/
|
|
220
|
+
descendantForPosition(startPosition, endPosition) {
|
|
221
|
+
const start = {
|
|
222
|
+
row: startPosition.line,
|
|
223
|
+
column: startPosition.character
|
|
224
|
+
};
|
|
225
|
+
const node = endPosition ? this.syntaxNode.descendantForPosition(start, {
|
|
226
|
+
row: endPosition.line,
|
|
227
|
+
column: endPosition.character
|
|
228
|
+
}) : this.syntaxNode.descendantForPosition(start);
|
|
229
|
+
return new ASTNode(node, this.sourceProvider);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get all descendants of specific type(s)
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* const vars = root.descendantsOfType('variable_decl');
|
|
237
|
+
* const allRefs = root.descendantsOfType(['identifier', 'name_ref']);
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
descendantsOfType(types, startPosition, endPosition) {
|
|
241
|
+
const start = startPosition ? {
|
|
242
|
+
row: startPosition.line,
|
|
243
|
+
column: startPosition.character
|
|
244
|
+
} : void 0;
|
|
245
|
+
const end = endPosition ? {
|
|
246
|
+
row: endPosition.line,
|
|
247
|
+
column: endPosition.character
|
|
248
|
+
} : void 0;
|
|
249
|
+
const nodes = this.syntaxNode.descendantsOfType(types, start, end);
|
|
250
|
+
return nodes.map((node) => new ASTNode(node, this.sourceProvider));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* String representation (for debugging)
|
|
254
|
+
*/
|
|
255
|
+
toString() {
|
|
256
|
+
return this.syntaxNode.toString();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get S-expression representation of the syntax tree
|
|
260
|
+
*/
|
|
261
|
+
toSExpression() {
|
|
262
|
+
return this.syntaxNode.toString();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/runtime/parser/wasm.ts
|
|
268
|
+
/**
|
|
269
|
+
* Global state - Parser.init() must be called exactly once
|
|
270
|
+
*/
|
|
271
|
+
const state = {
|
|
272
|
+
initialized: false,
|
|
273
|
+
initPromise: null
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Language cache - reuse loaded languages
|
|
277
|
+
*/
|
|
278
|
+
const languageCache = {};
|
|
279
|
+
/**
|
|
280
|
+
* Detect runtime platform
|
|
281
|
+
*/
|
|
282
|
+
function detectPlatform() {
|
|
283
|
+
const global = globalThis;
|
|
284
|
+
if (typeof global.window !== "undefined" && typeof global.document !== "undefined") return "browser";
|
|
285
|
+
if (typeof process !== "undefined" && process.versions?.node) return "node";
|
|
286
|
+
return "node";
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Ensure Parser is initialized
|
|
290
|
+
* Safe to call multiple times - initialization happens only once
|
|
291
|
+
*/
|
|
292
|
+
async function ensureInitialized() {
|
|
293
|
+
if (state.initialized) return;
|
|
294
|
+
if (state.initPromise) {
|
|
295
|
+
await state.initPromise;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
state.initPromise = (async () => {
|
|
299
|
+
const platform = detectPlatform();
|
|
300
|
+
if (platform === "browser") await Parser.init();
|
|
301
|
+
else await Parser.init();
|
|
302
|
+
state.initialized = true;
|
|
303
|
+
})();
|
|
304
|
+
await state.initPromise;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Load a Tree-sitter language from WASM file
|
|
308
|
+
* Languages are cached to avoid redundant loading
|
|
309
|
+
*
|
|
310
|
+
* @param wasmPath Path to grammar.wasm file
|
|
311
|
+
* @returns Tree-sitter Language instance
|
|
312
|
+
*/
|
|
313
|
+
async function loadLanguage(wasmPath) {
|
|
314
|
+
await ensureInitialized();
|
|
315
|
+
const cached = languageCache[wasmPath];
|
|
316
|
+
if (cached) return cached;
|
|
317
|
+
try {
|
|
318
|
+
const language = await Parser.Language.load(wasmPath);
|
|
319
|
+
languageCache[wasmPath] = language;
|
|
320
|
+
return language;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
throw new Error(`Failed to load Tree-sitter grammar from ${wasmPath}. Ensure the WASM file exists and is accessible. Run 'treelsp build' to generate the grammar. Original error: ${error instanceof Error ? error.message : String(error)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Create a new Tree-sitter parser with the specified language
|
|
327
|
+
* This is the main entry point for creating parsers
|
|
328
|
+
*
|
|
329
|
+
* @param wasmPath Path to grammar.wasm file
|
|
330
|
+
* @returns Configured Parser instance ready to use
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const parser = await createParser('./generated/grammar.wasm');
|
|
335
|
+
* const tree = parser.parse('let x = 42;');
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
async function createParser(wasmPath) {
|
|
339
|
+
const language = await loadLanguage(wasmPath);
|
|
340
|
+
const parser = new Parser();
|
|
341
|
+
parser.setLanguage(language);
|
|
342
|
+
return parser;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Preload Parser initialization (optional)
|
|
346
|
+
* Useful for reducing latency of the first parse
|
|
347
|
+
*/
|
|
348
|
+
async function preloadParser() {
|
|
349
|
+
await ensureInitialized();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/runtime/parser/tree.ts
|
|
354
|
+
/**
|
|
355
|
+
* Document state with incremental parsing
|
|
356
|
+
*
|
|
357
|
+
* V1 strategy (DESIGN.md lines 16-24):
|
|
358
|
+
* - Keystroke → Tree-sitter incremental CST reparse → full AST rebuild → full scope rebuild
|
|
359
|
+
* - Tree-sitter's CST reparse is incremental automatically (via oldTree)
|
|
360
|
+
* - AST and scope are full recompute (simple and correct)
|
|
361
|
+
* - DocumentState.update() owns the boundary, designed for v2 incremental upgrade
|
|
362
|
+
*
|
|
363
|
+
* Memory management:
|
|
364
|
+
* - Tree-sitter uses WASM memory that must be explicitly freed
|
|
365
|
+
* - Call dispose() when done with the document
|
|
366
|
+
*/
|
|
367
|
+
var DocumentState = class {
|
|
368
|
+
/**
|
|
369
|
+
* Document metadata
|
|
370
|
+
*/
|
|
371
|
+
metadata;
|
|
372
|
+
/**
|
|
373
|
+
* Current source text
|
|
374
|
+
*/
|
|
375
|
+
sourceText;
|
|
376
|
+
/**
|
|
377
|
+
* Tree-sitter parser (owns the Language)
|
|
378
|
+
*/
|
|
379
|
+
parser;
|
|
380
|
+
/**
|
|
381
|
+
* Current parse tree (kept for incremental parsing)
|
|
382
|
+
* Must be deleted when replaced to free WASM memory
|
|
383
|
+
*/
|
|
384
|
+
tree = null;
|
|
385
|
+
/**
|
|
386
|
+
* Root AST node (cached after parse)
|
|
387
|
+
*/
|
|
388
|
+
rootNode = null;
|
|
389
|
+
constructor(parser, metadata, initialText) {
|
|
390
|
+
this.parser = parser;
|
|
391
|
+
this.metadata = metadata;
|
|
392
|
+
this.sourceText = initialText;
|
|
393
|
+
this.reparse();
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Parse or reparse the document
|
|
397
|
+
*
|
|
398
|
+
* V1 strategy: Always parse from scratch (no incremental CST reuse).
|
|
399
|
+
* Tree-sitter's incremental parsing requires calling tree.edit() with
|
|
400
|
+
* precise byte-offset edit info before passing the old tree. Without it,
|
|
401
|
+
* tree-sitter reuses old nodes at wrong positions, producing garbled ASTs.
|
|
402
|
+
* V2 will add proper edit tracking to enable incremental parsing.
|
|
403
|
+
*/
|
|
404
|
+
reparse() {
|
|
405
|
+
const newTree = this.parser.parse(this.sourceText);
|
|
406
|
+
if (this.tree) this.tree.delete();
|
|
407
|
+
this.tree = newTree;
|
|
408
|
+
this.rootNode = new ASTNode(newTree.rootNode, () => this.sourceText);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get root AST node
|
|
412
|
+
*/
|
|
413
|
+
get root() {
|
|
414
|
+
if (!this.rootNode) throw new Error("Document has not been parsed");
|
|
415
|
+
return this.rootNode;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Update document with new text (full replacement)
|
|
419
|
+
*
|
|
420
|
+
* V1 API: Simple full text replacement
|
|
421
|
+
* Tree-sitter does incremental CST reparse automatically via oldTree
|
|
422
|
+
* We rebuild the full AST wrapper (simple and correct for v1)
|
|
423
|
+
*
|
|
424
|
+
* @param newText New source text
|
|
425
|
+
* @param newVersion Document version after update (optional, will auto-increment)
|
|
426
|
+
*/
|
|
427
|
+
update(newText, newVersion) {
|
|
428
|
+
this.sourceText = newText;
|
|
429
|
+
if (newVersion !== void 0) this.metadata.version = newVersion;
|
|
430
|
+
else this.metadata.version++;
|
|
431
|
+
this.reparse();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Get current source text
|
|
435
|
+
*/
|
|
436
|
+
get text() {
|
|
437
|
+
return this.sourceText;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get document URI
|
|
441
|
+
*/
|
|
442
|
+
get uri() {
|
|
443
|
+
return this.metadata.uri;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get document version
|
|
447
|
+
*/
|
|
448
|
+
get version() {
|
|
449
|
+
return this.metadata.version;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get language ID
|
|
453
|
+
*/
|
|
454
|
+
get languageId() {
|
|
455
|
+
return this.metadata.languageId;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Check if document has parse errors
|
|
459
|
+
* Tree-sitter is error-tolerant - always returns a tree
|
|
460
|
+
* Check this to detect ERROR nodes
|
|
461
|
+
*/
|
|
462
|
+
get hasErrors() {
|
|
463
|
+
return this.root.isError;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Dispose resources (free WASM memory)
|
|
467
|
+
* Call this when done with the document
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* const doc = await createDocumentState(...);
|
|
472
|
+
* try {
|
|
473
|
+
* // Use document
|
|
474
|
+
* } finally {
|
|
475
|
+
* doc.dispose();
|
|
476
|
+
* }
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
dispose() {
|
|
480
|
+
if (this.tree) {
|
|
481
|
+
this.tree.delete();
|
|
482
|
+
this.tree = null;
|
|
483
|
+
}
|
|
484
|
+
this.rootNode = null;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* Create a new document state
|
|
489
|
+
*
|
|
490
|
+
* @param wasmPath Path to grammar WASM file (e.g., './generated/grammar.wasm')
|
|
491
|
+
* @param metadata Document metadata
|
|
492
|
+
* @param initialText Initial document text
|
|
493
|
+
* @returns Promise<DocumentState>
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* const doc = await createDocumentState(
|
|
498
|
+
* './generated/grammar.wasm',
|
|
499
|
+
* { uri: 'file:///test.mylang', version: 1, languageId: 'mylang' },
|
|
500
|
+
* 'let x = 42;'
|
|
501
|
+
* );
|
|
502
|
+
*
|
|
503
|
+
* const root = doc.root;
|
|
504
|
+
* console.log(root.type); // 'program'
|
|
505
|
+
*
|
|
506
|
+
* doc.update('let y = 99;');
|
|
507
|
+
* console.log(doc.version); // 2
|
|
508
|
+
*
|
|
509
|
+
* doc.dispose(); // Free WASM memory
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
async function createDocumentState(wasmPath, metadata, initialText) {
|
|
513
|
+
const parser = await createParser(wasmPath);
|
|
514
|
+
return new DocumentState(parser, metadata, initialText);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region src/runtime/scope/scope.ts
|
|
519
|
+
/**
|
|
520
|
+
* Scope - manages declarations and name resolution
|
|
521
|
+
*
|
|
522
|
+
* Supports three scope kinds:
|
|
523
|
+
* - 'global': Root scope, one per document
|
|
524
|
+
* - 'lexical': Standard block scope, inherits from parent
|
|
525
|
+
* - 'isolated': No access to parent scope (module boundaries)
|
|
526
|
+
*
|
|
527
|
+
* Visibility:
|
|
528
|
+
* - 'public': Visible to parent and cross-file
|
|
529
|
+
* - 'private': Visible only within this scope
|
|
530
|
+
*/
|
|
531
|
+
var Scope = class {
|
|
532
|
+
/**
|
|
533
|
+
* Scope kind
|
|
534
|
+
*/
|
|
535
|
+
kind;
|
|
536
|
+
/**
|
|
537
|
+
* AST node that owns this scope (null for global scope)
|
|
538
|
+
*/
|
|
539
|
+
node;
|
|
540
|
+
/**
|
|
541
|
+
* Parent scope (null for global scope)
|
|
542
|
+
*/
|
|
543
|
+
parent;
|
|
544
|
+
/**
|
|
545
|
+
* Declarations in this scope
|
|
546
|
+
* Maps name → array of declarations (for declaration merging in v2)
|
|
547
|
+
*/
|
|
548
|
+
declarations = /* @__PURE__ */ new Map();
|
|
549
|
+
/**
|
|
550
|
+
* Child scopes
|
|
551
|
+
*/
|
|
552
|
+
children = [];
|
|
553
|
+
constructor(kind, node, parent) {
|
|
554
|
+
this.kind = kind;
|
|
555
|
+
this.node = node;
|
|
556
|
+
this.parent = parent;
|
|
557
|
+
if (parent) parent.children.push(this);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Add a declaration to this scope
|
|
561
|
+
*
|
|
562
|
+
* V1: Single declarations per name (declaration merging in v2)
|
|
563
|
+
* If name already exists, adds to array (for future merging support)
|
|
564
|
+
*/
|
|
565
|
+
declare(name, node, declaredBy, visibility) {
|
|
566
|
+
const decl = {
|
|
567
|
+
node,
|
|
568
|
+
name,
|
|
569
|
+
visibility,
|
|
570
|
+
declaredBy
|
|
571
|
+
};
|
|
572
|
+
const existing = this.declarations.get(name);
|
|
573
|
+
if (existing) existing.push(decl);
|
|
574
|
+
else this.declarations.set(name, [decl]);
|
|
575
|
+
return decl;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Look up a name in this scope only (does not search parent)
|
|
579
|
+
*
|
|
580
|
+
* @param name Name to look up
|
|
581
|
+
* @param declaredBy Optional filter by declaration type(s)
|
|
582
|
+
* @param visibility Optional filter by visibility
|
|
583
|
+
* @returns First matching declaration or null
|
|
584
|
+
*/
|
|
585
|
+
lookupLocal(name, declaredBy, visibility) {
|
|
586
|
+
const declarations = this.declarations.get(name);
|
|
587
|
+
if (!declarations) return null;
|
|
588
|
+
let filtered = declarations;
|
|
589
|
+
if (declaredBy) {
|
|
590
|
+
const types = Array.isArray(declaredBy) ? declaredBy : [declaredBy];
|
|
591
|
+
filtered = declarations.filter((d) => types.includes(d.declaredBy));
|
|
592
|
+
}
|
|
593
|
+
if (visibility) filtered = filtered.filter((d) => d.visibility === visibility);
|
|
594
|
+
return filtered[0] ?? null;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Look up a name in this scope and parent scopes
|
|
598
|
+
*
|
|
599
|
+
* Respects scope kinds:
|
|
600
|
+
* - 'global': No parent to search
|
|
601
|
+
* - 'lexical': Searches parent recursively
|
|
602
|
+
* - 'isolated': Does not search parent
|
|
603
|
+
*
|
|
604
|
+
* @param name Name to look up
|
|
605
|
+
* @param declaredBy Optional filter by declaration type(s)
|
|
606
|
+
* @returns First matching declaration or null
|
|
607
|
+
*/
|
|
608
|
+
lookup(name, declaredBy) {
|
|
609
|
+
const local = this.lookupLocal(name, declaredBy);
|
|
610
|
+
if (local) return local;
|
|
611
|
+
if (this.kind === "isolated") return null;
|
|
612
|
+
if (this.parent) return this.parent.lookup(name, declaredBy);
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get all declarations in this scope
|
|
617
|
+
*
|
|
618
|
+
* @param options Filtering options
|
|
619
|
+
* @returns Array of declarations
|
|
620
|
+
*/
|
|
621
|
+
allDeclarations(options) {
|
|
622
|
+
const all = [];
|
|
623
|
+
for (const declarations of this.declarations.values()) all.push(...declarations);
|
|
624
|
+
let filtered = all;
|
|
625
|
+
if (options?.visibility) filtered = filtered.filter((d) => d.visibility === options.visibility);
|
|
626
|
+
if (options?.declaredBy) {
|
|
627
|
+
const types = Array.isArray(options.declaredBy) ? options.declaredBy : [options.declaredBy];
|
|
628
|
+
filtered = filtered.filter((d) => types.includes(d.declaredBy));
|
|
629
|
+
}
|
|
630
|
+
return filtered;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Check if a name is declared in this scope (local only)
|
|
634
|
+
*/
|
|
635
|
+
isDeclared(name, declaredBy) {
|
|
636
|
+
return this.lookupLocal(name, declaredBy) !== null;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Get the global scope (root of scope tree)
|
|
640
|
+
*/
|
|
641
|
+
global() {
|
|
642
|
+
let scope = this;
|
|
643
|
+
while (scope.parent) scope = scope.parent;
|
|
644
|
+
return scope;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get all child scopes
|
|
648
|
+
*/
|
|
649
|
+
getChildren() {
|
|
650
|
+
return [...this.children];
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Get all descendant scopes (recursive)
|
|
654
|
+
*/
|
|
655
|
+
descendants() {
|
|
656
|
+
const result = [];
|
|
657
|
+
for (const child of this.children) {
|
|
658
|
+
result.push(child);
|
|
659
|
+
result.push(...child.descendants());
|
|
660
|
+
}
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Debug string representation
|
|
665
|
+
*/
|
|
666
|
+
toString() {
|
|
667
|
+
const nodeType = this.node?.type ?? "document";
|
|
668
|
+
const declCount = Array.from(this.declarations.values()).flat().length;
|
|
669
|
+
return `Scope(${this.kind}, ${nodeType}, ${declCount} decls)`;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/runtime/scope/resolver.ts
|
|
675
|
+
/**
|
|
676
|
+
* Build scope tree for a document
|
|
677
|
+
*
|
|
678
|
+
* V1 strategy: Full rebuild on every document change
|
|
679
|
+
* - Walk AST to create scope tree
|
|
680
|
+
* - Register all declarations
|
|
681
|
+
* - Register all references
|
|
682
|
+
* - Resolve references to declarations
|
|
683
|
+
*
|
|
684
|
+
* @param document The document to analyze
|
|
685
|
+
* @param semantic Semantic definition from defineLanguage()
|
|
686
|
+
* @returns DocumentScope with scope tree and resolution data
|
|
687
|
+
*/
|
|
688
|
+
function buildScopes(document, semantic) {
|
|
689
|
+
const root = document.root;
|
|
690
|
+
const nodeScopes = /* @__PURE__ */ new Map();
|
|
691
|
+
const references = [];
|
|
692
|
+
const declaredNodeIds = /* @__PURE__ */ new Set();
|
|
693
|
+
const context = {
|
|
694
|
+
scopeOf: (node) => {
|
|
695
|
+
let current = node;
|
|
696
|
+
while (current) {
|
|
697
|
+
const scope = nodeScopes.get(current.id);
|
|
698
|
+
if (scope) return scope;
|
|
699
|
+
current = current.parent;
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
},
|
|
703
|
+
resolveModule: () => null,
|
|
704
|
+
getReferences: () => references,
|
|
705
|
+
getDeclarations: () => {
|
|
706
|
+
const all = [];
|
|
707
|
+
for (const scope of nodeScopes.values()) all.push(...scope.allDeclarations());
|
|
708
|
+
return all;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const globalScope = new Scope("global", root, null);
|
|
712
|
+
nodeScopes.set(root.id, globalScope);
|
|
713
|
+
walkTree(root, globalScope, semantic, nodeScopes, references, context, declaredNodeIds);
|
|
714
|
+
for (const ref of references) ref.resolved = resolveReference(ref, context);
|
|
715
|
+
const declarations = context.getDeclarations();
|
|
716
|
+
return {
|
|
717
|
+
root: globalScope,
|
|
718
|
+
nodeScopes,
|
|
719
|
+
references,
|
|
720
|
+
declarations
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Walk AST tree to build scopes and register declarations/references
|
|
725
|
+
*/
|
|
726
|
+
function walkTree(node, currentScope, semantic, nodeScopes, references, context, declaredNodeIds) {
|
|
727
|
+
const rule = semantic[node.type];
|
|
728
|
+
let nodeScope = currentScope;
|
|
729
|
+
if (rule?.scope) {
|
|
730
|
+
nodeScope = new Scope(rule.scope, node, currentScope);
|
|
731
|
+
nodeScopes.set(node.id, nodeScope);
|
|
732
|
+
}
|
|
733
|
+
if (rule) {
|
|
734
|
+
processDeclarations(node, rule, nodeScope, semantic, nodeScopes, context, declaredNodeIds);
|
|
735
|
+
processReferences(node, rule, nodeScope, references, context, declaredNodeIds);
|
|
736
|
+
}
|
|
737
|
+
for (const child of node.namedChildren) walkTree(child, nodeScope, semantic, nodeScopes, references, context, declaredNodeIds);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Process declarations for a node
|
|
741
|
+
*/
|
|
742
|
+
function processDeclarations(node, rule, _scope, _semantic, nodeScopes, context, declaredNodeIds) {
|
|
743
|
+
if (!rule.declares) return;
|
|
744
|
+
const descriptor = rule.declares;
|
|
745
|
+
const nameNode = node.field(descriptor.field);
|
|
746
|
+
if (!nameNode) return;
|
|
747
|
+
declaredNodeIds.add(nameNode.id);
|
|
748
|
+
const name = nameNode.text;
|
|
749
|
+
const visibility = getVisibility(node, descriptor);
|
|
750
|
+
const targetScope = getTargetScope(node, descriptor.scope, nodeScopes);
|
|
751
|
+
if (!targetScope) return;
|
|
752
|
+
const strategy = descriptor.strategy ?? "if-not-declared";
|
|
753
|
+
if (strategy === "if-not-declared") {
|
|
754
|
+
if (targetScope.isDeclared(name)) return;
|
|
755
|
+
}
|
|
756
|
+
if (descriptor.resolve) {
|
|
757
|
+
const resolved = descriptor.resolve({
|
|
758
|
+
node,
|
|
759
|
+
name,
|
|
760
|
+
text: name
|
|
761
|
+
}, context);
|
|
762
|
+
if (!resolved) return;
|
|
763
|
+
}
|
|
764
|
+
targetScope.declare(name, nameNode, node.type, visibility);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Process references for a node
|
|
768
|
+
*/
|
|
769
|
+
function processReferences(node, rule, _scope, references, context, declaredNodeIds) {
|
|
770
|
+
if (!rule.references) return;
|
|
771
|
+
const descriptor = rule.references;
|
|
772
|
+
const nameNode = node.field(descriptor.field) ?? node;
|
|
773
|
+
if (declaredNodeIds.has(nameNode.id)) return;
|
|
774
|
+
const name = nameNode.text;
|
|
775
|
+
const to = Array.isArray(descriptor.to) ? descriptor.to : [descriptor.to];
|
|
776
|
+
const ref = {
|
|
777
|
+
node: nameNode,
|
|
778
|
+
name,
|
|
779
|
+
to,
|
|
780
|
+
resolved: null
|
|
781
|
+
};
|
|
782
|
+
if (descriptor.resolve) {
|
|
783
|
+
const resolved = descriptor.resolve({
|
|
784
|
+
node: nameNode,
|
|
785
|
+
name,
|
|
786
|
+
text: name
|
|
787
|
+
}, context);
|
|
788
|
+
if (resolved) ref.resolved = resolved;
|
|
789
|
+
}
|
|
790
|
+
references.push(ref);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Resolve a reference to its declaration
|
|
794
|
+
*/
|
|
795
|
+
function resolveReference(ref, context) {
|
|
796
|
+
if (ref.resolved) return ref.resolved;
|
|
797
|
+
const scope = context.scopeOf(ref.node);
|
|
798
|
+
if (!scope) return null;
|
|
799
|
+
const decl = scope.lookup(ref.name, ref.to);
|
|
800
|
+
return decl;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Get visibility for a declaration
|
|
804
|
+
*/
|
|
805
|
+
function getVisibility(node, descriptor) {
|
|
806
|
+
if (!descriptor.visibility) return "private";
|
|
807
|
+
if (typeof descriptor.visibility === "function") return descriptor.visibility(node);
|
|
808
|
+
return descriptor.visibility;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get target scope for a declaration
|
|
812
|
+
*/
|
|
813
|
+
function getTargetScope(node, target, nodeScopes) {
|
|
814
|
+
if (target === "global") {
|
|
815
|
+
let current = node;
|
|
816
|
+
while (current) {
|
|
817
|
+
const scope = nodeScopes.get(current.id);
|
|
818
|
+
if (scope?.kind === "global") return scope;
|
|
819
|
+
current = current.parent;
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
if (target === "enclosing") {
|
|
824
|
+
let current = node.parent;
|
|
825
|
+
while (current) {
|
|
826
|
+
const scope = nodeScopes.get(current.id);
|
|
827
|
+
if (scope) return scope;
|
|
828
|
+
current = current.parent;
|
|
829
|
+
}
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
if (target === "local") {
|
|
833
|
+
let current = node.parent;
|
|
834
|
+
while (current) {
|
|
835
|
+
const scope = nodeScopes.get(current.id);
|
|
836
|
+
if (scope) return scope;
|
|
837
|
+
current = current.parent;
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region src/runtime/scope/workspace.ts
|
|
846
|
+
/**
|
|
847
|
+
* Workspace - manages multiple documents and cross-file resolution
|
|
848
|
+
*
|
|
849
|
+
* V1: Basic cross-file index for public declarations
|
|
850
|
+
* - Stores DocumentScope for each file
|
|
851
|
+
* - Provides lookup by URI
|
|
852
|
+
* - Exports public declarations for cross-file resolution
|
|
853
|
+
*
|
|
854
|
+
* V2: Will add module resolution, import tracking, circular dependency detection
|
|
855
|
+
*/
|
|
856
|
+
var Workspace = class {
|
|
857
|
+
/**
|
|
858
|
+
* Semantic definition (shared across all documents)
|
|
859
|
+
*/
|
|
860
|
+
semantic;
|
|
861
|
+
/**
|
|
862
|
+
* Documents indexed by URI
|
|
863
|
+
*/
|
|
864
|
+
documents = /* @__PURE__ */ new Map();
|
|
865
|
+
/**
|
|
866
|
+
* Index of public declarations by name (for cross-file lookup)
|
|
867
|
+
* Maps name → array of declarations from all files
|
|
868
|
+
*/
|
|
869
|
+
publicDeclarations = /* @__PURE__ */ new Map();
|
|
870
|
+
constructor(semantic) {
|
|
871
|
+
this.semantic = semantic;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Add or update a document in the workspace
|
|
875
|
+
*
|
|
876
|
+
* @param document The document to add/update
|
|
877
|
+
* @returns The DocumentScope for this document
|
|
878
|
+
*/
|
|
879
|
+
addDocument(document) {
|
|
880
|
+
const scope = buildScopes(document, this.semantic);
|
|
881
|
+
this.documents.set(document.uri, {
|
|
882
|
+
document,
|
|
883
|
+
scope
|
|
884
|
+
});
|
|
885
|
+
this.rebuildPublicIndex();
|
|
886
|
+
return scope;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Remove a document from the workspace
|
|
890
|
+
*/
|
|
891
|
+
removeDocument(uri) {
|
|
892
|
+
this.documents.delete(uri);
|
|
893
|
+
this.rebuildPublicIndex();
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Get a document by URI
|
|
897
|
+
*/
|
|
898
|
+
getDocument(uri) {
|
|
899
|
+
return this.documents.get(uri) ?? null;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Get all documents in the workspace
|
|
903
|
+
*/
|
|
904
|
+
getAllDocuments() {
|
|
905
|
+
return Array.from(this.documents.values());
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Look up a public declaration by name across all files
|
|
909
|
+
*
|
|
910
|
+
* @param name The name to look up
|
|
911
|
+
* @param declaredBy Optional filter by declaration type(s)
|
|
912
|
+
* @returns Array of matching declarations from all files
|
|
913
|
+
*/
|
|
914
|
+
lookupPublic(name, declaredBy) {
|
|
915
|
+
const declarations = this.publicDeclarations.get(name);
|
|
916
|
+
if (!declarations) return [];
|
|
917
|
+
if (declaredBy) {
|
|
918
|
+
const types = Array.isArray(declaredBy) ? declaredBy : [declaredBy];
|
|
919
|
+
return declarations.filter((d) => types.includes(d.declaredBy));
|
|
920
|
+
}
|
|
921
|
+
return declarations;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Get all public declarations in the workspace
|
|
925
|
+
*/
|
|
926
|
+
getAllPublicDeclarations() {
|
|
927
|
+
const all = [];
|
|
928
|
+
for (const declarations of this.publicDeclarations.values()) all.push(...declarations);
|
|
929
|
+
return all;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Rebuild the public declarations index
|
|
933
|
+
* Called after any document is added/removed/updated
|
|
934
|
+
*/
|
|
935
|
+
rebuildPublicIndex() {
|
|
936
|
+
this.publicDeclarations.clear();
|
|
937
|
+
for (const { scope } of this.documents.values()) {
|
|
938
|
+
const publicDecls = scope.root.allDeclarations({ visibility: "public" });
|
|
939
|
+
for (const decl of publicDecls) {
|
|
940
|
+
const existing = this.publicDeclarations.get(decl.name);
|
|
941
|
+
if (existing) existing.push(decl);
|
|
942
|
+
else this.publicDeclarations.set(decl.name, [decl]);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Find circular imports (placeholder for v2)
|
|
948
|
+
*
|
|
949
|
+
* V1: Not implemented
|
|
950
|
+
* V2: Will track import graph and detect cycles
|
|
951
|
+
*
|
|
952
|
+
* @returns Array of circular import chains
|
|
953
|
+
*/
|
|
954
|
+
findCircularImports() {
|
|
955
|
+
return [];
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Get workspace statistics (for debugging)
|
|
959
|
+
*/
|
|
960
|
+
getStats() {
|
|
961
|
+
let totalDecls = 0;
|
|
962
|
+
let totalRefs = 0;
|
|
963
|
+
for (const { scope } of this.documents.values()) {
|
|
964
|
+
totalDecls += scope.declarations.length;
|
|
965
|
+
totalRefs += scope.references.length;
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
documentCount: this.documents.size,
|
|
969
|
+
publicDeclarationCount: this.getAllPublicDeclarations().length,
|
|
970
|
+
totalDeclarationCount: totalDecls,
|
|
971
|
+
totalReferenceCount: totalRefs
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Clear all documents from the workspace
|
|
976
|
+
*/
|
|
977
|
+
clear() {
|
|
978
|
+
this.documents.clear();
|
|
979
|
+
this.publicDeclarations.clear();
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
//#endregion
|
|
984
|
+
//#region src/runtime/lsp/documents.ts
|
|
985
|
+
/**
|
|
986
|
+
* Document manager for LSP
|
|
987
|
+
*
|
|
988
|
+
* Manages the lifecycle of open documents:
|
|
989
|
+
* - Tracks open/change/close events
|
|
990
|
+
* - Rebuilds scopes on document changes
|
|
991
|
+
* - Maintains workspace index
|
|
992
|
+
*/
|
|
993
|
+
var DocumentManager = class {
|
|
994
|
+
workspace;
|
|
995
|
+
constructor(semantic) {
|
|
996
|
+
this.workspace = new Workspace(semantic);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Open or update a document
|
|
1000
|
+
*
|
|
1001
|
+
* @param document The document state (already parsed)
|
|
1002
|
+
* @returns The computed scope for this document
|
|
1003
|
+
*/
|
|
1004
|
+
open(document) {
|
|
1005
|
+
return this.workspace.addDocument(document);
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Handle a document change
|
|
1009
|
+
* The caller should have already called document.update() with new text
|
|
1010
|
+
*
|
|
1011
|
+
* @param document The updated document state
|
|
1012
|
+
* @returns The recomputed scope
|
|
1013
|
+
*/
|
|
1014
|
+
change(document) {
|
|
1015
|
+
return this.workspace.addDocument(document);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Close a document
|
|
1019
|
+
*/
|
|
1020
|
+
close(uri) {
|
|
1021
|
+
this.workspace.removeDocument(uri);
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Get document by URI
|
|
1025
|
+
*/
|
|
1026
|
+
get(uri) {
|
|
1027
|
+
return this.workspace.getDocument(uri);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Get the workspace instance
|
|
1031
|
+
*/
|
|
1032
|
+
getWorkspace() {
|
|
1033
|
+
return this.workspace;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get all open documents
|
|
1037
|
+
*/
|
|
1038
|
+
getAllDocuments() {
|
|
1039
|
+
return this.workspace.getAllDocuments();
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Clear all documents
|
|
1043
|
+
*/
|
|
1044
|
+
clear() {
|
|
1045
|
+
this.workspace.clear();
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/runtime/lsp/context.ts
|
|
1051
|
+
/**
|
|
1052
|
+
* Create LspContext for handler use
|
|
1053
|
+
*/
|
|
1054
|
+
function createLspContext(docScope, workspace, document, _semantic) {
|
|
1055
|
+
return {
|
|
1056
|
+
resolve(node) {
|
|
1057
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1058
|
+
return ref?.resolved?.node ?? null;
|
|
1059
|
+
},
|
|
1060
|
+
typeOf(_node) {
|
|
1061
|
+
return null;
|
|
1062
|
+
},
|
|
1063
|
+
scopeOf(node) {
|
|
1064
|
+
return findScopeForNode(node, docScope);
|
|
1065
|
+
},
|
|
1066
|
+
document,
|
|
1067
|
+
workspace
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Find the smallest named node at a position
|
|
1072
|
+
*
|
|
1073
|
+
* When the cursor is at the exclusive end of a token (e.g., right edge of an
|
|
1074
|
+
* identifier), tree-sitter returns the parent node because that position falls
|
|
1075
|
+
* in whitespace. In that case, look one character back to find the token the
|
|
1076
|
+
* user likely intended.
|
|
1077
|
+
*
|
|
1078
|
+
* Similarly, when the cursor lands on an anonymous node (punctuation like ";",
|
|
1079
|
+
* "+", "="), look one character back to find the named token the user likely
|
|
1080
|
+
* intended. This handles the common case of clicking at the boundary between
|
|
1081
|
+
* an identifier and adjacent punctuation.
|
|
1082
|
+
*/
|
|
1083
|
+
function findNodeAtPosition(root, position) {
|
|
1084
|
+
const node = root.descendantForPosition(position);
|
|
1085
|
+
if (position.character > 0 && (node.namedChildCount > 0 || !node.isNamed)) {
|
|
1086
|
+
const prev = root.descendantForPosition({
|
|
1087
|
+
line: position.line,
|
|
1088
|
+
character: position.character - 1
|
|
1089
|
+
});
|
|
1090
|
+
if (prev.namedChildCount === 0 && prev.isNamed) return prev;
|
|
1091
|
+
}
|
|
1092
|
+
return node;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Find the reference entry for a node (by position match)
|
|
1096
|
+
*/
|
|
1097
|
+
function findReferenceForNode(node, docScope) {
|
|
1098
|
+
const startLine = node.startPosition.line;
|
|
1099
|
+
const startChar = node.startPosition.character;
|
|
1100
|
+
const endLine = node.endPosition.line;
|
|
1101
|
+
const endChar = node.endPosition.character;
|
|
1102
|
+
for (const ref of docScope.references) {
|
|
1103
|
+
const refStart = ref.node.startPosition;
|
|
1104
|
+
const refEnd = ref.node.endPosition;
|
|
1105
|
+
if (refStart.line === startLine && refStart.character === startChar && refEnd.line === endLine && refEnd.character === endChar) return ref;
|
|
1106
|
+
}
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Find the declaration entry for a node (by position match)
|
|
1111
|
+
*/
|
|
1112
|
+
function findDeclarationForNode(node, docScope) {
|
|
1113
|
+
const startLine = node.startPosition.line;
|
|
1114
|
+
const startChar = node.startPosition.character;
|
|
1115
|
+
for (const decl of docScope.declarations) {
|
|
1116
|
+
const declStart = decl.node.startPosition;
|
|
1117
|
+
if (declStart.line === startLine && declStart.character === startChar) return decl;
|
|
1118
|
+
}
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Find the scope that contains a node
|
|
1123
|
+
* Walks up the parent chain checking nodeScopes map
|
|
1124
|
+
*/
|
|
1125
|
+
function findScopeForNode(node, docScope) {
|
|
1126
|
+
let current = node;
|
|
1127
|
+
while (current) {
|
|
1128
|
+
const scope = docScope.nodeScopes.get(current.id);
|
|
1129
|
+
if (scope) return scope;
|
|
1130
|
+
current = current.parent;
|
|
1131
|
+
}
|
|
1132
|
+
return docScope.root;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Convert an ASTNode to an LSP-style range
|
|
1136
|
+
*/
|
|
1137
|
+
function nodeToRange(node) {
|
|
1138
|
+
return {
|
|
1139
|
+
start: node.startPosition,
|
|
1140
|
+
end: node.endPosition
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/runtime/lsp/diagnostics.ts
|
|
1146
|
+
/**
|
|
1147
|
+
* Compute all diagnostics for a document
|
|
1148
|
+
*/
|
|
1149
|
+
function computeDiagnostics(document, docScope, semantic, lsp, validation, workspace) {
|
|
1150
|
+
const diagnostics = [];
|
|
1151
|
+
collectParseErrors(document.root, diagnostics);
|
|
1152
|
+
collectUnresolvedReferences(docScope, semantic, lsp, diagnostics);
|
|
1153
|
+
if (validation) collectValidationDiagnostics(document, docScope, semantic, validation, workspace, diagnostics);
|
|
1154
|
+
return diagnostics;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Walk AST to find ERROR and MISSING nodes
|
|
1158
|
+
*/
|
|
1159
|
+
function collectParseErrors(node, diagnostics) {
|
|
1160
|
+
if (node.isMissing) {
|
|
1161
|
+
diagnostics.push({
|
|
1162
|
+
range: {
|
|
1163
|
+
start: node.startPosition,
|
|
1164
|
+
end: node.endPosition
|
|
1165
|
+
},
|
|
1166
|
+
severity: "error",
|
|
1167
|
+
message: `Missing ${node.type}`,
|
|
1168
|
+
code: "missing-node",
|
|
1169
|
+
source: "treelsp"
|
|
1170
|
+
});
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (node.isError) {
|
|
1174
|
+
const hasErrorChild = node.children.some((c) => c.isError || c.isMissing);
|
|
1175
|
+
if (!hasErrorChild) diagnostics.push({
|
|
1176
|
+
range: {
|
|
1177
|
+
start: node.startPosition,
|
|
1178
|
+
end: node.endPosition
|
|
1179
|
+
},
|
|
1180
|
+
severity: "error",
|
|
1181
|
+
message: "Syntax error",
|
|
1182
|
+
code: "syntax-error",
|
|
1183
|
+
source: "treelsp"
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
for (const child of node.children) collectParseErrors(child, diagnostics);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Report unresolved references based on semantic onUnresolved policy
|
|
1190
|
+
*/
|
|
1191
|
+
function collectUnresolvedReferences(docScope, semantic, lsp, diagnostics) {
|
|
1192
|
+
for (const ref of docScope.references) {
|
|
1193
|
+
if (ref.resolved) continue;
|
|
1194
|
+
const refNode = ref.node;
|
|
1195
|
+
let parentNode = refNode.parent;
|
|
1196
|
+
let policy = "error";
|
|
1197
|
+
let optional = false;
|
|
1198
|
+
while (parentNode) {
|
|
1199
|
+
const rule = semantic[parentNode.type];
|
|
1200
|
+
if (rule?.references) {
|
|
1201
|
+
policy = rule.references.onUnresolved ?? "error";
|
|
1202
|
+
optional = rule.references.optional ?? false;
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
parentNode = parentNode.parent;
|
|
1206
|
+
}
|
|
1207
|
+
if (optional || policy === "ignore") continue;
|
|
1208
|
+
const severity = policy === "warning" ? "warning" : "error";
|
|
1209
|
+
let message = `Cannot find name '${ref.name}'`;
|
|
1210
|
+
if (lsp?.$unresolved) {
|
|
1211
|
+
const ctx = {
|
|
1212
|
+
resolve: () => null,
|
|
1213
|
+
typeOf: () => null,
|
|
1214
|
+
scopeOf: () => docScope.root,
|
|
1215
|
+
document: null,
|
|
1216
|
+
workspace: null
|
|
1217
|
+
};
|
|
1218
|
+
const custom = lsp.$unresolved(refNode, ctx);
|
|
1219
|
+
if (custom) message = custom;
|
|
1220
|
+
}
|
|
1221
|
+
diagnostics.push({
|
|
1222
|
+
range: {
|
|
1223
|
+
start: refNode.startPosition,
|
|
1224
|
+
end: refNode.endPosition
|
|
1225
|
+
},
|
|
1226
|
+
severity,
|
|
1227
|
+
message,
|
|
1228
|
+
code: "unresolved-reference",
|
|
1229
|
+
source: "treelsp"
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Run custom validators and collect their diagnostics
|
|
1235
|
+
*/
|
|
1236
|
+
function collectValidationDiagnostics(document, docScope, semantic, validation, workspace, diagnostics) {
|
|
1237
|
+
const lspContext = createLspContext(docScope, workspace ?? {}, document, semantic);
|
|
1238
|
+
function createValidationContext(node) {
|
|
1239
|
+
return {
|
|
1240
|
+
error(target, message, options) {
|
|
1241
|
+
addValidationDiagnostic("error", target, message, options, diagnostics);
|
|
1242
|
+
},
|
|
1243
|
+
warning(target, message, options) {
|
|
1244
|
+
addValidationDiagnostic("warning", target, message, options, diagnostics);
|
|
1245
|
+
},
|
|
1246
|
+
info(target, message, options) {
|
|
1247
|
+
addValidationDiagnostic("info", target, message, options, diagnostics);
|
|
1248
|
+
},
|
|
1249
|
+
hint(target, message, options) {
|
|
1250
|
+
addValidationDiagnostic("hint", target, message, options, diagnostics);
|
|
1251
|
+
},
|
|
1252
|
+
resolve: (n) => lspContext.resolve(n),
|
|
1253
|
+
scopeOf: (n) => lspContext.scopeOf(n),
|
|
1254
|
+
declarationsOf(_target) {
|
|
1255
|
+
const scope = lspContext.scopeOf(node);
|
|
1256
|
+
return scope.allDeclarations();
|
|
1257
|
+
},
|
|
1258
|
+
referencesTo(_target) {
|
|
1259
|
+
return docScope.references.filter((r) => r.resolved?.node === _target);
|
|
1260
|
+
},
|
|
1261
|
+
document,
|
|
1262
|
+
workspace: workspace ?? null
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
walkForValidation(document.root, validation, createValidationContext);
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Walk AST and run matching validators
|
|
1269
|
+
*/
|
|
1270
|
+
function walkForValidation(node, validation, createCtx) {
|
|
1271
|
+
const validators = validation[node.type];
|
|
1272
|
+
if (validators) {
|
|
1273
|
+
const ctx = createCtx(node);
|
|
1274
|
+
const fns = Array.isArray(validators) ? validators : [validators];
|
|
1275
|
+
for (const fn of fns) fn(node, ctx);
|
|
1276
|
+
}
|
|
1277
|
+
for (const child of node.namedChildren) walkForValidation(child, validation, createCtx);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Add a validation diagnostic to the collection
|
|
1281
|
+
*/
|
|
1282
|
+
function addValidationDiagnostic(severity, node, message, options, diagnostics) {
|
|
1283
|
+
const target = options?.at ?? node;
|
|
1284
|
+
const diag = {
|
|
1285
|
+
range: {
|
|
1286
|
+
start: target.startPosition,
|
|
1287
|
+
end: target.endPosition
|
|
1288
|
+
},
|
|
1289
|
+
severity,
|
|
1290
|
+
message,
|
|
1291
|
+
source: "treelsp"
|
|
1292
|
+
};
|
|
1293
|
+
if (options?.code) diag.code = options.code;
|
|
1294
|
+
diagnostics.push(diag);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region src/runtime/lsp/hover.ts
|
|
1299
|
+
/**
|
|
1300
|
+
* Provide hover information at position
|
|
1301
|
+
*
|
|
1302
|
+
* Strategy:
|
|
1303
|
+
* 1. Find node at position
|
|
1304
|
+
* 2. If node is a reference → resolve to declaration → use declaration's hover
|
|
1305
|
+
* 3. If node is a declaration → use its hover directly
|
|
1306
|
+
* 4. Fall back to default hover (type + name)
|
|
1307
|
+
*/
|
|
1308
|
+
function provideHover(document, position, docScope, semantic, lsp, workspace) {
|
|
1309
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1310
|
+
if (node.type === document.root.type) return null;
|
|
1311
|
+
const ctx = createLspContext(docScope, workspace ?? {}, document, semantic);
|
|
1312
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1313
|
+
if (ref) {
|
|
1314
|
+
if (!ref.resolved) return null;
|
|
1315
|
+
const declNode = ref.resolved.node;
|
|
1316
|
+
const declParent = declNode.parent;
|
|
1317
|
+
const declType = ref.resolved.declaredBy;
|
|
1318
|
+
const hoverHandler = lsp?.[declType]?.hover;
|
|
1319
|
+
if (hoverHandler && declParent) {
|
|
1320
|
+
const contents = hoverHandler(declParent, ctx);
|
|
1321
|
+
if (contents) return {
|
|
1322
|
+
contents,
|
|
1323
|
+
range: {
|
|
1324
|
+
start: node.startPosition,
|
|
1325
|
+
end: node.endPosition
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
return {
|
|
1330
|
+
contents: `**${declType}** \`${ref.resolved.name}\``,
|
|
1331
|
+
range: {
|
|
1332
|
+
start: node.startPosition,
|
|
1333
|
+
end: node.endPosition
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const decl = findDeclarationForNode(node, docScope);
|
|
1338
|
+
if (decl) {
|
|
1339
|
+
const declParent = node.parent;
|
|
1340
|
+
const hoverHandler = lsp?.[decl.declaredBy]?.hover;
|
|
1341
|
+
if (hoverHandler && declParent) {
|
|
1342
|
+
const contents = hoverHandler(declParent, ctx);
|
|
1343
|
+
if (contents) return {
|
|
1344
|
+
contents,
|
|
1345
|
+
range: {
|
|
1346
|
+
start: node.startPosition,
|
|
1347
|
+
end: node.endPosition
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
contents: `**${decl.declaredBy}** \`${decl.name}\``,
|
|
1353
|
+
range: {
|
|
1354
|
+
start: node.startPosition,
|
|
1355
|
+
end: node.endPosition
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
//#endregion
|
|
1363
|
+
//#region src/runtime/lsp/definition.ts
|
|
1364
|
+
/**
|
|
1365
|
+
* Provide definition location for a reference
|
|
1366
|
+
*
|
|
1367
|
+
* Fully automatic: find reference at position → return declaration location
|
|
1368
|
+
*/
|
|
1369
|
+
function provideDefinition(document, position, docScope) {
|
|
1370
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1371
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1372
|
+
if (!ref?.resolved) return null;
|
|
1373
|
+
return {
|
|
1374
|
+
uri: document.uri,
|
|
1375
|
+
range: nodeToRange(ref.resolved.node)
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/runtime/lsp/references.ts
|
|
1381
|
+
/**
|
|
1382
|
+
* Find all references to the symbol at position
|
|
1383
|
+
*
|
|
1384
|
+
* Strategy:
|
|
1385
|
+
* 1. Find node at position
|
|
1386
|
+
* 2. If reference → resolve to declaration
|
|
1387
|
+
* 3. If declaration → use directly
|
|
1388
|
+
* 4. Scan all documents in workspace for references to that declaration
|
|
1389
|
+
*/
|
|
1390
|
+
function provideReferences(document, position, docScope, workspace) {
|
|
1391
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1392
|
+
let targetDecl = null;
|
|
1393
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1394
|
+
if (ref?.resolved) targetDecl = ref.resolved;
|
|
1395
|
+
if (!targetDecl) targetDecl = findDeclarationForNode(node, docScope);
|
|
1396
|
+
if (!targetDecl) return [];
|
|
1397
|
+
const locations = [];
|
|
1398
|
+
if (workspace) for (const wsDoc of workspace.getAllDocuments()) collectReferencesInDocument(wsDoc.document.uri, wsDoc.scope, targetDecl, locations);
|
|
1399
|
+
else collectReferencesInDocument(document.uri, docScope, targetDecl, locations);
|
|
1400
|
+
return locations;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Collect all references to a declaration within a single document
|
|
1404
|
+
*/
|
|
1405
|
+
function collectReferencesInDocument(uri, docScope, targetDecl, locations) {
|
|
1406
|
+
for (const ref of docScope.references) {
|
|
1407
|
+
if (!ref.resolved) continue;
|
|
1408
|
+
if (ref.resolved.name === targetDecl.name && ref.resolved.declaredBy === targetDecl.declaredBy) locations.push({
|
|
1409
|
+
uri,
|
|
1410
|
+
range: nodeToRange(ref.node)
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/runtime/lsp/completion.ts
|
|
1417
|
+
/**
|
|
1418
|
+
* LSP CompletionItemKind mapping
|
|
1419
|
+
*/
|
|
1420
|
+
const COMPLETION_KIND_MAP = {
|
|
1421
|
+
Text: 1,
|
|
1422
|
+
Method: 2,
|
|
1423
|
+
Function: 3,
|
|
1424
|
+
Constructor: 4,
|
|
1425
|
+
Field: 5,
|
|
1426
|
+
Variable: 6,
|
|
1427
|
+
Class: 7,
|
|
1428
|
+
Interface: 8,
|
|
1429
|
+
Module: 9,
|
|
1430
|
+
Property: 10,
|
|
1431
|
+
Enum: 13,
|
|
1432
|
+
Keyword: 14,
|
|
1433
|
+
Snippet: 15,
|
|
1434
|
+
Constant: 21
|
|
1435
|
+
};
|
|
1436
|
+
/**
|
|
1437
|
+
* Provide completions at position
|
|
1438
|
+
*
|
|
1439
|
+
* Sources:
|
|
1440
|
+
* 1. Scope-based: all declarations visible from current scope
|
|
1441
|
+
* 2. Keywords: from $keywords config
|
|
1442
|
+
* 3. Custom: from per-rule complete() handlers
|
|
1443
|
+
*/
|
|
1444
|
+
function provideCompletion(document, position, docScope, semantic, lsp, workspace) {
|
|
1445
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1446
|
+
const ctx = createLspContext(docScope, workspace ?? {}, document, semantic);
|
|
1447
|
+
const scopeItems = getScopeCompletions(node, docScope, lsp);
|
|
1448
|
+
const keywordItems = getKeywordCompletions(lsp);
|
|
1449
|
+
const customResult = getCustomCompletions(node, ctx, lsp);
|
|
1450
|
+
if (customResult?.replace) return customResult.items;
|
|
1451
|
+
const allItems = [...scopeItems, ...keywordItems];
|
|
1452
|
+
if (customResult) allItems.push(...customResult.items);
|
|
1453
|
+
return deduplicateCompletions(allItems);
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Get completions from all declarations visible in scope
|
|
1457
|
+
*/
|
|
1458
|
+
function getScopeCompletions(node, docScope, lsp) {
|
|
1459
|
+
const scope = findScopeForNode(node, docScope);
|
|
1460
|
+
const items = [];
|
|
1461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1462
|
+
let currentScope = scope;
|
|
1463
|
+
while (currentScope) {
|
|
1464
|
+
for (const decl of currentScope.allDeclarations()) {
|
|
1465
|
+
if (seen.has(decl.name)) continue;
|
|
1466
|
+
seen.add(decl.name);
|
|
1467
|
+
const lspRule = lsp?.[decl.declaredBy];
|
|
1468
|
+
const kind = lspRule?.completionKind;
|
|
1469
|
+
const item = {
|
|
1470
|
+
label: decl.name,
|
|
1471
|
+
detail: decl.declaredBy
|
|
1472
|
+
};
|
|
1473
|
+
if (kind) item.kind = kind;
|
|
1474
|
+
items.push(item);
|
|
1475
|
+
}
|
|
1476
|
+
if (currentScope.kind === "isolated") break;
|
|
1477
|
+
currentScope = currentScope.parent;
|
|
1478
|
+
}
|
|
1479
|
+
return items;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Get keyword completions from $keywords config
|
|
1483
|
+
*/
|
|
1484
|
+
function getKeywordCompletions(lsp) {
|
|
1485
|
+
const keywords = lsp?.$keywords;
|
|
1486
|
+
if (!keywords) return [];
|
|
1487
|
+
return Object.entries(keywords).map(([keyword, descriptor]) => {
|
|
1488
|
+
const item = {
|
|
1489
|
+
label: keyword,
|
|
1490
|
+
kind: "Keyword"
|
|
1491
|
+
};
|
|
1492
|
+
if (descriptor.detail) item.detail = descriptor.detail;
|
|
1493
|
+
if (descriptor.documentation) item.documentation = descriptor.documentation;
|
|
1494
|
+
return item;
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Get custom completions from per-rule complete() handler
|
|
1499
|
+
*/
|
|
1500
|
+
function getCustomCompletions(node, ctx, lsp) {
|
|
1501
|
+
if (!lsp) return null;
|
|
1502
|
+
let current = node;
|
|
1503
|
+
while (current) {
|
|
1504
|
+
const handler = lsp[current.type]?.complete;
|
|
1505
|
+
if (handler) {
|
|
1506
|
+
const result = handler(current, ctx);
|
|
1507
|
+
if (Array.isArray(result)) return { items: result };
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
current = current.parent;
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Remove duplicate completion items (by label)
|
|
1516
|
+
*/
|
|
1517
|
+
function deduplicateCompletions(items) {
|
|
1518
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1519
|
+
const result = [];
|
|
1520
|
+
for (const item of items) if (!seen.has(item.label)) {
|
|
1521
|
+
seen.add(item.label);
|
|
1522
|
+
result.push(item);
|
|
1523
|
+
}
|
|
1524
|
+
return result;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
//#endregion
|
|
1528
|
+
//#region src/runtime/lsp/rename.ts
|
|
1529
|
+
/**
|
|
1530
|
+
* Check if rename is possible at position and return the symbol range
|
|
1531
|
+
*
|
|
1532
|
+
* Returns the range of the symbol and its current name as placeholder,
|
|
1533
|
+
* or null if the position is not on a renameable symbol.
|
|
1534
|
+
*/
|
|
1535
|
+
function prepareRename(document, position, docScope) {
|
|
1536
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1537
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1538
|
+
if (ref?.resolved) return {
|
|
1539
|
+
range: nodeToRange(node),
|
|
1540
|
+
placeholder: ref.name
|
|
1541
|
+
};
|
|
1542
|
+
const decl = findDeclarationForNode(node, docScope);
|
|
1543
|
+
if (decl) return {
|
|
1544
|
+
range: nodeToRange(node),
|
|
1545
|
+
placeholder: decl.name
|
|
1546
|
+
};
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Provide rename edits for symbol at position
|
|
1551
|
+
*
|
|
1552
|
+
* Strategy:
|
|
1553
|
+
* 1. Find declaration (directly or via reference resolution)
|
|
1554
|
+
* 2. Find all references across workspace
|
|
1555
|
+
* 3. Return edits for declaration + all references
|
|
1556
|
+
*/
|
|
1557
|
+
function provideRename(document, position, newName, docScope, workspace) {
|
|
1558
|
+
const node = findNodeAtPosition(document.root, position);
|
|
1559
|
+
let targetDecl = null;
|
|
1560
|
+
const ref = findReferenceForNode(node, docScope);
|
|
1561
|
+
if (ref?.resolved) targetDecl = ref.resolved;
|
|
1562
|
+
if (!targetDecl) targetDecl = findDeclarationForNode(node, docScope);
|
|
1563
|
+
if (!targetDecl) return null;
|
|
1564
|
+
const changes = {};
|
|
1565
|
+
function addEdit(uri, targetNode) {
|
|
1566
|
+
const edits = changes[uri] ?? (changes[uri] = []);
|
|
1567
|
+
edits.push({
|
|
1568
|
+
range: nodeToRange(targetNode),
|
|
1569
|
+
newText: newName
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
if (workspace) for (const wsDoc of workspace.getAllDocuments()) {
|
|
1573
|
+
const uri = wsDoc.document.uri;
|
|
1574
|
+
for (const decl of wsDoc.scope.declarations) if (decl.name === targetDecl.name && decl.declaredBy === targetDecl.declaredBy) addEdit(uri, decl.node);
|
|
1575
|
+
for (const r of wsDoc.scope.references) if (r.resolved && r.resolved.name === targetDecl.name && r.resolved.declaredBy === targetDecl.declaredBy) addEdit(uri, r.node);
|
|
1576
|
+
}
|
|
1577
|
+
else {
|
|
1578
|
+
addEdit(document.uri, targetDecl.node);
|
|
1579
|
+
for (const r of docScope.references) if (r.resolved && r.resolved.name === targetDecl.name && r.resolved.declaredBy === targetDecl.declaredBy) addEdit(document.uri, r.node);
|
|
1580
|
+
}
|
|
1581
|
+
return { changes };
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
//#endregion
|
|
1585
|
+
//#region src/runtime/lsp/symbols.ts
|
|
1586
|
+
/**
|
|
1587
|
+
* LSP SymbolKind mapping
|
|
1588
|
+
*/
|
|
1589
|
+
const SYMBOL_KIND_MAP = {
|
|
1590
|
+
File: 1,
|
|
1591
|
+
Module: 2,
|
|
1592
|
+
Namespace: 3,
|
|
1593
|
+
Package: 4,
|
|
1594
|
+
Class: 5,
|
|
1595
|
+
Method: 6,
|
|
1596
|
+
Property: 7,
|
|
1597
|
+
Field: 8,
|
|
1598
|
+
Constructor: 9,
|
|
1599
|
+
Enum: 10,
|
|
1600
|
+
Interface: 11,
|
|
1601
|
+
Function: 12,
|
|
1602
|
+
Variable: 13,
|
|
1603
|
+
Constant: 14,
|
|
1604
|
+
String: 15,
|
|
1605
|
+
Number: 16,
|
|
1606
|
+
Boolean: 17,
|
|
1607
|
+
Array: 18
|
|
1608
|
+
};
|
|
1609
|
+
/**
|
|
1610
|
+
* Provide document symbols
|
|
1611
|
+
*
|
|
1612
|
+
* Walks all declarations and builds symbol entries for those
|
|
1613
|
+
* with a symbol descriptor defined in the LSP config
|
|
1614
|
+
*/
|
|
1615
|
+
function provideSymbols(docScope, lsp) {
|
|
1616
|
+
if (!lsp) return [];
|
|
1617
|
+
const symbols = [];
|
|
1618
|
+
for (const decl of docScope.declarations) {
|
|
1619
|
+
const lspRule = lsp[decl.declaredBy];
|
|
1620
|
+
if (!lspRule?.symbol) continue;
|
|
1621
|
+
const descriptor = lspRule.symbol;
|
|
1622
|
+
const declParent = decl.node.parent;
|
|
1623
|
+
let name;
|
|
1624
|
+
if (typeof descriptor.label === "function") name = declParent ? descriptor.label(declParent) : decl.name;
|
|
1625
|
+
else name = descriptor.label;
|
|
1626
|
+
let detail;
|
|
1627
|
+
if (typeof descriptor.detail === "function") detail = declParent ? descriptor.detail(declParent) : void 0;
|
|
1628
|
+
else detail = descriptor.detail;
|
|
1629
|
+
const rangeNode = declParent ?? decl.node;
|
|
1630
|
+
const sym = {
|
|
1631
|
+
name,
|
|
1632
|
+
kind: descriptor.kind,
|
|
1633
|
+
kindNumber: SYMBOL_KIND_MAP[descriptor.kind] ?? 13,
|
|
1634
|
+
range: {
|
|
1635
|
+
start: rangeNode.startPosition,
|
|
1636
|
+
end: rangeNode.endPosition
|
|
1637
|
+
},
|
|
1638
|
+
selectionRange: {
|
|
1639
|
+
start: decl.node.startPosition,
|
|
1640
|
+
end: decl.node.endPosition
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
if (detail) sym.detail = detail;
|
|
1644
|
+
symbols.push(sym);
|
|
1645
|
+
}
|
|
1646
|
+
return symbols;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
//#endregion
|
|
1650
|
+
//#region src/runtime/lsp/semantic-tokens.ts
|
|
1651
|
+
/**
|
|
1652
|
+
* Standard LSP semantic token types
|
|
1653
|
+
* Index in this array = tokenType value in the encoded data
|
|
1654
|
+
*/
|
|
1655
|
+
const SEMANTIC_TOKEN_TYPES = [
|
|
1656
|
+
"namespace",
|
|
1657
|
+
"type",
|
|
1658
|
+
"class",
|
|
1659
|
+
"enum",
|
|
1660
|
+
"interface",
|
|
1661
|
+
"struct",
|
|
1662
|
+
"typeParameter",
|
|
1663
|
+
"parameter",
|
|
1664
|
+
"variable",
|
|
1665
|
+
"property",
|
|
1666
|
+
"enumMember",
|
|
1667
|
+
"event",
|
|
1668
|
+
"function",
|
|
1669
|
+
"method",
|
|
1670
|
+
"macro",
|
|
1671
|
+
"keyword",
|
|
1672
|
+
"modifier",
|
|
1673
|
+
"comment",
|
|
1674
|
+
"string",
|
|
1675
|
+
"number",
|
|
1676
|
+
"regexp",
|
|
1677
|
+
"operator",
|
|
1678
|
+
"decorator"
|
|
1679
|
+
];
|
|
1680
|
+
/**
|
|
1681
|
+
* Standard LSP semantic token modifiers
|
|
1682
|
+
* Encoded as bit flags: modifier at index N has value 2^N
|
|
1683
|
+
*/
|
|
1684
|
+
const SEMANTIC_TOKEN_MODIFIERS = [
|
|
1685
|
+
"declaration",
|
|
1686
|
+
"definition",
|
|
1687
|
+
"readonly",
|
|
1688
|
+
"static",
|
|
1689
|
+
"deprecated",
|
|
1690
|
+
"abstract",
|
|
1691
|
+
"async",
|
|
1692
|
+
"modification",
|
|
1693
|
+
"documentation",
|
|
1694
|
+
"defaultLibrary"
|
|
1695
|
+
];
|
|
1696
|
+
/** Index lookup for token types */
|
|
1697
|
+
const TOKEN_TYPE_INDEX = {};
|
|
1698
|
+
for (let i = 0; i < SEMANTIC_TOKEN_TYPES.length; i++) TOKEN_TYPE_INDEX[SEMANTIC_TOKEN_TYPES[i]] = i;
|
|
1699
|
+
/** Mapping from CompletionKind to semantic token type name */
|
|
1700
|
+
const COMPLETION_KIND_TO_TOKEN_TYPE = {
|
|
1701
|
+
Variable: "variable",
|
|
1702
|
+
Function: "function",
|
|
1703
|
+
Method: "method",
|
|
1704
|
+
Class: "class",
|
|
1705
|
+
Interface: "interface",
|
|
1706
|
+
Module: "namespace",
|
|
1707
|
+
Enum: "enum",
|
|
1708
|
+
Constant: "variable",
|
|
1709
|
+
Property: "property",
|
|
1710
|
+
Field: "property",
|
|
1711
|
+
Constructor: "function"
|
|
1712
|
+
};
|
|
1713
|
+
const BRACKETS = new Set([
|
|
1714
|
+
"(",
|
|
1715
|
+
")",
|
|
1716
|
+
"{",
|
|
1717
|
+
"}",
|
|
1718
|
+
"[",
|
|
1719
|
+
"]"
|
|
1720
|
+
]);
|
|
1721
|
+
const DELIMITERS = new Set([
|
|
1722
|
+
";",
|
|
1723
|
+
",",
|
|
1724
|
+
".",
|
|
1725
|
+
":"
|
|
1726
|
+
]);
|
|
1727
|
+
/**
|
|
1728
|
+
* Classify a named token rule by its name (heuristic)
|
|
1729
|
+
*/
|
|
1730
|
+
function classifyTokenRuleName(ruleName) {
|
|
1731
|
+
const lower = ruleName.toLowerCase();
|
|
1732
|
+
if (lower.includes("comment")) return "comment";
|
|
1733
|
+
if (lower.includes("string")) return "string";
|
|
1734
|
+
if (lower.includes("number") || lower.includes("integer") || lower.includes("float")) return "number";
|
|
1735
|
+
if (lower.includes("bool")) return "boolean";
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Compute semantic tokens for a full document
|
|
1740
|
+
*
|
|
1741
|
+
* Walks the AST and classifies each leaf node into an LSP semantic token type.
|
|
1742
|
+
* Uses the semantic and LSP definitions to determine token types for
|
|
1743
|
+
* declarations and references. Falls back to heuristics for other tokens.
|
|
1744
|
+
*/
|
|
1745
|
+
function provideSemanticTokensFull(document, docScope, semantic, lsp) {
|
|
1746
|
+
const tokens = [];
|
|
1747
|
+
const declNodeTokenType = /* @__PURE__ */ new Map();
|
|
1748
|
+
const refNodeTokenType = /* @__PURE__ */ new Map();
|
|
1749
|
+
for (const decl of docScope.declarations) {
|
|
1750
|
+
const lspRule = lsp?.[decl.declaredBy];
|
|
1751
|
+
const kind = lspRule?.completionKind;
|
|
1752
|
+
const typeName = kind ? COMPLETION_KIND_TO_TOKEN_TYPE[kind] ?? "variable" : "variable";
|
|
1753
|
+
const typeIndex = TOKEN_TYPE_INDEX[typeName] ?? TOKEN_TYPE_INDEX["variable"];
|
|
1754
|
+
declNodeTokenType.set(decl.node.id, typeIndex);
|
|
1755
|
+
}
|
|
1756
|
+
for (const ref of docScope.references) if (ref.resolved) {
|
|
1757
|
+
const lspRule = lsp?.[ref.resolved.declaredBy];
|
|
1758
|
+
const kind = lspRule?.completionKind;
|
|
1759
|
+
const typeName = kind ? COMPLETION_KIND_TO_TOKEN_TYPE[kind] ?? "variable" : "variable";
|
|
1760
|
+
const typeIndex = TOKEN_TYPE_INDEX[typeName] ?? TOKEN_TYPE_INDEX["variable"];
|
|
1761
|
+
refNodeTokenType.set(ref.node.id, typeIndex);
|
|
1762
|
+
}
|
|
1763
|
+
walkForTokens(document.root, tokens, declNodeTokenType, refNodeTokenType, semantic);
|
|
1764
|
+
tokens.sort((a, b) => a.line - b.line || a.character - b.character);
|
|
1765
|
+
const data = [];
|
|
1766
|
+
let prevLine = 0;
|
|
1767
|
+
let prevChar = 0;
|
|
1768
|
+
for (const token of tokens) {
|
|
1769
|
+
const deltaLine = token.line - prevLine;
|
|
1770
|
+
const deltaChar = deltaLine === 0 ? token.character - prevChar : token.character;
|
|
1771
|
+
data.push(deltaLine, deltaChar, token.length, token.tokenType, token.modifiers);
|
|
1772
|
+
prevLine = token.line;
|
|
1773
|
+
prevChar = token.character;
|
|
1774
|
+
}
|
|
1775
|
+
return { data };
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Walk the AST to collect semantic tokens
|
|
1779
|
+
*/
|
|
1780
|
+
function walkForTokens(node, tokens, declNodeTokenType, refNodeTokenType, semantic) {
|
|
1781
|
+
if (node.childCount === 0) {
|
|
1782
|
+
const token = classifyLeaf(node, declNodeTokenType, refNodeTokenType, semantic);
|
|
1783
|
+
if (token) tokens.push(token);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
for (const child of node.children) walkForTokens(child, tokens, declNodeTokenType, refNodeTokenType, semantic);
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Classify a leaf node into a semantic token
|
|
1790
|
+
*/
|
|
1791
|
+
function classifyLeaf(node, declNodeTokenType, refNodeTokenType, semantic) {
|
|
1792
|
+
const start = node.startPosition;
|
|
1793
|
+
const length = node.endIndex - node.startIndex;
|
|
1794
|
+
if (length <= 0) return null;
|
|
1795
|
+
const declType = declNodeTokenType.get(node.id);
|
|
1796
|
+
if (declType !== void 0) return {
|
|
1797
|
+
line: start.line,
|
|
1798
|
+
character: start.character,
|
|
1799
|
+
length,
|
|
1800
|
+
tokenType: declType,
|
|
1801
|
+
modifiers: 1
|
|
1802
|
+
};
|
|
1803
|
+
const refType = refNodeTokenType.get(node.id);
|
|
1804
|
+
if (refType !== void 0) return {
|
|
1805
|
+
line: start.line,
|
|
1806
|
+
character: start.character,
|
|
1807
|
+
length,
|
|
1808
|
+
tokenType: refType,
|
|
1809
|
+
modifiers: 0
|
|
1810
|
+
};
|
|
1811
|
+
if (!node.isNamed) {
|
|
1812
|
+
const text = node.text;
|
|
1813
|
+
if (/^[a-zA-Z_]+$/.test(text)) return {
|
|
1814
|
+
line: start.line,
|
|
1815
|
+
character: start.character,
|
|
1816
|
+
length,
|
|
1817
|
+
tokenType: TOKEN_TYPE_INDEX["keyword"],
|
|
1818
|
+
modifiers: 0
|
|
1819
|
+
};
|
|
1820
|
+
if (BRACKETS.has(text) || DELIMITERS.has(text)) return null;
|
|
1821
|
+
return {
|
|
1822
|
+
line: start.line,
|
|
1823
|
+
character: start.character,
|
|
1824
|
+
length,
|
|
1825
|
+
tokenType: TOKEN_TYPE_INDEX["operator"],
|
|
1826
|
+
modifiers: 0
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
const tokenClass = classifyTokenRuleName(node.type);
|
|
1830
|
+
if (tokenClass) {
|
|
1831
|
+
const typeIndex = TOKEN_TYPE_INDEX[tokenClass];
|
|
1832
|
+
if (typeIndex !== void 0) return {
|
|
1833
|
+
line: start.line,
|
|
1834
|
+
character: start.character,
|
|
1835
|
+
length,
|
|
1836
|
+
tokenType: typeIndex,
|
|
1837
|
+
modifiers: 0
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
const semRule = semantic[node.type];
|
|
1841
|
+
if (semRule?.references) return {
|
|
1842
|
+
line: start.line,
|
|
1843
|
+
character: start.character,
|
|
1844
|
+
length,
|
|
1845
|
+
tokenType: TOKEN_TYPE_INDEX["variable"],
|
|
1846
|
+
modifiers: 0
|
|
1847
|
+
};
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
//#endregion
|
|
1852
|
+
//#region src/runtime/lsp/server.ts
|
|
1853
|
+
/**
|
|
1854
|
+
* Create language service from language definition
|
|
1855
|
+
*
|
|
1856
|
+
* @param definition The language definition from defineLanguage()
|
|
1857
|
+
* @returns LanguageService with all LSP handler methods
|
|
1858
|
+
*/
|
|
1859
|
+
function createServer(definition) {
|
|
1860
|
+
const semantic = definition.semantic ?? {};
|
|
1861
|
+
const lsp = definition.lsp;
|
|
1862
|
+
const validation = definition.validation;
|
|
1863
|
+
const documents = new DocumentManager(semantic);
|
|
1864
|
+
function getDocScope(document) {
|
|
1865
|
+
const wsDoc = documents.get(document.uri);
|
|
1866
|
+
if (wsDoc) {
|
|
1867
|
+
if (wsDoc.document !== document) return documents.change(document);
|
|
1868
|
+
return wsDoc.scope;
|
|
1869
|
+
}
|
|
1870
|
+
return documents.open(document);
|
|
1871
|
+
}
|
|
1872
|
+
return {
|
|
1873
|
+
documents,
|
|
1874
|
+
computeDiagnostics(document) {
|
|
1875
|
+
const docScope = getDocScope(document);
|
|
1876
|
+
return computeDiagnostics(document, docScope, semantic, lsp, validation, documents.getWorkspace());
|
|
1877
|
+
},
|
|
1878
|
+
provideHover(document, position) {
|
|
1879
|
+
const docScope = getDocScope(document);
|
|
1880
|
+
return provideHover(document, position, docScope, semantic, lsp, documents.getWorkspace());
|
|
1881
|
+
},
|
|
1882
|
+
provideDefinition(document, position) {
|
|
1883
|
+
const docScope = getDocScope(document);
|
|
1884
|
+
return provideDefinition(document, position, docScope);
|
|
1885
|
+
},
|
|
1886
|
+
provideReferences(document, position) {
|
|
1887
|
+
const docScope = getDocScope(document);
|
|
1888
|
+
return provideReferences(document, position, docScope, documents.getWorkspace());
|
|
1889
|
+
},
|
|
1890
|
+
provideCompletion(document, position) {
|
|
1891
|
+
const docScope = getDocScope(document);
|
|
1892
|
+
return provideCompletion(document, position, docScope, semantic, lsp, documents.getWorkspace());
|
|
1893
|
+
},
|
|
1894
|
+
prepareRename(document, position) {
|
|
1895
|
+
const docScope = getDocScope(document);
|
|
1896
|
+
return prepareRename(document, position, docScope);
|
|
1897
|
+
},
|
|
1898
|
+
provideRename(document, position, newName) {
|
|
1899
|
+
const docScope = getDocScope(document);
|
|
1900
|
+
return provideRename(document, position, newName, docScope, documents.getWorkspace());
|
|
1901
|
+
},
|
|
1902
|
+
provideSymbols(document) {
|
|
1903
|
+
const docScope = getDocScope(document);
|
|
1904
|
+
return provideSymbols(docScope, lsp);
|
|
1905
|
+
},
|
|
1906
|
+
provideSemanticTokensFull(document) {
|
|
1907
|
+
const docScope = getDocScope(document);
|
|
1908
|
+
return provideSemanticTokensFull(document, docScope, semantic, lsp);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
//#endregion
|
|
1914
|
+
export { COMPLETION_KIND_MAP, DocumentManager, DocumentState, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, Scope, Workspace, buildScopes, createDocumentState, createParser, createServer, loadLanguage, preloadParser };
|