@lewin671/lsp-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/lib/common/Feature.d.ts +192 -0
  4. package/lib/common/Feature.js +159 -0
  5. package/lib/common/Feature.js.map +1 -0
  6. package/lib/common/LanguageClient.d.ts +230 -0
  7. package/lib/common/LanguageClient.js +512 -0
  8. package/lib/common/LanguageClient.js.map +1 -0
  9. package/lib/common/features/CompletionFeature.d.ts +23 -0
  10. package/lib/common/features/CompletionFeature.js +101 -0
  11. package/lib/common/features/CompletionFeature.js.map +1 -0
  12. package/lib/common/features/DefinitionFeature.d.ts +23 -0
  13. package/lib/common/features/DefinitionFeature.js +49 -0
  14. package/lib/common/features/DefinitionFeature.js.map +1 -0
  15. package/lib/common/features/DiagnosticFeature.d.ts +15 -0
  16. package/lib/common/features/DiagnosticFeature.js +39 -0
  17. package/lib/common/features/DiagnosticFeature.js.map +1 -0
  18. package/lib/common/features/DocumentSymbolFeature.d.ts +23 -0
  19. package/lib/common/features/DocumentSymbolFeature.js +87 -0
  20. package/lib/common/features/DocumentSymbolFeature.js.map +1 -0
  21. package/lib/common/features/FormattingFeature.d.ts +32 -0
  22. package/lib/common/features/FormattingFeature.js +72 -0
  23. package/lib/common/features/FormattingFeature.js.map +1 -0
  24. package/lib/common/features/HoverFeature.d.ts +23 -0
  25. package/lib/common/features/HoverFeature.js +49 -0
  26. package/lib/common/features/HoverFeature.js.map +1 -0
  27. package/lib/common/features/ReferencesFeature.d.ts +23 -0
  28. package/lib/common/features/ReferencesFeature.js +48 -0
  29. package/lib/common/features/ReferencesFeature.js.map +1 -0
  30. package/lib/common/features/RenameFeature.d.ts +37 -0
  31. package/lib/common/features/RenameFeature.js +72 -0
  32. package/lib/common/features/RenameFeature.js.map +1 -0
  33. package/lib/common/features/index.d.ts +8 -0
  34. package/lib/common/features/index.js +21 -0
  35. package/lib/common/features/index.js.map +1 -0
  36. package/lib/common/utils/index.d.ts +2 -0
  37. package/lib/common/utils/index.js +19 -0
  38. package/lib/common/utils/index.js.map +1 -0
  39. package/lib/common/utils/is.d.ts +15 -0
  40. package/lib/common/utils/is.js +56 -0
  41. package/lib/common/utils/is.js.map +1 -0
  42. package/lib/common/utils/uuid.d.ts +17 -0
  43. package/lib/common/utils/uuid.js +87 -0
  44. package/lib/common/utils/uuid.js.map +1 -0
  45. package/lib/demo/custom-lsp/client.d.ts +1 -0
  46. package/lib/demo/custom-lsp/client.js +86 -0
  47. package/lib/demo/custom-lsp/client.js.map +1 -0
  48. package/lib/demo/custom-lsp/server.d.ts +1 -0
  49. package/lib/demo/custom-lsp/server.js +64 -0
  50. package/lib/demo/custom-lsp/server.js.map +1 -0
  51. package/lib/demo/go-demo/client.d.ts +14 -0
  52. package/lib/demo/go-demo/client.js +697 -0
  53. package/lib/demo/go-demo/client.js.map +1 -0
  54. package/lib/demo/typescript-demo/client.d.ts +14 -0
  55. package/lib/demo/typescript-demo/client.js +680 -0
  56. package/lib/demo/typescript-demo/client.js.map +1 -0
  57. package/lib/demo/typescript-demo/sample-project/src/index.d.ts +9 -0
  58. package/lib/demo/typescript-demo/sample-project/src/index.js +65 -0
  59. package/lib/demo/typescript-demo/sample-project/src/index.js.map +1 -0
  60. package/lib/demo/typescript-demo/sample-project/src/math.d.ts +38 -0
  61. package/lib/demo/typescript-demo/sample-project/src/math.js +63 -0
  62. package/lib/demo/typescript-demo/sample-project/src/math.js.map +1 -0
  63. package/lib/demo/typescript-demo/sample-project/src/models/person.d.ts +41 -0
  64. package/lib/demo/typescript-demo/sample-project/src/models/person.js +53 -0
  65. package/lib/demo/typescript-demo/sample-project/src/models/person.js.map +1 -0
  66. package/lib/demo/typescript-demo/sample-project/src/services/calculator.d.ts +39 -0
  67. package/lib/demo/typescript-demo/sample-project/src/services/calculator.js +69 -0
  68. package/lib/demo/typescript-demo/sample-project/src/services/calculator.js.map +1 -0
  69. package/lib/demo/typescript-demo/sample-project/src/services/user-service.d.ts +40 -0
  70. package/lib/demo/typescript-demo/sample-project/src/services/user-service.js +88 -0
  71. package/lib/demo/typescript-demo/sample-project/src/services/user-service.js.map +1 -0
  72. package/lib/index.d.ts +8 -0
  73. package/lib/index.js +32 -0
  74. package/lib/index.js.map +1 -0
  75. package/lib/interfaces/IHost.d.ts +19 -0
  76. package/lib/interfaces/IHost.js +3 -0
  77. package/lib/interfaces/IHost.js.map +1 -0
  78. package/lib/transports/ITransport.d.ts +8 -0
  79. package/lib/transports/ITransport.js +3 -0
  80. package/lib/transports/ITransport.js.map +1 -0
  81. package/lib/transports/StdioTransport.d.ts +13 -0
  82. package/lib/transports/StdioTransport.js +27 -0
  83. package/lib/transports/StdioTransport.js.map +1 -0
  84. package/package.json +48 -0
