partnercore-proxy 0.1.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -6
- package/README.md +168 -130
- package/dist/al/language-server.d.ts +315 -2
- package/dist/al/language-server.d.ts.map +1 -1
- package/dist/al/language-server.js +676 -1
- package/dist/al/language-server.js.map +1 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +64 -0
- package/dist/config/types.js.map +1 -1
- package/dist/container/bc-container.d.ts +212 -0
- package/dist/container/bc-container.d.ts.map +1 -0
- package/dist/container/bc-container.js +740 -0
- package/dist/container/bc-container.js.map +1 -0
- package/dist/git/git-operations.d.ts +182 -0
- package/dist/git/git-operations.d.ts.map +1 -0
- package/dist/git/git-operations.js +446 -0
- package/dist/git/git-operations.js.map +1 -0
- package/dist/memory/project-memory.d.ts +83 -0
- package/dist/memory/project-memory.d.ts.map +1 -0
- package/dist/memory/project-memory.js +310 -0
- package/dist/memory/project-memory.js.map +1 -0
- package/dist/router/tool-router.d.ts +62 -0
- package/dist/router/tool-router.d.ts.map +1 -1
- package/dist/router/tool-router.js +2564 -278
- package/dist/router/tool-router.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AL Language Server Client
|
|
4
4
|
*
|
|
5
5
|
* Communicates with the Microsoft AL Language Server via LSP protocol.
|
|
6
|
-
*
|
|
6
|
+
* Provides full LSP integration for AL development.
|
|
7
7
|
*/
|
|
8
8
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
9
|
if (k2 === undefined) k2 = k;
|
|
@@ -146,6 +146,91 @@ class ALLanguageServer {
|
|
|
146
146
|
publishDiagnostics: {
|
|
147
147
|
relatedInformation: true,
|
|
148
148
|
},
|
|
149
|
+
codeAction: {
|
|
150
|
+
dynamicRegistration: true,
|
|
151
|
+
codeActionLiteralSupport: {
|
|
152
|
+
codeActionKind: {
|
|
153
|
+
valueSet: [
|
|
154
|
+
'quickfix',
|
|
155
|
+
'refactor',
|
|
156
|
+
'refactor.extract',
|
|
157
|
+
'refactor.inline',
|
|
158
|
+
'refactor.rewrite',
|
|
159
|
+
'source',
|
|
160
|
+
'source.organizeImports',
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
resolveSupport: {
|
|
165
|
+
properties: ['edit'],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
signatureHelp: {
|
|
169
|
+
dynamicRegistration: true,
|
|
170
|
+
signatureInformation: {
|
|
171
|
+
documentationFormat: ['markdown', 'plaintext'],
|
|
172
|
+
parameterInformation: {
|
|
173
|
+
labelOffsetSupport: true,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
contextSupport: true,
|
|
177
|
+
},
|
|
178
|
+
formatting: {
|
|
179
|
+
dynamicRegistration: true,
|
|
180
|
+
},
|
|
181
|
+
rangeFormatting: {
|
|
182
|
+
dynamicRegistration: true,
|
|
183
|
+
},
|
|
184
|
+
onTypeFormatting: {
|
|
185
|
+
dynamicRegistration: true,
|
|
186
|
+
},
|
|
187
|
+
documentHighlight: {
|
|
188
|
+
dynamicRegistration: true,
|
|
189
|
+
},
|
|
190
|
+
foldingRange: {
|
|
191
|
+
dynamicRegistration: true,
|
|
192
|
+
foldingRangeKind: {
|
|
193
|
+
valueSet: ['comment', 'imports', 'region'],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
selectionRange: {
|
|
197
|
+
dynamicRegistration: true,
|
|
198
|
+
},
|
|
199
|
+
codeLens: {
|
|
200
|
+
dynamicRegistration: true,
|
|
201
|
+
},
|
|
202
|
+
documentLink: {
|
|
203
|
+
dynamicRegistration: true,
|
|
204
|
+
tooltipSupport: true,
|
|
205
|
+
},
|
|
206
|
+
typeDefinition: {
|
|
207
|
+
dynamicRegistration: true,
|
|
208
|
+
linkSupport: true,
|
|
209
|
+
},
|
|
210
|
+
implementation: {
|
|
211
|
+
dynamicRegistration: true,
|
|
212
|
+
linkSupport: true,
|
|
213
|
+
},
|
|
214
|
+
semanticTokens: {
|
|
215
|
+
dynamicRegistration: true,
|
|
216
|
+
tokenTypes: [
|
|
217
|
+
'namespace', 'type', 'class', 'enum', 'interface', 'struct',
|
|
218
|
+
'typeParameter', 'parameter', 'variable', 'property', 'enumMember',
|
|
219
|
+
'event', 'function', 'method', 'macro', 'keyword', 'modifier',
|
|
220
|
+
'comment', 'string', 'number', 'regexp', 'operator', 'decorator',
|
|
221
|
+
],
|
|
222
|
+
tokenModifiers: [
|
|
223
|
+
'declaration', 'definition', 'readonly', 'static', 'deprecated',
|
|
224
|
+
'abstract', 'async', 'modification', 'documentation', 'defaultLibrary',
|
|
225
|
+
],
|
|
226
|
+
formats: ['relative'],
|
|
227
|
+
requests: {
|
|
228
|
+
full: true,
|
|
229
|
+
range: true,
|
|
230
|
+
},
|
|
231
|
+
multilineTokenSupport: false,
|
|
232
|
+
overlappingTokenSupport: false,
|
|
233
|
+
},
|
|
149
234
|
},
|
|
150
235
|
workspace: {
|
|
151
236
|
applyEdit: true,
|
|
@@ -313,6 +398,269 @@ class ALLanguageServer {
|
|
|
313
398
|
const result = await this.connection.sendRequest('workspace/symbol', { query });
|
|
314
399
|
return result || [];
|
|
315
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Update document content (for editing)
|
|
403
|
+
*/
|
|
404
|
+
async updateDocument(uri, content, version) {
|
|
405
|
+
if (!this.connection) {
|
|
406
|
+
throw new Error('Language server not initialized');
|
|
407
|
+
}
|
|
408
|
+
// If document is not open, open it first
|
|
409
|
+
if (!this.openDocuments.has(uri)) {
|
|
410
|
+
await this.openDocument(uri);
|
|
411
|
+
}
|
|
412
|
+
const params = {
|
|
413
|
+
textDocument: { uri, version },
|
|
414
|
+
contentChanges: [{ text: content }],
|
|
415
|
+
};
|
|
416
|
+
void this.connection.sendNotification('textDocument/didChange', params);
|
|
417
|
+
// Wait for diagnostics to be processed
|
|
418
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Rename symbol across the workspace
|
|
422
|
+
* @returns WorkspaceEdit with all changes needed
|
|
423
|
+
*/
|
|
424
|
+
async renameSymbol(uri, line, character, newName) {
|
|
425
|
+
await this.ensureInitialized();
|
|
426
|
+
await this.openDocument(uri);
|
|
427
|
+
// First, check if rename is valid
|
|
428
|
+
const prepareParams = {
|
|
429
|
+
textDocument: { uri },
|
|
430
|
+
position: { line, character },
|
|
431
|
+
};
|
|
432
|
+
try {
|
|
433
|
+
const prepareResult = await this.connection.sendRequest('textDocument/prepareRename', prepareParams);
|
|
434
|
+
if (!prepareResult) {
|
|
435
|
+
this.logger.debug('Rename not available at this position');
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
// Perform the rename
|
|
439
|
+
const renameParams = {
|
|
440
|
+
textDocument: { uri },
|
|
441
|
+
position: { line, character },
|
|
442
|
+
newName,
|
|
443
|
+
};
|
|
444
|
+
const result = await this.connection.sendRequest('textDocument/rename', renameParams);
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
this.logger.error('Rename failed:', error);
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get the symbol at a specific position
|
|
454
|
+
*/
|
|
455
|
+
async getSymbolAtPosition(uri, line, character) {
|
|
456
|
+
const symbols = await this.getDocumentSymbols(uri);
|
|
457
|
+
return this.findSymbolAtPosition(symbols, line, character);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get code actions (quick fixes, refactorings) at a specific position or range
|
|
461
|
+
*/
|
|
462
|
+
async getCodeActions(uri, range, context) {
|
|
463
|
+
await this.ensureInitialized();
|
|
464
|
+
await this.openDocument(uri);
|
|
465
|
+
// Build diagnostics with proper typing
|
|
466
|
+
const diagnostics = context?.diagnostics?.map(d => ({
|
|
467
|
+
message: d.message,
|
|
468
|
+
severity: this.severityToNumber(d.severity),
|
|
469
|
+
range: d.range,
|
|
470
|
+
code: d.code,
|
|
471
|
+
source: d.source,
|
|
472
|
+
})) || [];
|
|
473
|
+
const params = {
|
|
474
|
+
textDocument: { uri },
|
|
475
|
+
range,
|
|
476
|
+
context: {
|
|
477
|
+
diagnostics,
|
|
478
|
+
only: context?.only,
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
const result = await this.connection.sendRequest('textDocument/codeAction', params);
|
|
482
|
+
if (!result)
|
|
483
|
+
return [];
|
|
484
|
+
return result.map(item => {
|
|
485
|
+
// Check if it's a CodeAction (has 'title' as required field for both, but CodeAction has more fields)
|
|
486
|
+
const isCodeAction = 'kind' in item || 'edit' in item || ('command' in item && typeof item.command === 'object');
|
|
487
|
+
if (isCodeAction) {
|
|
488
|
+
const action = item;
|
|
489
|
+
let editResult;
|
|
490
|
+
if (action.edit && action.edit.changes) {
|
|
491
|
+
editResult = {
|
|
492
|
+
changes: action.edit.changes,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
title: action.title,
|
|
497
|
+
kind: action.kind,
|
|
498
|
+
isPreferred: action.isPreferred,
|
|
499
|
+
edit: editResult,
|
|
500
|
+
command: action.command ? {
|
|
501
|
+
title: action.command.title,
|
|
502
|
+
command: action.command.command,
|
|
503
|
+
arguments: action.command.arguments,
|
|
504
|
+
} : undefined,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
// It's a Command (simpler structure: title, command string, arguments)
|
|
509
|
+
const cmd = item;
|
|
510
|
+
return {
|
|
511
|
+
title: cmd.title,
|
|
512
|
+
command: {
|
|
513
|
+
title: cmd.title,
|
|
514
|
+
command: cmd.command,
|
|
515
|
+
arguments: cmd.arguments,
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get signature help (function parameter hints) at a position
|
|
523
|
+
*/
|
|
524
|
+
async getSignatureHelp(uri, line, character, context) {
|
|
525
|
+
await this.ensureInitialized();
|
|
526
|
+
await this.openDocument(uri);
|
|
527
|
+
const params = {
|
|
528
|
+
textDocument: { uri },
|
|
529
|
+
position: { line, character },
|
|
530
|
+
context: context ? {
|
|
531
|
+
triggerKind: context.triggerKind || 1,
|
|
532
|
+
triggerCharacter: context.triggerCharacter,
|
|
533
|
+
isRetrigger: context.isRetrigger || false,
|
|
534
|
+
} : undefined,
|
|
535
|
+
};
|
|
536
|
+
const result = await this.connection.sendRequest('textDocument/signatureHelp', params);
|
|
537
|
+
if (!result)
|
|
538
|
+
return null;
|
|
539
|
+
return {
|
|
540
|
+
signatures: result.signatures.map(sig => ({
|
|
541
|
+
label: sig.label,
|
|
542
|
+
documentation: typeof sig.documentation === 'string'
|
|
543
|
+
? sig.documentation
|
|
544
|
+
: sig.documentation?.value,
|
|
545
|
+
parameters: sig.parameters?.map(p => ({
|
|
546
|
+
label: p.label,
|
|
547
|
+
documentation: typeof p.documentation === 'string'
|
|
548
|
+
? p.documentation
|
|
549
|
+
: p.documentation?.value,
|
|
550
|
+
})),
|
|
551
|
+
})),
|
|
552
|
+
activeSignature: result.activeSignature,
|
|
553
|
+
activeParameter: result.activeParameter,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Format an entire document
|
|
558
|
+
*/
|
|
559
|
+
async formatDocument(uri, options) {
|
|
560
|
+
await this.ensureInitialized();
|
|
561
|
+
await this.openDocument(uri);
|
|
562
|
+
const formattingOptions = {
|
|
563
|
+
tabSize: options?.tabSize ?? 4,
|
|
564
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
565
|
+
};
|
|
566
|
+
const params = {
|
|
567
|
+
textDocument: { uri },
|
|
568
|
+
options: formattingOptions,
|
|
569
|
+
};
|
|
570
|
+
const result = await this.connection.sendRequest('textDocument/formatting', params);
|
|
571
|
+
if (!result)
|
|
572
|
+
return [];
|
|
573
|
+
return result.map(edit => ({
|
|
574
|
+
range: {
|
|
575
|
+
start: { line: edit.range.start.line, character: edit.range.start.character },
|
|
576
|
+
end: { line: edit.range.end.line, character: edit.range.end.character },
|
|
577
|
+
},
|
|
578
|
+
newText: edit.newText,
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Format a range within a document
|
|
583
|
+
*/
|
|
584
|
+
async formatRange(uri, range, options) {
|
|
585
|
+
await this.ensureInitialized();
|
|
586
|
+
await this.openDocument(uri);
|
|
587
|
+
const formattingOptions = {
|
|
588
|
+
tabSize: options?.tabSize ?? 4,
|
|
589
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
590
|
+
};
|
|
591
|
+
const params = {
|
|
592
|
+
textDocument: { uri },
|
|
593
|
+
range,
|
|
594
|
+
options: formattingOptions,
|
|
595
|
+
};
|
|
596
|
+
const result = await this.connection.sendRequest('textDocument/rangeFormatting', params);
|
|
597
|
+
if (!result)
|
|
598
|
+
return [];
|
|
599
|
+
return result.map(edit => ({
|
|
600
|
+
range: {
|
|
601
|
+
start: { line: edit.range.start.line, character: edit.range.start.character },
|
|
602
|
+
end: { line: edit.range.end.line, character: edit.range.end.character },
|
|
603
|
+
},
|
|
604
|
+
newText: edit.newText,
|
|
605
|
+
}));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Convert severity string to LSP severity number
|
|
609
|
+
*/
|
|
610
|
+
severityToNumber(severity) {
|
|
611
|
+
switch (severity) {
|
|
612
|
+
case 'error': return 1;
|
|
613
|
+
case 'warning': return 2;
|
|
614
|
+
case 'info': return 3;
|
|
615
|
+
case 'hint': return 4;
|
|
616
|
+
default: return 3;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Find symbol containing the given position
|
|
621
|
+
*/
|
|
622
|
+
findSymbolAtPosition(symbols, line, character) {
|
|
623
|
+
for (const symbol of symbols) {
|
|
624
|
+
const { start, end } = symbol.range;
|
|
625
|
+
// Check if position is within this symbol's range
|
|
626
|
+
const isAfterStart = line > start.line || (line === start.line && character >= start.character);
|
|
627
|
+
const isBeforeEnd = line < end.line || (line === end.line && character <= end.character);
|
|
628
|
+
if (isAfterStart && isBeforeEnd) {
|
|
629
|
+
// Check children first (more specific match)
|
|
630
|
+
if (symbol.children) {
|
|
631
|
+
const childMatch = this.findSymbolAtPosition(symbol.children, line, character);
|
|
632
|
+
if (childMatch) {
|
|
633
|
+
return childMatch;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return symbol;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Find a symbol by name in the document
|
|
643
|
+
*/
|
|
644
|
+
async findSymbolByName(uri, symbolName) {
|
|
645
|
+
const symbols = await this.getDocumentSymbols(uri);
|
|
646
|
+
return this.searchSymbolByName(symbols, symbolName);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Search for symbol by name recursively
|
|
650
|
+
*/
|
|
651
|
+
searchSymbolByName(symbols, name) {
|
|
652
|
+
for (const symbol of symbols) {
|
|
653
|
+
if (symbol.name.toLowerCase() === name.toLowerCase()) {
|
|
654
|
+
return symbol;
|
|
655
|
+
}
|
|
656
|
+
if (symbol.children) {
|
|
657
|
+
const found = this.searchSymbolByName(symbol.children, name);
|
|
658
|
+
if (found)
|
|
659
|
+
return found;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
316
664
|
/**
|
|
317
665
|
* Convert LSP symbols to our format
|
|
318
666
|
*/
|
|
@@ -403,6 +751,333 @@ class ALLanguageServer {
|
|
|
403
751
|
await this.initialize();
|
|
404
752
|
}
|
|
405
753
|
}
|
|
754
|
+
// ==================== Remaining LSP Features ====================
|
|
755
|
+
/**
|
|
756
|
+
* Close a document (cleanup)
|
|
757
|
+
*/
|
|
758
|
+
async closeDocument(uri) {
|
|
759
|
+
await this.ensureInitialized();
|
|
760
|
+
if (!this.openDocuments.has(uri)) {
|
|
761
|
+
return; // Not open
|
|
762
|
+
}
|
|
763
|
+
const params = {
|
|
764
|
+
textDocument: { uri },
|
|
765
|
+
};
|
|
766
|
+
await this.connection.sendNotification('textDocument/didClose', params);
|
|
767
|
+
this.openDocuments.delete(uri);
|
|
768
|
+
this.diagnosticsCache.delete(uri);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Notify save (triggers recompile in some LSPs)
|
|
772
|
+
*/
|
|
773
|
+
async saveDocument(uri, text) {
|
|
774
|
+
await this.ensureInitialized();
|
|
775
|
+
await this.openDocument(uri);
|
|
776
|
+
const params = {
|
|
777
|
+
textDocument: { uri },
|
|
778
|
+
text,
|
|
779
|
+
};
|
|
780
|
+
await this.connection.sendNotification('textDocument/didSave', params);
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Get document highlights (all occurrences of symbol under cursor)
|
|
784
|
+
*/
|
|
785
|
+
async getDocumentHighlights(uri, line, character) {
|
|
786
|
+
await this.ensureInitialized();
|
|
787
|
+
await this.openDocument(uri);
|
|
788
|
+
const params = {
|
|
789
|
+
textDocument: { uri },
|
|
790
|
+
position: { line, character },
|
|
791
|
+
};
|
|
792
|
+
const result = await this.connection.sendRequest('textDocument/documentHighlight', params);
|
|
793
|
+
if (!result)
|
|
794
|
+
return [];
|
|
795
|
+
return result.map(h => ({
|
|
796
|
+
range: {
|
|
797
|
+
start: { line: h.range.start.line, character: h.range.start.character },
|
|
798
|
+
end: { line: h.range.end.line, character: h.range.end.character },
|
|
799
|
+
},
|
|
800
|
+
kind: h.kind === 1 ? 'text' : h.kind === 2 ? 'read' : h.kind === 3 ? 'write' : undefined,
|
|
801
|
+
}));
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Get folding ranges (code regions, procedures, etc.)
|
|
805
|
+
*/
|
|
806
|
+
async getFoldingRanges(uri) {
|
|
807
|
+
await this.ensureInitialized();
|
|
808
|
+
await this.openDocument(uri);
|
|
809
|
+
const params = {
|
|
810
|
+
textDocument: { uri },
|
|
811
|
+
};
|
|
812
|
+
const result = await this.connection.sendRequest('textDocument/foldingRange', params);
|
|
813
|
+
if (!result)
|
|
814
|
+
return [];
|
|
815
|
+
return result.map(r => ({
|
|
816
|
+
startLine: r.startLine,
|
|
817
|
+
startCharacter: r.startCharacter,
|
|
818
|
+
endLine: r.endLine,
|
|
819
|
+
endCharacter: r.endCharacter,
|
|
820
|
+
kind: r.kind,
|
|
821
|
+
}));
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Get selection ranges (smart expand/shrink selection)
|
|
825
|
+
*/
|
|
826
|
+
async getSelectionRanges(uri, positions) {
|
|
827
|
+
await this.ensureInitialized();
|
|
828
|
+
await this.openDocument(uri);
|
|
829
|
+
const params = {
|
|
830
|
+
textDocument: { uri },
|
|
831
|
+
positions,
|
|
832
|
+
};
|
|
833
|
+
const result = await this.connection.sendRequest('textDocument/selectionRange', params);
|
|
834
|
+
if (!result)
|
|
835
|
+
return [];
|
|
836
|
+
const convertSelectionRange = (sr) => ({
|
|
837
|
+
range: {
|
|
838
|
+
start: { line: sr.range.start.line, character: sr.range.start.character },
|
|
839
|
+
end: { line: sr.range.end.line, character: sr.range.end.character },
|
|
840
|
+
},
|
|
841
|
+
parent: sr.parent ? convertSelectionRange(sr.parent) : undefined,
|
|
842
|
+
});
|
|
843
|
+
return result.map(convertSelectionRange);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Go to type definition (variable's type)
|
|
847
|
+
*/
|
|
848
|
+
async getTypeDefinition(uri, line, character) {
|
|
849
|
+
await this.ensureInitialized();
|
|
850
|
+
await this.openDocument(uri);
|
|
851
|
+
const params = {
|
|
852
|
+
textDocument: { uri },
|
|
853
|
+
position: { line, character },
|
|
854
|
+
};
|
|
855
|
+
const result = await this.connection.sendRequest('textDocument/typeDefinition', params);
|
|
856
|
+
if (!result)
|
|
857
|
+
return [];
|
|
858
|
+
return Array.isArray(result) ? result : [result];
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Go to implementation (interface implementations)
|
|
862
|
+
*/
|
|
863
|
+
async getImplementation(uri, line, character) {
|
|
864
|
+
await this.ensureInitialized();
|
|
865
|
+
await this.openDocument(uri);
|
|
866
|
+
const params = {
|
|
867
|
+
textDocument: { uri },
|
|
868
|
+
position: { line, character },
|
|
869
|
+
};
|
|
870
|
+
const result = await this.connection.sendRequest('textDocument/implementation', params);
|
|
871
|
+
if (!result)
|
|
872
|
+
return [];
|
|
873
|
+
return Array.isArray(result) ? result : [result];
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Format on type (format after specific character like ';')
|
|
877
|
+
*/
|
|
878
|
+
async formatOnType(uri, line, character, ch, options) {
|
|
879
|
+
await this.ensureInitialized();
|
|
880
|
+
await this.openDocument(uri);
|
|
881
|
+
const params = {
|
|
882
|
+
textDocument: { uri },
|
|
883
|
+
position: { line, character },
|
|
884
|
+
ch,
|
|
885
|
+
options: {
|
|
886
|
+
tabSize: options?.tabSize ?? 4,
|
|
887
|
+
insertSpaces: options?.insertSpaces ?? true,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
const result = await this.connection.sendRequest('textDocument/onTypeFormatting', params);
|
|
891
|
+
if (!result)
|
|
892
|
+
return [];
|
|
893
|
+
return result.map(edit => ({
|
|
894
|
+
range: {
|
|
895
|
+
start: { line: edit.range.start.line, character: edit.range.start.character },
|
|
896
|
+
end: { line: edit.range.end.line, character: edit.range.end.character },
|
|
897
|
+
},
|
|
898
|
+
newText: edit.newText,
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Get code lenses (inline hints like reference counts)
|
|
903
|
+
*/
|
|
904
|
+
async getCodeLenses(uri) {
|
|
905
|
+
await this.ensureInitialized();
|
|
906
|
+
await this.openDocument(uri);
|
|
907
|
+
const params = {
|
|
908
|
+
textDocument: { uri },
|
|
909
|
+
};
|
|
910
|
+
const result = await this.connection.sendRequest('textDocument/codeLens', params);
|
|
911
|
+
if (!result)
|
|
912
|
+
return [];
|
|
913
|
+
return result.map(lens => ({
|
|
914
|
+
range: {
|
|
915
|
+
start: { line: lens.range.start.line, character: lens.range.start.character },
|
|
916
|
+
end: { line: lens.range.end.line, character: lens.range.end.character },
|
|
917
|
+
},
|
|
918
|
+
command: lens.command ? {
|
|
919
|
+
title: lens.command.title,
|
|
920
|
+
command: lens.command.command,
|
|
921
|
+
arguments: lens.command.arguments,
|
|
922
|
+
} : undefined,
|
|
923
|
+
data: lens.data,
|
|
924
|
+
}));
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Resolve a code lens (get the command for a lens)
|
|
928
|
+
*/
|
|
929
|
+
async resolveCodeLens(lens) {
|
|
930
|
+
await this.ensureInitialized();
|
|
931
|
+
const lspLens = {
|
|
932
|
+
range: {
|
|
933
|
+
start: { line: lens.range.start.line, character: lens.range.start.character },
|
|
934
|
+
end: { line: lens.range.end.line, character: lens.range.end.character },
|
|
935
|
+
},
|
|
936
|
+
data: lens.data,
|
|
937
|
+
};
|
|
938
|
+
const result = await this.connection.sendRequest('codeLens/resolve', lspLens);
|
|
939
|
+
return {
|
|
940
|
+
range: {
|
|
941
|
+
start: { line: result.range.start.line, character: result.range.start.character },
|
|
942
|
+
end: { line: result.range.end.line, character: result.range.end.character },
|
|
943
|
+
},
|
|
944
|
+
command: result.command ? {
|
|
945
|
+
title: result.command.title,
|
|
946
|
+
command: result.command.command,
|
|
947
|
+
arguments: result.command.arguments,
|
|
948
|
+
} : undefined,
|
|
949
|
+
data: result.data,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Get document links (clickable URLs in comments)
|
|
954
|
+
*/
|
|
955
|
+
async getDocumentLinks(uri) {
|
|
956
|
+
await this.ensureInitialized();
|
|
957
|
+
await this.openDocument(uri);
|
|
958
|
+
const params = {
|
|
959
|
+
textDocument: { uri },
|
|
960
|
+
};
|
|
961
|
+
const result = await this.connection.sendRequest('textDocument/documentLink', params);
|
|
962
|
+
if (!result)
|
|
963
|
+
return [];
|
|
964
|
+
return result.map(link => ({
|
|
965
|
+
range: {
|
|
966
|
+
start: { line: link.range.start.line, character: link.range.start.character },
|
|
967
|
+
end: { line: link.range.end.line, character: link.range.end.character },
|
|
968
|
+
},
|
|
969
|
+
target: link.target,
|
|
970
|
+
tooltip: link.tooltip,
|
|
971
|
+
}));
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Execute a command (e.g., from code action)
|
|
975
|
+
*/
|
|
976
|
+
async executeCommand(command, args) {
|
|
977
|
+
await this.ensureInitialized();
|
|
978
|
+
const params = {
|
|
979
|
+
command,
|
|
980
|
+
arguments: args,
|
|
981
|
+
};
|
|
982
|
+
return this.connection.sendRequest('workspace/executeCommand', params);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Get semantic tokens (for syntax highlighting)
|
|
986
|
+
*/
|
|
987
|
+
async getSemanticTokens(uri) {
|
|
988
|
+
await this.ensureInitialized();
|
|
989
|
+
await this.openDocument(uri);
|
|
990
|
+
const params = {
|
|
991
|
+
textDocument: { uri },
|
|
992
|
+
};
|
|
993
|
+
const result = await this.connection.sendRequest('textDocument/semanticTokens/full', params);
|
|
994
|
+
if (!result)
|
|
995
|
+
return null;
|
|
996
|
+
return {
|
|
997
|
+
resultId: result.resultId,
|
|
998
|
+
data: result.data,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Get semantic tokens for a range
|
|
1003
|
+
*/
|
|
1004
|
+
async getSemanticTokensRange(uri, range) {
|
|
1005
|
+
await this.ensureInitialized();
|
|
1006
|
+
await this.openDocument(uri);
|
|
1007
|
+
const params = {
|
|
1008
|
+
textDocument: { uri },
|
|
1009
|
+
range,
|
|
1010
|
+
};
|
|
1011
|
+
const result = await this.connection.sendRequest('textDocument/semanticTokens/range', params);
|
|
1012
|
+
if (!result)
|
|
1013
|
+
return null;
|
|
1014
|
+
return {
|
|
1015
|
+
resultId: result.resultId,
|
|
1016
|
+
data: result.data,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Restart the language server (useful when it hangs or after external changes)
|
|
1021
|
+
*/
|
|
1022
|
+
async restart() {
|
|
1023
|
+
this.logger.info('Restarting AL Language Server...');
|
|
1024
|
+
await this.shutdown();
|
|
1025
|
+
await this.initialize();
|
|
1026
|
+
this.logger.info('AL Language Server restarted successfully');
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Find all symbols that reference a given symbol (enhanced reference navigation)
|
|
1030
|
+
* Returns referencing symbols with context around the reference
|
|
1031
|
+
*/
|
|
1032
|
+
async findReferencingSymbols(uri, line, character, options) {
|
|
1033
|
+
await this.ensureInitialized();
|
|
1034
|
+
await this.openDocument(uri);
|
|
1035
|
+
const contextBefore = options?.contextLinesBefore ?? 1;
|
|
1036
|
+
const contextAfter = options?.contextLinesAfter ?? 1;
|
|
1037
|
+
// Get all references
|
|
1038
|
+
const params = {
|
|
1039
|
+
textDocument: { uri },
|
|
1040
|
+
position: { line, character },
|
|
1041
|
+
context: { includeDeclaration: options?.includeDeclaration ?? false },
|
|
1042
|
+
};
|
|
1043
|
+
const locations = await this.connection.sendRequest('textDocument/references', params);
|
|
1044
|
+
if (!locations || locations.length === 0) {
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
const results = [];
|
|
1048
|
+
for (const loc of locations) {
|
|
1049
|
+
const result = {
|
|
1050
|
+
location: loc,
|
|
1051
|
+
};
|
|
1052
|
+
// Try to get the containing symbol
|
|
1053
|
+
try {
|
|
1054
|
+
const symbols = await this.getDocumentSymbols(loc.uri);
|
|
1055
|
+
const containingSymbol = this.findSymbolAtPosition(symbols, loc.range.start.line, loc.range.start.character);
|
|
1056
|
+
if (containingSymbol) {
|
|
1057
|
+
result.containingSymbol = containingSymbol;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
catch {
|
|
1061
|
+
// Ignore errors getting symbols
|
|
1062
|
+
}
|
|
1063
|
+
// Try to get context snippet
|
|
1064
|
+
try {
|
|
1065
|
+
const filePath = this.uriToPath(loc.uri);
|
|
1066
|
+
if (fs.existsSync(filePath)) {
|
|
1067
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1068
|
+
const lines = content.split('\n');
|
|
1069
|
+
const startLine = Math.max(0, loc.range.start.line - contextBefore);
|
|
1070
|
+
const endLine = Math.min(lines.length - 1, loc.range.start.line + contextAfter);
|
|
1071
|
+
result.contextSnippet = lines.slice(startLine, endLine + 1).join('\n');
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
// Ignore errors reading file
|
|
1076
|
+
}
|
|
1077
|
+
results.push(result);
|
|
1078
|
+
}
|
|
1079
|
+
return results;
|
|
1080
|
+
}
|
|
406
1081
|
/**
|
|
407
1082
|
* Shutdown the language server
|
|
408
1083
|
*/
|