partnercore-proxy 0.1.5 → 0.4.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +114 -6
  2. package/README.md +168 -130
  3. package/dist/al/extension-manager.js +10 -50
  4. package/dist/al/extension-manager.js.map +1 -1
  5. package/dist/al/index.js +2 -18
  6. package/dist/al/index.js.map +1 -1
  7. package/dist/al/language-server.d.ts +315 -2
  8. package/dist/al/language-server.d.ts.map +1 -1
  9. package/dist/al/language-server.js +685 -47
  10. package/dist/al/language-server.js.map +1 -1
  11. package/dist/cli.js +36 -68
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cloud/index.js +1 -17
  14. package/dist/cloud/index.js.map +1 -1
  15. package/dist/cloud/relay-client.js +5 -12
  16. package/dist/cloud/relay-client.js.map +1 -1
  17. package/dist/config/index.js +2 -18
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/config/loader.js +8 -47
  20. package/dist/config/loader.js.map +1 -1
  21. package/dist/config/types.d.ts.map +1 -1
  22. package/dist/config/types.js +65 -4
  23. package/dist/config/types.js.map +1 -1
  24. package/dist/container/bc-container.d.ts +212 -0
  25. package/dist/container/bc-container.d.ts.map +1 -0
  26. package/dist/container/bc-container.js +703 -0
  27. package/dist/container/bc-container.js.map +1 -0
  28. package/dist/git/git-operations.d.ts +182 -0
  29. package/dist/git/git-operations.d.ts.map +1 -0
  30. package/dist/git/git-operations.js +442 -0
  31. package/dist/git/git-operations.js.map +1 -0
  32. package/dist/index.js +6 -22
  33. package/dist/index.js.map +1 -1
  34. package/dist/mcp/index.js +1 -17
  35. package/dist/mcp/index.js.map +1 -1
  36. package/dist/mcp/server.js +10 -14
  37. package/dist/mcp/server.js.map +1 -1
  38. package/dist/memory/project-memory.d.ts +83 -0
  39. package/dist/memory/project-memory.d.ts.map +1 -0
  40. package/dist/memory/project-memory.js +273 -0
  41. package/dist/memory/project-memory.js.map +1 -0
  42. package/dist/router/index.js +1 -17
  43. package/dist/router/index.js.map +1 -1
  44. package/dist/router/tool-router.d.ts +62 -0
  45. package/dist/router/tool-router.d.ts.map +1 -1
  46. package/dist/router/tool-router.js +2577 -328
  47. package/dist/router/tool-router.js.map +1 -1
  48. package/dist/utils/index.js +2 -18
  49. package/dist/utils/index.js.map +1 -1
  50. package/dist/utils/logger.js +8 -16
  51. package/dist/utils/logger.js.map +1 -1
  52. package/dist/utils/security.js +10 -54
  53. package/dist/utils/security.js.map +1 -1
  54. package/package.json +4 -3
@@ -1,59 +1,23 @@
1
- "use strict";
2
1
  /**
3
2
  * AL Language Server Client
4
3
  *
5
4
  * Communicates with the Microsoft AL Language Server via LSP protocol.
6
- * Based on insights from Serena project's implementation.
5
+ * Provides full LSP integration for AL development.
7
6
  */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.ALLanguageServer = void 0;
