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