@@ -0,0 +1,680 @@
1
+ "use strict";
2
+ /**
3
+ * TypeScript LSP Demo Client
4
+ *
5
+ * This demo shows how to use the lsp-client library to interact with
6
+ * TypeScript Language Server (tsserver) and test various LSP features:
7
+ * - Hover
8
+ * - Completion
9
+ * - Go to Definition
10
+ * - Find References
11
+ * - Document Symbols
12
+ * - Rename
13
+ * - Diagnostics
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ const path = require("path");
17
+ const fs = require("fs");
18
+ const LanguageClient_1 = require("../../common/LanguageClient");
19
+ const CompletionFeature_1 = require("../../common/features/CompletionFeature");
20
+ const HoverFeature_1 = require("../../common/features/HoverFeature");
21
+ const DefinitionFeature_1 = require("../../common/features/DefinitionFeature");
22
+ const ReferencesFeature_1 = require("../../common/features/ReferencesFeature");
23
+ const DocumentSymbolFeature_1 = require("../../common/features/DocumentSymbolFeature");
24
+ const RenameFeature_1 = require("../../common/features/RenameFeature");
25
+ const DiagnosticFeature_1 = require("../../common/features/DiagnosticFeature");
26
+ const StdioTransport_1 = require("../../transports/StdioTransport");
27
+ const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
28
+ // Color utilities for console output
29
+ const colors = {
30
+ reset: '\x1b[0m',
31
+ bright: '\x1b[1m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ blue: '\x1b[34m',
35
+ magenta: '\x1b[35m',
36
+ cyan: '\x1b[36m',
37
+ red: '\x1b[31m'
38
+ };
39
+ function log(color, prefix, message) {
40
+ console.log(`${color}[${prefix}]${colors.reset} ${message}`);
41
+ }
42
+ function logSection(title) {
43
+ console.log(`\n${colors.bright}${colors.cyan}${'='.repeat(60)}${colors.reset}`);
44
+ console.log(`${colors.bright}${colors.cyan} ${title}${colors.reset}`);
45
+ console.log(`${colors.bright}${colors.cyan}${'='.repeat(60)}${colors.reset}\n`);
46
+ }
47
+ function logSuccess(message) {
48
+ log(colors.green, '✓', message);
49
+ }
50
+ function logInfo(message) {
51
+ log(colors.blue, 'i', message);
52
+ }
53
+ function logWarning(message) {
54
+ log(colors.yellow, '!', message);
55
+ }
56
+ function logError(message) {
57
+ log(colors.red, '✗', message);
58
+ }
59
+ // Sample project paths - resolve from source directory, not lib
60
+ // When running from lib, we need to point to src/demo/typescript-demo/sample-project
61
+ const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
62
+ const SAMPLE_PROJECT_DIR = path.join(PROJECT_ROOT, 'src', 'demo', 'typescript-demo', 'sample-project');
63
+ const SAMPLE_FILES = {
64
+ math: path.join(SAMPLE_PROJECT_DIR, 'src', 'math.ts'),
65
+ person: path.join(SAMPLE_PROJECT_DIR, 'src', 'models', 'person.ts'),
66
+ calculator: path.join(SAMPLE_PROJECT_DIR, 'src', 'services', 'calculator.ts'),
67
+ userService: path.join(SAMPLE_PROJECT_DIR, 'src', 'services', 'user-service.ts'),
68
+ index: path.join(SAMPLE_PROJECT_DIR, 'src', 'index.ts')
69
+ };
70
+ /**
71
+ * Console-based Window implementation
72
+ */
73
+ class ConsoleWindow {
74
+ constructor() {
75
+ this.diagnosticsMap = new Map();
76
+ }
77
+ showMessage(type, message) {
78
+ const typeStr = type === vscode_languageserver_protocol_1.MessageType.Error ? 'ERROR' :
79
+ type === vscode_languageserver_protocol_1.MessageType.Warning ? 'WARNING' : 'INFO';
80
+ log(colors.magenta, `Window.${typeStr}`, message);
81
+ }
82
+ async showMessageRequest(type, message, actions) {
83
+ log(colors.magenta, 'Window.Request', message);
84
+ if (actions && actions.length > 0) {
85
+ logInfo(`Available actions: ${actions.map(a => a.title).join(', ')}`);
86
+ }
87
+ return undefined;
88
+ }
89
+ logMessage(type, message) {
90
+ log(colors.magenta, 'Window.Log', message);
91
+ }
92
+ publishDiagnostics(uri, diagnostics) {
93
+ this.diagnosticsMap.set(uri, diagnostics);
94
+ if (diagnostics.length > 0) {
95
+ logWarning(`Diagnostics for ${path.basename(uri)}: ${diagnostics.length} issue(s)`);
96
+ diagnostics.forEach(d => {
97
+ const severity = d.severity === 1 ? 'Error' : d.severity === 2 ? 'Warning' : 'Info';
98
+ console.log(` - [${severity}] Line ${d.range.start.line + 1}: ${d.message}`);
99
+ });
100
+ }
101
+ }
102
+ getDiagnostics(uri) {
103
+ return this.diagnosticsMap.get(uri) || [];
104
+ }
105
+ }
106
+ /**
107
+ * Workspace implementation pointing to sample project
108
+ */
109
+ class SampleWorkspace {
110
+ constructor() {
111
+ this.rootUri = `file://${SAMPLE_PROJECT_DIR}`;
112
+ }
113
+ }
114
+ /**
115
+ * Simple configuration
116
+ */
117
+ class SimpleConfiguration {
118
+ get(section) {
119
+ // TypeScript specific settings
120
+ if (section === 'typescript') {
121
+ return {
122
+ suggest: {
123
+ autoImports: true,
124
+ completeFunctionCalls: true
125
+ },
126
+ format: {
127
+ enable: true
128
+ }
129
+ };
130
+ }
131
+ return {};
132
+ }
133
+ }
134
+ /**
135
+ * Host implementation
136
+ */
137
+ class TypeScriptHost {
138
+ constructor() {
139
+ this.window = new ConsoleWindow();
140
+ this.workspace = new SampleWorkspace();
141
+ this.configuration = new SimpleConfiguration();
142
+ }
143
+ dispose() { }
144
+ }
145
+ /**
146
+ * Read file content
147
+ */
148
+ function readFileContent(filePath) {
149
+ return fs.readFileSync(filePath, 'utf-8');
150
+ }
151
+ /**
152
+ * Convert file path to URI
153
+ */
154
+ function toUri(filePath) {
155
+ return `file://${filePath}`;
156
+ }
157
+ /**
158
+ * Find line and character for a search string in file content
159
+ */
160
+ function findPosition(content, searchStr) {
161
+ const index = content.indexOf(searchStr);
162
+ if (index === -1)
163
+ return null;
164
+ const lines = content.substring(0, index).split('\n');
165
+ return {
166
+ line: lines.length - 1,
167
+ character: lines[lines.length - 1].length
168
+ };
169
+ }
170
+ /**
171
+ * Main demo function
172
+ */
173
+ async function main() {
174
+ console.log(`${colors.bright}${colors.cyan}`);
175
+ console.log('╔════════════════════════════════════════════════════════════╗');
176
+ console.log('║ TypeScript LSP Client Demo ║');
177
+ console.log('║ Testing LSP features with real tsserver ║');
178
+ console.log('╚════════════════════════════════════════════════════════════╝');
179
+ console.log(colors.reset);
180
+ // Check if sample project exists
181
+ if (!fs.existsSync(SAMPLE_PROJECT_DIR)) {
182
+ logError(`Sample project not found at: ${SAMPLE_PROJECT_DIR}`);
183
+ logInfo('Please make sure the sample-project directory exists.');
184
+ process.exit(1);
185
+ }
186
+ // Find TypeScript Language Server
187
+ const tsserverPath = findTsServer();
188
+ if (!tsserverPath) {
189
+ logError('Could not find TypeScript Language Server (tsserver)');
190
+ logInfo('Please install TypeScript: npm install -g typescript');
191
+ process.exit(1);
192
+ }
193
+ logSuccess(`Found tsserver at: ${tsserverPath}`);
194
+ // Create transport using typescript-language-server or tsserver wrapper
195
+ // Using typescript-language-server as it provides proper LSP interface
196
+ let transport;
197
+ try {
198
+ // Try to use typescript-language-server (recommended for LSP)
199
+ transport = new StdioTransport_1.StdioTransport('npx', ['typescript-language-server', '--stdio']);
200
+ logInfo('Using typescript-language-server for LSP communication');
201
+ }
202
+ catch (e) {
203
+ logWarning('typescript-language-server not available, falling back to tsserver');
204
+ transport = new StdioTransport_1.StdioTransport(tsserverPath, ['--stdio']);
205
+ }
206
+ const host = new TypeScriptHost();
207
+ const client = new LanguageClient_1.LanguageClient(host, transport, {
208
+ textDocument: {
209
+ hover: { dynamicRegistration: true, contentFormat: ['markdown', 'plaintext'] },
210
+ completion: {
211
+ dynamicRegistration: true,
212
+ completionItem: {
213
+ snippetSupport: true,
214
+ documentationFormat: ['markdown', 'plaintext']
215
+ }
216
+ },
217
+ definition: { dynamicRegistration: true, linkSupport: true },
218
+ references: { dynamicRegistration: true },
219
+ documentSymbol: {
220
+ dynamicRegistration: true,
221
+ hierarchicalDocumentSymbolSupport: true
222
+ },
223
+ rename: { dynamicRegistration: true, prepareSupport: true },
224
+ publishDiagnostics: { relatedInformation: true }
225
+ },
226
+ workspace: {
227
+ workspaceFolders: true
228
+ }
229
+ });
230
+ // Register all features
231
+ const hoverFeature = new HoverFeature_1.HoverFeature(client);
232
+ const completionFeature = new CompletionFeature_1.CompletionFeature(client);
233
+ const definitionFeature = new DefinitionFeature_1.DefinitionFeature(client);
234
+ const referencesFeature = new ReferencesFeature_1.ReferencesFeature(client);
235
+ const documentSymbolFeature = new DocumentSymbolFeature_1.DocumentSymbolFeature(client);
236
+ const renameFeature = new RenameFeature_1.RenameFeature(client);
237
+ const diagnosticFeature = new DiagnosticFeature_1.DiagnosticFeature(client);
238
+ client.registerFeature(hoverFeature);
239
+ client.registerFeature(completionFeature);
240
+ client.registerFeature(definitionFeature);
241
+ client.registerFeature(referencesFeature);
242
+ client.registerFeature(documentSymbolFeature);
243
+ client.registerFeature(renameFeature);
244
+ client.registerFeature(diagnosticFeature);
245
+ try {
246
+ logSection('Starting Language Client');
247
+ await client.start();
248
+ logSuccess('Language client started successfully!');
249
+ // Give server time to initialize
250
+ await sleep(2000);
251
+ // Open documents
252
+ logSection('Opening Documents');
253
+ await openDocuments(client);
254
+ // Wait for server to process documents
255
+ await sleep(2000);
256
+ // Test each feature
257
+ await testHover(client, hoverFeature);
258
+ await testCompletion(client, completionFeature);
259
+ await testDefinition(client, definitionFeature);
260
+ await testReferences(client, referencesFeature);
261
+ await testDocumentSymbols(client, documentSymbolFeature);
262
+ await testRename(client, renameFeature);
263
+ // Final summary
264
+ logSection('Demo Complete');
265
+ logSuccess('All LSP feature tests completed!');
266
+ // Keep alive briefly then stop
267
+ await sleep(1000);
268
+ logInfo('Stopping language client...');
269
+ await client.stop();
270
+ logSuccess('Client stopped gracefully');
271
+ }
272
+ catch (e) {
273
+ logError(`Error: ${e instanceof Error ? e.message : String(e)}`);
274
+ console.error(e);
275
+ process.exit(1);
276
+ }
277
+ }
278
+ /**
279
+ * Find tsserver executable
280
+ */
281
+ function findTsServer() {
282
+ const possiblePaths = [
283
+ // Local node_modules
284
+ path.join(process.cwd(), 'node_modules', '.bin', 'tsserver'),
285
+ path.join(process.cwd(), 'node_modules', 'typescript', 'bin', 'tsserver'),
286
+ // Global installations (common locations)
287
+ '/usr/local/bin/tsserver',
288
+ '/usr/bin/tsserver',
289
+ ];
290
+ // Try using which/where command
291
+ try {
292
+ const { execSync } = require('child_process');
293
+ const result = execSync('which tsserver', { encoding: 'utf-8' }).trim();
294
+ if (result && fs.existsSync(result)) {
295
+ return result;
296
+ }
297
+ }
298
+ catch (e) {
299
+ // Ignore
300
+ }
301
+ for (const p of possiblePaths) {
302
+ if (fs.existsSync(p)) {
303
+ return p;
304
+ }
305
+ }
306
+ // Return npx fallback
307
+ return 'npx';
308
+ }
309
+ /**
310
+ * Sleep utility
311
+ */
312
+ function sleep(ms) {
313
+ return new Promise(resolve => setTimeout(resolve, ms));
314
+ }
315
+ /**
316
+ * Open sample documents
317
+ */
318
+ async function openDocuments(client) {
319
+ const files = [
320
+ { name: 'math.ts', path: SAMPLE_FILES.math },
321
+ { name: 'person.ts', path: SAMPLE_FILES.person },
322
+ { name: 'calculator.ts', path: SAMPLE_FILES.calculator },
323
+ { name: 'index.ts', path: SAMPLE_FILES.index }
324
+ ];
325
+ for (const file of files) {
326
+ if (fs.existsSync(file.path)) {
327
+ const content = readFileContent(file.path);
328
+ client.didOpen({
329
+ textDocument: {
330
+ uri: toUri(file.path),
331
+ languageId: 'typescript',
332
+ version: 1,
333
+ text: content
334
+ }
335
+ });
336
+ logSuccess(`Opened: ${file.name}`);
337
+ }
338
+ else {
339
+ logWarning(`File not found: ${file.path}`);
340
+ }
341
+ }
342
+ }
343
+ /**
344
+ * Test Hover feature
345
+ */
346
+ async function testHover(client, feature) {
347
+ logSection('Testing Hover Feature');
348
+ if (!feature.isSupported) {
349
+ logWarning('Hover not supported by server');
350
+ return;
351
+ }
352
+ const filePath = SAMPLE_FILES.math;
353
+ const content = readFileContent(filePath);
354
+ // Test hover on 'add' function
355
+ const addPos = findPosition(content, 'function add');
356
+ if (addPos) {
357
+ logInfo(`Testing hover at position: line ${addPos.line + 1}, char ${addPos.character + 9}`);
358
+ const hover = await feature.provideHover({
359
+ textDocument: { uri: toUri(filePath) },
360
+ position: { line: addPos.line, character: addPos.character + 9 } // position on 'add'
361
+ });
362
+ if (hover) {
363
+ logSuccess('Hover result received:');
364
+ printHoverContent(hover);
365
+ }
366
+ else {
367
+ logWarning('No hover information returned');
368
+ }
369
+ }
370
+ // Test hover on 'number' type
371
+ const numberPos = findPosition(content, 'a: number');
372
+ if (numberPos) {
373
+ logInfo(`Testing hover on type annotation`);
374
+ const hover = await feature.provideHover({
375
+ textDocument: { uri: toUri(filePath) },
376
+ position: { line: numberPos.line, character: numberPos.character + 3 }
377
+ });
378
+ if (hover) {
379
+ logSuccess('Hover on type:');
380
+ printHoverContent(hover);
381
+ }
382
+ }
383
+ }
384
+ /**
385
+ * Print hover content
386
+ */
387
+ function printHoverContent(hover) {
388
+ if (typeof hover.contents === 'string') {
389
+ console.log(` ${hover.contents}`);
390
+ }
391
+ else if (Array.isArray(hover.contents)) {
392
+ hover.contents.forEach(c => {
393
+ if (typeof c === 'string') {
394
+ console.log(` ${c}`);
395
+ }
396
+ else {
397
+ console.log(` [${c.language}] ${c.value}`);
398
+ }
399
+ });
400
+ }
401
+ else if ('kind' in hover.contents) {
402
+ console.log(` ${hover.contents.value.substring(0, 200)}...`);
403
+ }
404
+ else if ('value' in hover.contents) {
405
+ console.log(` [${hover.contents.language}] ${hover.contents.value}`);
406
+ }
407
+ }
408
+ /**
409
+ * Test Completion feature
410
+ */
411
+ async function testCompletion(client, feature) {
412
+ logSection('Testing Completion Feature');
413
+ if (!feature.isSupported) {
414
+ logWarning('Completion not supported by server');
415
+ return;
416
+ }
417
+ const filePath = SAMPLE_FILES.calculator;
418
+ const content = readFileContent(filePath);
419
+ // Find a good position for completion (after 'this.')
420
+ const thisPos = findPosition(content, 'this.history');
421
+ if (thisPos) {
422
+ logInfo(`Testing completion after 'this.'`);
423
+ const items = await feature.provideCompletion({
424
+ textDocument: { uri: toUri(filePath) },
425
+ position: { line: thisPos.line, character: thisPos.character + 5 } // after 'this.'
426
+ });
427
+ if (items) {
428
+ const completionItems = Array.isArray(items) ? items : items.items;
429
+ logSuccess(`Received ${completionItems.length} completion items:`);
430
+ completionItems.slice(0, 10).forEach((item) => {
431
+ console.log(` - ${item.label} (${getCompletionKindName(item.kind || 0)})`);
432
+ });
433
+ if (completionItems.length > 10) {
434
+ console.log(` ... and ${completionItems.length - 10} more`);
435
+ }
436
+ }
437
+ else {
438
+ logWarning('No completion items returned');
439
+ }
440
+ }
441
+ // Test completion for imports
442
+ const importPos = findPosition(content, "from '../math'");
443
+ if (importPos) {
444
+ logInfo(`Testing completion in import statement`);
445
+ const items = await feature.provideCompletion({
446
+ textDocument: { uri: toUri(filePath) },
447
+ position: { line: importPos.line, character: 10 } // in the import list
448
+ });
449
+ if (items) {
450
+ const completionItems = Array.isArray(items) ? items : items.items;
451
+ logSuccess(`Import completion: ${completionItems.length} items`);
452
+ }
453
+ }
454
+ }
455
+ /**
456
+ * Get completion kind name
457
+ */
458
+ function getCompletionKindName(kind) {
459
+ const kinds = {
460
+ 1: 'Text', 2: 'Method', 3: 'Function', 4: 'Constructor',
461
+ 5: 'Field', 6: 'Variable', 7: 'Class', 8: 'Interface',
462
+ 9: 'Module', 10: 'Property', 11: 'Unit', 12: 'Value',
463
+ 13: 'Enum', 14: 'Keyword', 15: 'Snippet', 16: 'Color',
464
+ 17: 'File', 18: 'Reference', 19: 'Folder', 20: 'EnumMember',
465
+ 21: 'Constant', 22: 'Struct', 23: 'Event', 24: 'Operator',
466
+ 25: 'TypeParameter'
467
+ };
468
+ return kinds[kind] || 'Unknown';
469
+ }
470
+ /**
471
+ * Test Definition feature
472
+ */
473
+ async function testDefinition(client, feature) {
474
+ logSection('Testing Go to Definition');
475
+ if (!feature.isSupported) {
476
+ logWarning('Definition not supported by server');
477
+ return;
478
+ }
479
+ const filePath = SAMPLE_FILES.calculator;
480
+ const content = readFileContent(filePath);
481
+ // Find usage of 'add' function
482
+ const addUsage = findPosition(content, 'result = add(');
483
+ if (addUsage) {
484
+ logInfo(`Testing go to definition on 'add' function call`);
485
+ const definition = await feature.provideDefinition({
486
+ textDocument: { uri: toUri(filePath) },
487
+ position: { line: addUsage.line, character: addUsage.character + 9 }
488
+ });
489
+ if (definition) {
490
+ if (Array.isArray(definition)) {
491
+ logSuccess(`Found ${definition.length} definition(s):`);
492
+ definition.forEach((loc) => {
493
+ const uri = 'targetUri' in loc ? loc.targetUri : loc.uri;
494
+ const range = 'targetRange' in loc ? loc.targetRange : loc.range;
495
+ console.log(` - ${path.basename(uri)} at line ${range.start.line + 1}`);
496
+ });
497
+ }
498
+ else {
499
+ const loc = definition;
500
+ logSuccess(`Definition found:`);
501
+ console.log(` - ${path.basename(loc.uri)} at line ${loc.range.start.line + 1}`);
502
+ }
503
+ }
504
+ else {
505
+ logWarning('No definition found');
506
+ }
507
+ }
508
+ // Test definition on Person class
509
+ const filePath2 = SAMPLE_FILES.userService;
510
+ const content2 = readFileContent(filePath2);
511
+ const personUsage = findPosition(content2, 'new Person(');
512
+ if (personUsage) {
513
+ logInfo(`Testing go to definition on 'Person' class usage`);
514
+ const definition = await feature.provideDefinition({
515
+ textDocument: { uri: toUri(filePath2) },
516
+ position: { line: personUsage.line, character: personUsage.character + 4 }
517
+ });
518
+ if (definition) {
519
+ logSuccess('Person class definition found');
520
+ }
521
+ }
522
+ }
523
+ /**
524
+ * Test References feature
525
+ */
526
+ async function testReferences(client, feature) {
527
+ logSection('Testing Find References');
528
+ if (!feature.isSupported) {
529
+ logWarning('References not supported by server');
530
+ return;
531
+ }
532
+ const filePath = SAMPLE_FILES.math;
533
+ const content = readFileContent(filePath);
534
+ // Find references to 'add' function
535
+ const addDef = findPosition(content, 'function add');
536
+ if (addDef) {
537
+ logInfo(`Finding all references to 'add' function`);
538
+ const references = await feature.findReferences({
539
+ textDocument: { uri: toUri(filePath) },
540
+ position: { line: addDef.line, character: addDef.character + 9 },
541
+ context: { includeDeclaration: true }
542
+ });
543
+ if (references && references.length > 0) {
544
+ logSuccess(`Found ${references.length} reference(s):`);
545
+ references.forEach((ref) => {
546
+ console.log(` - ${path.basename(ref.uri)} at line ${ref.range.start.line + 1}`);
547
+ });
548
+ }
549
+ else {
550
+ logWarning('No references found');
551
+ }
552
+ }
553
+ }
554
+ /**
555
+ * Test Document Symbols feature
556
+ */
557
+ async function testDocumentSymbols(client, feature) {
558
+ logSection('Testing Document Symbols');
559
+ if (!feature.isSupported) {
560
+ logWarning('Document symbols not supported by server');
561
+ return;
562
+ }
563
+ const filePath = SAMPLE_FILES.person;
564
+ logInfo(`Getting symbols for person.ts`);
565
+ const symbols = await feature.getDocumentSymbols({
566
+ textDocument: { uri: toUri(filePath) }
567
+ });
568
+ if (symbols && symbols.length > 0) {
569
+ logSuccess(`Found ${symbols.length} symbol(s):`);
570
+ printSymbols(symbols, 0);
571
+ }
572
+ else {
573
+ logWarning('No symbols found');
574
+ }
575
+ }
576
+ /**
577
+ * Print symbols recursively
578
+ */
579
+ function printSymbols(symbols, indent) {
580
+ const prefix = ' '.repeat(indent);
581
+ symbols.forEach(symbol => {
582
+ if ('children' in symbol) {
583
+ // DocumentSymbol with children
584
+ console.log(`${prefix}- ${symbol.name} (${getSymbolKindName(symbol.kind)})`);
585
+ if (symbol.children) {
586
+ printSymbols(symbol.children, indent + 1);
587
+ }
588
+ }
589
+ else {
590
+ // SymbolInformation
591
+ console.log(`${prefix}- ${symbol.name} (${getSymbolKindName(symbol.kind)})`);
592
+ }
593
+ });
594
+ }
595
+ /**
596
+ * Get symbol kind name
597
+ */
598
+ function getSymbolKindName(kind) {
599
+ const kinds = {
600
+ 1: 'File', 2: 'Module', 3: 'Namespace', 4: 'Package',
601
+ 5: 'Class', 6: 'Method', 7: 'Property', 8: 'Field',
602
+ 9: 'Constructor', 10: 'Enum', 11: 'Interface', 12: 'Function',
603
+ 13: 'Variable', 14: 'Constant', 15: 'String', 16: 'Number',
604
+ 17: 'Boolean', 18: 'Array', 19: 'Object', 20: 'Key',
605
+ 21: 'Null', 22: 'EnumMember', 23: 'Struct', 24: 'Event',
606
+ 25: 'Operator', 26: 'TypeParameter'
607
+ };
608
+ return kinds[kind] || 'Unknown';
609
+ }
610
+ /**
611
+ * Test Rename feature
612
+ */
613
+ async function testRename(client, feature) {
614
+ logSection('Testing Rename');
615
+ if (!feature.isSupported) {
616
+ logWarning('Rename not supported by server');
617
+ return;
618
+ }
619
+ const filePath = SAMPLE_FILES.math;
620
+ const content = readFileContent(filePath);
621
+ // Test prepare rename on 'add' function
622
+ const addDef = findPosition(content, 'function add');
623
+ if (addDef && feature.isPrepareSupported) {
624
+ logInfo(`Testing prepare rename on 'add' function`);
625
+ const prepareResult = await feature.prepareRename({
626
+ textDocument: { uri: toUri(filePath) },
627
+ position: { line: addDef.line, character: addDef.character + 9 }
628
+ });
629
+ if (prepareResult) {
630
+ logSuccess('Prepare rename result:');
631
+ if ('placeholder' in prepareResult) {
632
+ console.log(` Placeholder: ${prepareResult.placeholder}`);
633
+ }
634
+ else if ('defaultBehavior' in prepareResult) {
635
+ console.log(` Default behavior: ${prepareResult.defaultBehavior}`);
636
+ }
637
+ else {
638
+ console.log(` Range: line ${prepareResult.start.line + 1}, char ${prepareResult.start.character}`);
639
+ }
640
+ }
641
+ }
642
+ // Test actual rename (dry run - we won't apply the changes)
643
+ if (addDef) {
644
+ logInfo(`Testing rename 'add' -> 'addNumbers' (dry run)`);
645
+ const workspaceEdit = await feature.rename({
646
+ textDocument: { uri: toUri(filePath) },
647
+ position: { line: addDef.line, character: addDef.character + 9 },
648
+ newName: 'addNumbers'
649
+ });
650
+ if (workspaceEdit) {
651
+ logSuccess('Rename would affect:');
652
+ printWorkspaceEdit(workspaceEdit);
653
+ }
654
+ else {
655
+ logWarning('No rename edits returned');
656
+ }
657
+ }
658
+ }
659
+ /**
660
+ * Print workspace edit
661
+ */
662
+ function printWorkspaceEdit(edit) {
663
+ if (edit.changes) {
664
+ for (const [uri, edits] of Object.entries(edit.changes)) {
665
+ console.log(` ${path.basename(uri)}: ${edits.length} edit(s)`);
666
+ edits.slice(0, 3).forEach(e => {
667
+ console.log(` - Line ${e.range.start.line + 1}: "${e.newText}"`);
668
+ });
669
+ if (edits.length > 3) {
670
+ console.log(` ... and ${edits.length - 3} more`);
671
+ }
672
+ }
673
+ }
674
+ if (edit.documentChanges) {
675
+ console.log(` Document changes: ${edit.documentChanges.length} file(s)`);
676
+ }
677
+ }
678
+ // Run the demo
679
+ main().catch(console.error);
680
+ //# sourceMappingURL=client.js.map