43
- const child_process_1 = require("child_process");
44
- const path = __importStar(require("path"));
45
- const fs = __importStar(require("fs"));
46
- const node_js_1 = require("vscode-jsonrpc/node.js");
47
- const logger_js_1 = require("../utils/logger.js");
7
+ import { spawn } from 'child_process';
8
+ import * as path from 'path';
9
+ import * as fs from 'fs';
10
+ import { createMessageConnection, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node.js';
11
+ import { getLogger } from '../utils/logger.js';
48
12
  /**
49
13
  * AL Language Server Client
50
14
  */
51
- class ALLanguageServer {
15
+ export class ALLanguageServer {
52
16
  extensionInfo;
53
17
  workspaceRoot;
54
18
  process = null;
55
19
  connection = null;
56
- logger = (0, logger_js_1.getLogger)();
20
+ logger = getLogger();
57
21
  initialized = false;
58
22
  openDocuments = new Set();
59
23
  diagnosticsCache = new Map();
@@ -70,7 +34,7 @@ class ALLanguageServer {
70
34
  }
71
35
  this.logger.info('Starting AL Language Server...');
72
36
  // Start the EditorServices process
73
- this.process = (0, child_process_1.spawn)(this.extensionInfo.editorServicesPath, [], {
37
+ this.process = spawn(this.extensionInfo.editorServicesPath, [], {
74
38
  cwd: this.workspaceRoot,
75
39
  stdio: ['pipe', 'pipe', 'pipe'],
76
40
  });
@@ -78,7 +42,7 @@ class ALLanguageServer {
78
42
  throw new Error('Failed to start AL Language Server process');
79
43
  }
80
44
  // Create LSP connection
81
- this.connection = (0, node_js_1.createMessageConnection)(new node_js_1.StreamMessageReader(this.process.stdout), new node_js_1.StreamMessageWriter(this.process.stdin));
45
+ this.connection = createMessageConnection(new StreamMessageReader(this.process.stdout), new StreamMessageWriter(this.process.stdin));
82
46
  // Handle diagnostics
83
47
  this.connection.onNotification('textDocument/publishDiagnostics', (params) => {
84
48
  this.handleDiagnostics(params);
@@ -146,6 +110,91 @@ class ALLanguageServer {
146
110
  publishDiagnostics: {
147
111
  relatedInformation: true,
148
112
  },
113
+ codeAction: {
114
+ dynamicRegistration: true,
115
+ codeActionLiteralSupport: {
116
+ codeActionKind: {
117
+ valueSet: [
118
+ 'quickfix',
119
+ 'refactor',
120
+ 'refactor.extract',
121
+ 'refactor.inline',
122
+ 'refactor.rewrite',
123
+ 'source',
124
+ 'source.organizeImports',
125
+ ],
126
+ },
127
+ },
128
+ resolveSupport: {
129
+ properties: ['edit'],
130
+ },
131
+ },
132
+ signatureHelp: {
133
+ dynamicRegistration: true,
134
+ signatureInformation: {
135
+ documentationFormat: ['markdown', 'plaintext'],
136
+ parameterInformation: {
137
+ labelOffsetSupport: true,
138
+ },
139
+ },
140
+ contextSupport: true,
141
+ },
142
+ formatting: {
143
+ dynamicRegistration: true,
144
+ },
145
+ rangeFormatting: {
146
+ dynamicRegistration: true,
147
+ },
148
+ onTypeFormatting: {
149
+ dynamicRegistration: true,
150
+ },
151
+ documentHighlight: {
152
+ dynamicRegistration: true,
153
+ },
154
+ foldingRange: {
155
+ dynamicRegistration: true,
156
+ foldingRangeKind: {
157
+ valueSet: ['comment', 'imports', 'region'],
158
+ },
159
+ },
160
+ selectionRange: {
161
+ dynamicRegistration: true,
162
+ },
163
+ codeLens: {
164
+ dynamicRegistration: true,
165
+ },
166
+ documentLink: {
167
+ dynamicRegistration: true,
168
+ tooltipSupport: true,
169
+ },
170
+ typeDefinition: {
171
+ dynamicRegistration: true,
172
+ linkSupport: true,
173
+ },
174
+ implementation: {
175
+ dynamicRegistration: true,
176
+ linkSupport: true,
177
+ },
178
+ semanticTokens: {
179
+ dynamicRegistration: true,
180
+ tokenTypes: [
181
+ 'namespace', 'type', 'class', 'enum', 'interface', 'struct',
182
+ 'typeParameter', 'parameter', 'variable', 'property', 'enumMember',
183
+ 'event', 'function', 'method', 'macro', 'keyword', 'modifier',
184
+ 'comment', 'string', 'number', 'regexp', 'operator', 'decorator',
185
+ ],
186
+ tokenModifiers: [
187
+ 'declaration', 'definition', 'readonly', 'static', 'deprecated',
188
+ 'abstract', 'async', 'modification', 'documentation', 'defaultLibrary',
189
+ ],
190
+ formats: ['relative'],
191
+ requests: {
192
+ full: true,
193
+ range: true,
194
+ },
195
+ multilineTokenSupport: false,
196
+ overlappingTokenSupport: false,
197
+ },
149
198
  },
150
199
  workspace: {
151
200
  applyEdit: true,
@@ -313,6 +362,269 @@ class ALLanguageServer {
313
362
  const result = await this.connection.sendRequest('workspace/symbol', { query });
314
363
  return result || [];
315
364
  }
365
+ /**
366
+ * Update document content (for editing)
367
+ */
368
+ async updateDocument(uri, content, version) {
369
+ if (!this.connection) {
370
+ throw new Error('Language server not initialized');
371
+ }
372
+ // If document is not open, open it first
373
+ if (!this.openDocuments.has(uri)) {
374
+ await this.openDocument(uri);
375
+ }
376
+ const params = {
377
+ textDocument: { uri, version },
378
+ contentChanges: [{ text: content }],
379
+ };
380
+ void this.connection.sendNotification('textDocument/didChange', params);
381
+ // Wait for diagnostics to be processed
382
+ await new Promise(resolve => setTimeout(resolve, 100));
383
+ }
384
+ /**
385
+ * Rename symbol across the workspace
386
+ * @returns WorkspaceEdit with all changes needed
387
+ */
388
+ async renameSymbol(uri, line, character, newName) {
389
+ await this.ensureInitialized();
390
+ await this.openDocument(uri);
391
+ // First, check if rename is valid
392
+ const prepareParams = {
393
+ textDocument: { uri },
394
+ position: { line, character },
395
+ };
396
+ try {
397
+ const prepareResult = await this.connection.sendRequest('textDocument/prepareRename', prepareParams);
398
+ if (!prepareResult) {
399
+ this.logger.debug('Rename not available at this position');
400
+ return null;
401
+ }
402
+ // Perform the rename
403
+ const renameParams = {
404
+ textDocument: { uri },
405
+ position: { line, character },
406
+ newName,
407
+ };
408
+ const result = await this.connection.sendRequest('textDocument/rename', renameParams);
409
+ return result;
410
+ }
411
+ catch (error) {
412
+ this.logger.error('Rename failed:', error);
413
+ return null;
414
+ }
415
+ }
416
+ /**
417
+ * Get the symbol at a specific position
418
+ */
419
+ async getSymbolAtPosition(uri, line, character) {
420
+ const symbols = await this.getDocumentSymbols(uri);
421
+ return this.findSymbolAtPosition(symbols, line, character);
422
+ }
423
+ /**
424
+ * Get code actions (quick fixes, refactorings) at a specific position or range
425
+ */
426
+ async getCodeActions(uri, range, context) {
427
+ await this.ensureInitialized();
428
+ await this.openDocument(uri);
429
+ // Build diagnostics with proper typing
430
+ const diagnostics = context?.diagnostics?.map(d => ({
431
+ message: d.message,
432
+ severity: this.severityToNumber(d.severity),
433
+ range: d.range,
434
+ code: d.code,
435
+ source: d.source,
436
+ })) || [];
437
+ const params = {
438
+ textDocument: { uri },
439
+ range,
440
+ context: {
441
+ diagnostics,
442
+ only: context?.only,
443
+ },
444
+ };
445
+ const result = await this.connection.sendRequest('textDocument/codeAction', params);
446
+ if (!result)
447
+ return [];
448
+ return result.map(item => {
449
+ // Check if it's a CodeAction (has 'title' as required field for both, but CodeAction has more fields)
450
+ const isCodeAction = 'kind' in item || 'edit' in item || ('command' in item && typeof item.command === 'object');
451
+ if (isCodeAction) {
452
+ const action = item;
453
+ let editResult;
454
+ if (action.edit && action.edit.changes) {
455
+ editResult = {
456
+ changes: action.edit.changes,
457
+ };
458
+ }
459
+ return {
460
+ title: action.title,
461
+ kind: action.kind,
462
+ isPreferred: action.isPreferred,
463
+ edit: editResult,
464
+ command: action.command ? {
465
+ title: action.command.title,
466
+ command: action.command.command,
467
+ arguments: action.command.arguments,
468
+ } : undefined,
469
+ };
470
+ }
471
+ else {
472
+ // It's a Command (simpler structure: title, command string, arguments)
473
+ const cmd = item;
474
+ return {
475
+ title: cmd.title,
476
+ command: {
477
+ title: cmd.title,
478
+ command: cmd.command,
479
+ arguments: cmd.arguments,
480
+ },
481
+ };
482
+ }
483
+ });
484
+ }
485
+ /**
486
+ * Get signature help (function parameter hints) at a position
487
+ */
488
+ async getSignatureHelp(uri, line, character, context) {
489
+ await this.ensureInitialized();
490
+ await this.openDocument(uri);
491
+ const params = {
492
+ textDocument: { uri },
493
+ position: { line, character },
494
+ context: context ? {
495
+ triggerKind: context.triggerKind || 1,
496
+ triggerCharacter: context.triggerCharacter,
497
+ isRetrigger: context.isRetrigger || false,
498
+ } : undefined,
499
+ };
500
+ const result = await this.connection.sendRequest('textDocument/signatureHelp', params);
501
+ if (!result)
502
+ return null;
503
+ return {
504
+ signatures: result.signatures.map(sig => ({
505
+ label: sig.label,
506
+ documentation: typeof sig.documentation === 'string'
507
+ ? sig.documentation
508
+ : sig.documentation?.value,
509
+ parameters: sig.parameters?.map(p => ({
510
+ label: p.label,
511
+ documentation: typeof p.documentation === 'string'
512
+ ? p.documentation
513
+ : p.documentation?.value,
514
+ })),
515
+ })),
516
+ activeSignature: result.activeSignature,
517
+ activeParameter: result.activeParameter,
518
+ };
519
+ }
520
+ /**
521
+ * Format an entire document
522
+ */
523
+ async formatDocument(uri, options) {
524
+ await this.ensureInitialized();
525
+ await this.openDocument(uri);
526
+ const formattingOptions = {
527
+ tabSize: options?.tabSize ?? 4,
528
+ insertSpaces: options?.insertSpaces ?? true,
529
+ };
530
+ const params = {
531
+ textDocument: { uri },
532
+ options: formattingOptions,
533
+ };
534
+ const result = await this.connection.sendRequest('textDocument/formatting', params);
535
+ if (!result)
536
+ return [];
537
+ return result.map(edit => ({
538
+ range: {
539
+ start: { line: edit.range.start.line, character: edit.range.start.character },
540
+ end: { line: edit.range.end.line, character: edit.range.end.character },
541
+ },
542
+ newText: edit.newText,
543
+ }));
544
+ }
545
+ /**
546
+ * Format a range within a document
547
+ */
548
+ async formatRange(uri, range, options) {
549
+ await this.ensureInitialized();
550
+ await this.openDocument(uri);
551
+ const formattingOptions = {
552
+ tabSize: options?.tabSize ?? 4,
553
+ insertSpaces: options?.insertSpaces ?? true,
554
+ };
555
+ const params = {
556
+ textDocument: { uri },
557
+ range,
558
+ options: formattingOptions,
559
+ };
560
+ const result = await this.connection.sendRequest('textDocument/rangeFormatting', params);
561
+ if (!result)
562
+ return [];
563
+ return result.map(edit => ({
564
+ range: {
565
+ start: { line: edit.range.start.line, character: edit.range.start.character },
566
+ end: { line: edit.range.end.line, character: edit.range.end.character },
567
+ },
568
+ newText: edit.newText,
569
+ }));
570
+ }
571
+ /**
572
+ * Convert severity string to LSP severity number
573
+ */
574
+ severityToNumber(severity) {
575
+ switch (severity) {
576
+ case 'error': return 1;
577
+ case 'warning': return 2;
578
+ case 'info': return 3;
579
+ case 'hint': return 4;
580
+ default: return 3;
581
+ }
582
+ }
583
+ /**
584
+ * Find symbol containing the given position
585
+ */
586
+ findSymbolAtPosition(symbols, line, character) {
587
+ for (const symbol of symbols) {
588
+ const { start, end } = symbol.range;
589
+ // Check if position is within this symbol's range
590
+ const isAfterStart = line > start.line || (line === start.line && character >= start.character);
591
+ const isBeforeEnd = line < end.line || (line === end.line && character <= end.character);
592
+ if (isAfterStart && isBeforeEnd) {
593
+ // Check children first (more specific match)
594
+ if (symbol.children) {
595
+ const childMatch = this.findSymbolAtPosition(symbol.children, line, character);
596
+ if (childMatch) {
597
+ return childMatch;
598
+ }
599
+ }
600
+ return symbol;
601
+ }
602
+ }
603
+ return null;
604
+ }
605
+ /**
606
+ * Find a symbol by name in the document
607
+ */
608
+ async findSymbolByName(uri, symbolName) {
609
+ const symbols = await this.getDocumentSymbols(uri);
610
+ return this.searchSymbolByName(symbols, symbolName);
611
+ }
612
+ /**
613
+ * Search for symbol by name recursively
614
+ */
615
+ searchSymbolByName(symbols, name) {
616
+ for (const symbol of symbols) {
617
+ if (symbol.name.toLowerCase() === name.toLowerCase()) {
618
+ return symbol;
619
+ }
620
+ if (symbol.children) {
621
+ const found = this.searchSymbolByName(symbol.children, name);
622
+ if (found)
623
+ return found;
624
+ }
625
+ }
626
+ return null;
627
+ }
316
628
  /**
317
629
  * Convert LSP symbols to our format
318
630
  */
@@ -403,6 +715,333 @@ class ALLanguageServer {
403
715
  await this.initialize();
404
716
  }
405
717
  }
718
+ // ==================== Remaining LSP Features ====================
719
+ /**
720
+ * Close a document (cleanup)
721
+ */
722
+ async closeDocument(uri) {
723
+ await this.ensureInitialized();
724
+ if (!this.openDocuments.has(uri)) {
725
+ return; // Not open
726
+ }
727
+ const params = {
728
+ textDocument: { uri },
729
+ };
730
+ await this.connection.sendNotification('textDocument/didClose', params);
731
+ this.openDocuments.delete(uri);
732
+ this.diagnosticsCache.delete(uri);
733
+ }
734
+ /**
735
+ * Notify save (triggers recompile in some LSPs)
736
+ */
737
+ async saveDocument(uri, text) {
738
+ await this.ensureInitialized();
739
+ await this.openDocument(uri);
740
+ const params = {
741
+ textDocument: { uri },
742
+ text,
743
+ };
744
+ await this.connection.sendNotification('textDocument/didSave', params);
745
+ }
746
+ /**
747
+ * Get document highlights (all occurrences of symbol under cursor)
748
+ */
749
+ async getDocumentHighlights(uri, line, character) {
750
+ await this.ensureInitialized();
751
+ await this.openDocument(uri);
752
+ const params = {
753
+ textDocument: { uri },
754
+ position: { line, character },
755
+ };
756
+ const result = await this.connection.sendRequest('textDocument/documentHighlight', params);
757
+ if (!result)
758
+ return [];
759
+ return result.map(h => ({
760
+ range: {
761
+ start: { line: h.range.start.line, character: h.range.start.character },
762
+ end: { line: h.range.end.line, character: h.range.end.character },
763
+ },
764
+ kind: h.kind === 1 ? 'text' : h.kind === 2 ? 'read' : h.kind === 3 ? 'write' : undefined,
765
+ }));
766
+ }
767
+ /**
768
+ * Get folding ranges (code regions, procedures, etc.)
769
+ */
770
+ async getFoldingRanges(uri) {
771
+ await this.ensureInitialized();
772
+ await this.openDocument(uri);
773
+ const params = {
774
+ textDocument: { uri },
775
+ };
776
+ const result = await this.connection.sendRequest('textDocument/foldingRange', params);
777
+ if (!result)
778
+ return [];
779
+ return result.map(r => ({
780
+ startLine: r.startLine,
781
+ startCharacter: r.startCharacter,
782
+ endLine: r.endLine,
783
+ endCharacter: r.endCharacter,
784
+ kind: r.kind,
785
+ }));
786
+ }
787
+ /**
788
+ * Get selection ranges (smart expand/shrink selection)
789
+ */
790
+ async getSelectionRanges(uri, positions) {
791
+ await this.ensureInitialized();
792
+ await this.openDocument(uri);
793
+ const params = {
794
+ textDocument: { uri },
795
+ positions,
796
+ };
797
+ const result = await this.connection.sendRequest('textDocument/selectionRange', params);
798
+ if (!result)
799
+ return [];
800
+ const convertSelectionRange = (sr) => ({
801
+ range: {
802
+ start: { line: sr.range.start.line, character: sr.range.start.character },
803
+ end: { line: sr.range.end.line, character: sr.range.end.character },
804
+ },
805
+ parent: sr.parent ? convertSelectionRange(sr.parent) : undefined,
806
+ });
807
+ return result.map(convertSelectionRange);
808
+ }
809
+ /**
810
+ * Go to type definition (variable's type)
811
+ */
812
+ async getTypeDefinition(uri, line, character) {
813
+ await this.ensureInitialized();
814
+ await this.openDocument(uri);
815
+ const params = {
816
+ textDocument: { uri },
817
+ position: { line, character },
818
+ };
819
+ const result = await this.connection.sendRequest('textDocument/typeDefinition', params);
820
+ if (!result)
821
+ return [];
822
+ return Array.isArray(result) ? result : [result];
823
+ }
824
+ /**
825
+ * Go to implementation (interface implementations)
826
+ */
827
+ async getImplementation(uri, line, character) {
828
+ await this.ensureInitialized();
829
+ await this.openDocument(uri);
830
+ const params = {
831
+ textDocument: { uri },
832
+ position: { line, character },
833
+ };
834
+ const result = await this.connection.sendRequest('textDocument/implementation', params);
835
+ if (!result)
836
+ return [];
837
+ return Array.isArray(result) ? result : [result];
838
+ }
839
+ /**
840
+ * Format on type (format after specific character like ';')
841
+ */
842
+ async formatOnType(uri, line, character, ch, options) {
843
+ await this.ensureInitialized();
844
+ await this.openDocument(uri);
845
+ const params = {
846
+ textDocument: { uri },
847
+ position: { line, character },
848
+ ch,
849
+ options: {
850
+ tabSize: options?.tabSize ?? 4,
851
+ insertSpaces: options?.insertSpaces ?? true,
852
+ },
853
+ };
854
+ const result = await this.connection.sendRequest('textDocument/onTypeFormatting', params);
855
+ if (!result)
856
+ return [];
857
+ return result.map(edit => ({
858
+ range: {
859
+ start: { line: edit.range.start.line, character: edit.range.start.character },
860
+ end: { line: edit.range.end.line, character: edit.range.end.character },
861
+ },
862
+ newText: edit.newText,
863
+ }));
864
+ }
865
+ /**
866
+ * Get code lenses (inline hints like reference counts)
867
+ */
868
+ async getCodeLenses(uri) {
869
+ await this.ensureInitialized();
870
+ await this.openDocument(uri);
871
+ const params = {
872
+ textDocument: { uri },
873
+ };
874
+ const result = await this.connection.sendRequest('textDocument/codeLens', params);
875
+ if (!result)
876
+ return [];
877
+ return result.map(lens => ({
878
+ range: {
879
+ start: { line: lens.range.start.line, character: lens.range.start.character },
880
+ end: { line: lens.range.end.line, character: lens.range.end.character },
881
+ },
882
+ command: lens.command ? {
883
+ title: lens.command.title,
884
+ command: lens.command.command,
885
+ arguments: lens.command.arguments,
886
+ } : undefined,
887
+ data: lens.data,
888
+ }));
889
+ }
890
+ /**
891
+ * Resolve a code lens (get the command for a lens)
892
+ */
893
+ async resolveCodeLens(lens) {
894
+ await this.ensureInitialized();
895
+ const lspLens = {
896
+ range: {
897
+ start: { line: lens.range.start.line, character: lens.range.start.character },
898
+ end: { line: lens.range.end.line, character: lens.range.end.character },
899
+ },
900
+ data: lens.data,
901
+ };
902
+ const result = await this.connection.sendRequest('codeLens/resolve', lspLens);
903
+ return {
904
+ range: {
905
+ start: { line: result.range.start.line, character: result.range.start.character },
906
+ end: { line: result.range.end.line, character: result.range.end.character },
907
+ },
908
+ command: result.command ? {
909
+ title: result.command.title,
910
+ command: result.command.command,
911
+ arguments: result.command.arguments,
912
+ } : undefined,
913
+ data: result.data,
914
+ };
915
+ }
916
+ /**
917
+ * Get document links (clickable URLs in comments)
918
+ */
919
+ async getDocumentLinks(uri) {
920
+ await this.ensureInitialized();
921
+ await this.openDocument(uri);
922
+ const params = {
923
+ textDocument: { uri },
924
+ };
925
+ const result = await this.connection.sendRequest('textDocument/documentLink', params);
926
+ if (!result)
927
+ return [];
928
+ return result.map(link => ({
929
+ range: {
930
+ start: { line: link.range.start.line, character: link.range.start.character },
931
+ end: { line: link.range.end.line, character: link.range.end.character },
932
+ },
933
+ target: link.target,
934
+ tooltip: link.tooltip,
935
+ }));
936
+ }
937
+ /**
938
+ * Execute a command (e.g., from code action)
939
+ */
940
+ async executeCommand(command, args) {
941
+ await this.ensureInitialized();
942
+ const params = {
943
+ command,
944
+ arguments: args,
945
+ };
946
+ return this.connection.sendRequest('workspace/executeCommand', params);
947
+ }
948
+ /**
949
+ * Get semantic tokens (for syntax highlighting)
950
+ */
951
+ async getSemanticTokens(uri) {
952
+ await this.ensureInitialized();
953
+ await this.openDocument(uri);
954
+ const params = {
955
+ textDocument: { uri },
956
+ };
957
+ const result = await this.connection.sendRequest('textDocument/semanticTokens/full', params);
958
+ if (!result)
959
+ return null;
960
+ return {
961
+ resultId: result.resultId,
962
+ data: result.data,
963
+ };
964
+ }
965
+ /**
966
+ * Get semantic tokens for a range
967
+ */
968
+ async getSemanticTokensRange(uri, range) {
969
+ await this.ensureInitialized();
970
+ await this.openDocument(uri);
971
+ const params = {
972
+ textDocument: { uri },
973
+ range,
974
+ };
975
+ const result = await this.connection.sendRequest('textDocument/semanticTokens/range', params);
976
+ if (!result)
977
+ return null;
978
+ return {
979
+ resultId: result.resultId,
980
+ data: result.data,
981
+ };
982
+ }
983
+ /**
984
+ * Restart the language server (useful when it hangs or after external changes)
985
+ */
986
+ async restart() {
987
+ this.logger.info('Restarting AL Language Server...');
988
+ await this.shutdown();
989
+ await this.initialize();
990
+ this.logger.info('AL Language Server restarted successfully');
991
+ }
992
+ /**
993
+ * Find all symbols that reference a given symbol (enhanced reference navigation)
994
+ * Returns referencing symbols with context around the reference
995
+ */
996
+ async findReferencingSymbols(uri, line, character, options) {
997
+ await this.ensureInitialized();
998
+ await this.openDocument(uri);
999
+ const contextBefore = options?.contextLinesBefore ?? 1;
1000
+ const contextAfter = options?.contextLinesAfter ?? 1;
1001
+ // Get all references
1002
+ const params = {
1003
+ textDocument: { uri },
1004
+ position: { line, character },
1005
+ context: { includeDeclaration: options?.includeDeclaration ?? false },
1006
+ };
1007
+ const locations = await this.connection.sendRequest('textDocument/references', params);
1008
+ if (!locations || locations.length === 0) {
1009
+ return [];
1010
+ }
1011
+ const results = [];
1012
+ for (const loc of locations) {
1013
+ const result = {
1014
+ location: loc,
1015
+ };
1016
+ // Try to get the containing symbol
1017
+ try {
1018
+ const symbols = await this.getDocumentSymbols(loc.uri);
1019
+ const containingSymbol = this.findSymbolAtPosition(symbols, loc.range.start.line, loc.range.start.character);
1020
+ if (containingSymbol) {
1021
+ result.containingSymbol = containingSymbol;
1022
+ }
1023
+ }
1024
+ catch {
1025
+ // Ignore errors getting symbols
1026
+ }
1027
+ // Try to get context snippet
1028
+ try {
1029
+ const filePath = this.uriToPath(loc.uri);
1030
+ if (fs.existsSync(filePath)) {
1031
+ const content = fs.readFileSync(filePath, 'utf-8');
1032
+ const lines = content.split('\n');
1033
+ const startLine = Math.max(0, loc.range.start.line - contextBefore);
1034
+ const endLine = Math.min(lines.length - 1, loc.range.start.line + contextAfter);
1035
+ result.contextSnippet = lines.slice(startLine, endLine + 1).join('\n');
1036
+ }
1037
+ }
1038
+ catch {
1039
+ // Ignore errors reading file
1040
+ }
1041
+ results.push(result);
1042
+ }
1043
+ return results;
1044
+ }
406
1045
  /**
407
1046
  * Shutdown the language server
408
1047
  */
@@ -427,5 +1066,4 @@ class ALLanguageServer {
427
1066
  this.diagnosticsCache.clear();
428
1067
  }
429
1068
  }
430
- exports.ALLanguageServer = ALLanguageServer;
431
1069
  //# sourceMappingURL=language-server.js.map