@rescript/language-server 1.22.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/out/server.js ADDED
@@ -0,0 +1,1090 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const process_1 = __importDefault(require("process"));
30
+ const p = __importStar(require("vscode-languageserver-protocol"));
31
+ const v = __importStar(require("vscode-languageserver"));
32
+ const rpc = __importStar(require("vscode-jsonrpc/node"));
33
+ const path = __importStar(require("path"));
34
+ const fs_1 = __importDefault(require("fs"));
35
+ // TODO: check DidChangeWatchedFilesNotification.
36
+ const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
37
+ const lookup = __importStar(require("./lookup"));
38
+ const utils = __importStar(require("./utils"));
39
+ const c = __importStar(require("./constants"));
40
+ const chokidar = __importStar(require("chokidar"));
41
+ const console_1 = require("console");
42
+ const url_1 = require("url");
43
+ let extensionClientCapabilities = {};
44
+ // All values here are temporary, and will be overridden as the server is
45
+ // initialized, and the current config is received from the client.
46
+ let extensionConfiguration = {
47
+ allowBuiltInFormatter: false,
48
+ askToStartBuild: true,
49
+ inlayHints: {
50
+ enable: false,
51
+ maxLength: 25,
52
+ },
53
+ codeLens: false,
54
+ binaryPath: null,
55
+ platformPath: null,
56
+ signatureHelp: {
57
+ enabled: true,
58
+ },
59
+ };
60
+ // Below here is some state that's not important exactly how long it lives.
61
+ let hasPromptedAboutBuiltInFormatter = false;
62
+ let pullConfigurationPeriodically = null;
63
+ // https://microsoft.github.io/language-server-protocol/specification#initialize
64
+ // According to the spec, there could be requests before the 'initialize' request. Link in comment tells how to handle them.
65
+ let initialized = false;
66
+ let serverSentRequestIdCounter = 0;
67
+ // https://microsoft.github.io/language-server-protocol/specification#exit
68
+ let shutdownRequestAlreadyReceived = false;
69
+ let stupidFileContentCache = new Map();
70
+ let projectsFiles = new Map();
71
+ // ^ caching AND states AND distributed system. Why does LSP has to be stupid like this
72
+ // This keeps track of code actions extracted from diagnostics.
73
+ let codeActionsFromDiagnostics = {};
74
+ // will be properly defined later depending on the mode (stdio/node-rpc)
75
+ let send = (_) => { };
76
+ let findRescriptBinary = (projectRootPath) => extensionConfiguration.binaryPath == null
77
+ ? lookup.findFilePathFromProjectRoot(projectRootPath, path.join(c.nodeModulesBinDir, c.rescriptBinName))
78
+ : utils.findBinary(extensionConfiguration.binaryPath, c.rescriptBinName);
79
+ let findPlatformPath = (projectRootPath) => {
80
+ if (extensionConfiguration.platformPath != null) {
81
+ return extensionConfiguration.platformPath;
82
+ }
83
+ let rescriptDir = lookup.findFilePathFromProjectRoot(projectRootPath, path.join("node_modules", "rescript"));
84
+ if (rescriptDir == null) {
85
+ return null;
86
+ }
87
+ let platformPath = path.join(rescriptDir, c.platformDir);
88
+ // Workaround for darwinarm64 which has no folder yet in ReScript <= 9.1.4
89
+ if (process_1.default.platform == "darwin" &&
90
+ process_1.default.arch == "arm64" &&
91
+ !fs_1.default.existsSync(platformPath)) {
92
+ platformPath = path.join(rescriptDir, process_1.default.platform);
93
+ }
94
+ return platformPath;
95
+ };
96
+ let findBscExeBinary = (projectRootPath) => utils.findBinary(findPlatformPath(projectRootPath), c.bscExeName);
97
+ let createInterfaceRequest = new v.RequestType("textDocument/createInterface");
98
+ let openCompiledFileRequest = new v.RequestType("textDocument/openCompiled");
99
+ let getCurrentCompilerDiagnosticsForFile = (fileUri) => {
100
+ let diagnostics = null;
101
+ projectsFiles.forEach((projectFile, _projectRootPath) => {
102
+ if (diagnostics == null && projectFile.filesDiagnostics[fileUri] != null) {
103
+ diagnostics = projectFile.filesDiagnostics[fileUri].slice();
104
+ }
105
+ });
106
+ return diagnostics !== null && diagnostics !== void 0 ? diagnostics : [];
107
+ };
108
+ let sendUpdatedDiagnostics = () => {
109
+ projectsFiles.forEach((projectFile, projectRootPath) => {
110
+ let { filesWithDiagnostics } = projectFile;
111
+ let compilerLogPath = path.join(projectRootPath, c.compilerLogPartialPath);
112
+ let content = fs_1.default.readFileSync(compilerLogPath, { encoding: "utf-8" });
113
+ let { done, result: filesAndErrors, codeActions, linesWithParseErrors, } = utils.parseCompilerLogOutput(content);
114
+ if (linesWithParseErrors.length > 0) {
115
+ let params = {
116
+ type: p.MessageType.Warning,
117
+ message: `There are more compiler warning/errors that we could not parse. You can help us fix this by opening an [issue on the repository](https://github.com/rescript-lang/rescript-vscode/issues/new?title=Compiler%20log%20parse%20error), pasting the contents of the file [lib/bs/.compiler.log](file://${compilerLogPath}).`,
118
+ };
119
+ let message = {
120
+ jsonrpc: c.jsonrpcVersion,
121
+ method: "window/showMessage",
122
+ params: params,
123
+ };
124
+ send(message);
125
+ }
126
+ projectFile.filesDiagnostics = filesAndErrors;
127
+ codeActionsFromDiagnostics = codeActions;
128
+ // diff
129
+ Object.keys(filesAndErrors).forEach((file) => {
130
+ let params = {
131
+ uri: file,
132
+ diagnostics: filesAndErrors[file],
133
+ };
134
+ let notification = {
135
+ jsonrpc: c.jsonrpcVersion,
136
+ method: "textDocument/publishDiagnostics",
137
+ params: params,
138
+ };
139
+ send(notification);
140
+ filesWithDiagnostics.add(file);
141
+ });
142
+ if (done) {
143
+ // clear old files
144
+ filesWithDiagnostics.forEach((file) => {
145
+ if (filesAndErrors[file] == null) {
146
+ // Doesn't exist in the new diagnostics. Clear this diagnostic
147
+ let params = {
148
+ uri: file,
149
+ diagnostics: [],
150
+ };
151
+ let notification = {
152
+ jsonrpc: c.jsonrpcVersion,
153
+ method: "textDocument/publishDiagnostics",
154
+ params: params,
155
+ };
156
+ send(notification);
157
+ filesWithDiagnostics.delete(file);
158
+ }
159
+ });
160
+ }
161
+ });
162
+ };
163
+ let deleteProjectDiagnostics = (projectRootPath) => {
164
+ let root = projectsFiles.get(projectRootPath);
165
+ if (root != null) {
166
+ root.filesWithDiagnostics.forEach((file) => {
167
+ let params = {
168
+ uri: file,
169
+ diagnostics: [],
170
+ };
171
+ let notification = {
172
+ jsonrpc: c.jsonrpcVersion,
173
+ method: "textDocument/publishDiagnostics",
174
+ params: params,
175
+ };
176
+ send(notification);
177
+ });
178
+ projectsFiles.delete(projectRootPath);
179
+ }
180
+ };
181
+ let sendCompilationFinishedMessage = () => {
182
+ let notification = {
183
+ jsonrpc: c.jsonrpcVersion,
184
+ method: "rescript/compilationFinished",
185
+ };
186
+ send(notification);
187
+ };
188
+ let compilerLogsWatcher = chokidar
189
+ .watch([], {
190
+ awaitWriteFinish: {
191
+ stabilityThreshold: 1,
192
+ },
193
+ })
194
+ .on("all", (_e, changedPath) => {
195
+ var _a;
196
+ sendUpdatedDiagnostics();
197
+ sendCompilationFinishedMessage();
198
+ if (((_a = extensionConfiguration.inlayHints) === null || _a === void 0 ? void 0 : _a.enable) === true) {
199
+ sendInlayHintsRefresh();
200
+ }
201
+ if (extensionConfiguration.codeLens === true) {
202
+ sendCodeLensRefresh();
203
+ }
204
+ });
205
+ let stopWatchingCompilerLog = () => {
206
+ // TODO: cleanup of compilerLogs?
207
+ compilerLogsWatcher.close();
208
+ };
209
+ let openedFile = (fileUri, fileContent) => {
210
+ let filePath = (0, url_1.fileURLToPath)(fileUri);
211
+ stupidFileContentCache.set(filePath, fileContent);
212
+ let projectRootPath = utils.findProjectRootOfFile(filePath);
213
+ if (projectRootPath != null) {
214
+ let projectRootState = projectsFiles.get(projectRootPath);
215
+ if (projectRootState == null) {
216
+ projectRootState = {
217
+ openFiles: new Set(),
218
+ filesWithDiagnostics: new Set(),
219
+ filesDiagnostics: {},
220
+ bsbWatcherByEditor: null,
221
+ hasPromptedToStartBuild: /(\/|\\)node_modules(\/|\\)/.test(projectRootPath)
222
+ ? "never"
223
+ : false,
224
+ };
225
+ projectsFiles.set(projectRootPath, projectRootState);
226
+ compilerLogsWatcher.add(path.join(projectRootPath, c.compilerLogPartialPath));
227
+ }
228
+ let root = projectsFiles.get(projectRootPath);
229
+ root.openFiles.add(filePath);
230
+ // check if .bsb.lock is still there. If not, start a bsb -w ourselves
231
+ // because otherwise the diagnostics info we'll display might be stale
232
+ let bsbLockPath = path.join(projectRootPath, c.bsbLock);
233
+ if (projectRootState.hasPromptedToStartBuild === false &&
234
+ extensionConfiguration.askToStartBuild === true &&
235
+ !fs_1.default.existsSync(bsbLockPath)) {
236
+ // TODO: sometime stale .bsb.lock dangling. bsb -w knows .bsb.lock is
237
+ // stale. Use that logic
238
+ // TODO: close watcher when lang-server shuts down
239
+ if (findRescriptBinary(projectRootPath) != null) {
240
+ let payload = {
241
+ title: c.startBuildAction,
242
+ projectRootPath: projectRootPath,
243
+ };
244
+ let params = {
245
+ type: p.MessageType.Info,
246
+ message: `Start a build for this project to get the freshest data?`,
247
+ actions: [payload],
248
+ };
249
+ let request = {
250
+ jsonrpc: c.jsonrpcVersion,
251
+ id: serverSentRequestIdCounter++,
252
+ method: "window/showMessageRequest",
253
+ params: params,
254
+ };
255
+ send(request);
256
+ projectRootState.hasPromptedToStartBuild = true;
257
+ // the client might send us back the "start build" action, which we'll
258
+ // handle in the isResponseMessage check in the message handling way
259
+ // below
260
+ }
261
+ else {
262
+ let request = {
263
+ jsonrpc: c.jsonrpcVersion,
264
+ method: "window/showMessage",
265
+ params: {
266
+ type: p.MessageType.Error,
267
+ message: extensionConfiguration.binaryPath == null
268
+ ? `Can't find ReScript binary in ${path.join(projectRootPath, c.nodeModulesBinDir)} or parent directories. Did you install it? It's required to use "rescript" > 9.1`
269
+ : `Can't find ReScript binary in the directory ${extensionConfiguration.binaryPath}`,
270
+ },
271
+ };
272
+ send(request);
273
+ }
274
+ }
275
+ // no need to call sendUpdatedDiagnostics() here; the watcher add will
276
+ // call the listener which calls it
277
+ }
278
+ };
279
+ let closedFile = (fileUri) => {
280
+ let filePath = (0, url_1.fileURLToPath)(fileUri);
281
+ stupidFileContentCache.delete(filePath);
282
+ let projectRootPath = utils.findProjectRootOfFile(filePath);
283
+ if (projectRootPath != null) {
284
+ let root = projectsFiles.get(projectRootPath);
285
+ if (root != null) {
286
+ root.openFiles.delete(filePath);
287
+ // clear diagnostics too if no open files open in said project
288
+ if (root.openFiles.size === 0) {
289
+ compilerLogsWatcher.unwatch(path.join(projectRootPath, c.compilerLogPartialPath));
290
+ deleteProjectDiagnostics(projectRootPath);
291
+ if (root.bsbWatcherByEditor !== null) {
292
+ root.bsbWatcherByEditor.kill();
293
+ root.bsbWatcherByEditor = null;
294
+ }
295
+ }
296
+ }
297
+ }
298
+ };
299
+ let updateOpenedFile = (fileUri, fileContent) => {
300
+ let filePath = (0, url_1.fileURLToPath)(fileUri);
301
+ (0, console_1.assert)(stupidFileContentCache.has(filePath));
302
+ stupidFileContentCache.set(filePath, fileContent);
303
+ };
304
+ let getOpenedFileContent = (fileUri) => {
305
+ let filePath = (0, url_1.fileURLToPath)(fileUri);
306
+ let content = stupidFileContentCache.get(filePath);
307
+ (0, console_1.assert)(content != null);
308
+ return content;
309
+ };
310
+ function listen(useStdio = false) {
311
+ // Start listening now!
312
+ // We support two modes: the regular node RPC mode for VSCode, and the --stdio
313
+ // mode for other editors The latter is _technically unsupported_. It's an
314
+ // implementation detail that might change at any time
315
+ if (useStdio) {
316
+ let writer = new rpc.StreamMessageWriter(process_1.default.stdout);
317
+ let reader = new rpc.StreamMessageReader(process_1.default.stdin);
318
+ // proper `this` scope for writer
319
+ send = (msg) => writer.write(msg);
320
+ reader.listen(onMessage);
321
+ }
322
+ else {
323
+ // proper `this` scope for process
324
+ send = (msg) => process_1.default.send(msg);
325
+ process_1.default.on("message", onMessage);
326
+ }
327
+ }
328
+ exports.default = listen;
329
+ function hover(msg) {
330
+ let params = msg.params;
331
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
332
+ let code = getOpenedFileContent(params.textDocument.uri);
333
+ let tmpname = utils.createFileInTempDir();
334
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
335
+ let response = utils.runAnalysisCommand(filePath, [
336
+ "hover",
337
+ filePath,
338
+ params.position.line,
339
+ params.position.character,
340
+ tmpname,
341
+ Boolean(extensionClientCapabilities.supportsMarkdownLinks),
342
+ ], msg);
343
+ fs_1.default.unlink(tmpname, () => null);
344
+ return response;
345
+ }
346
+ function inlayHint(msg) {
347
+ const params = msg.params;
348
+ const filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
349
+ const response = utils.runAnalysisCommand(filePath, [
350
+ "inlayHint",
351
+ filePath,
352
+ params.range.start.line,
353
+ params.range.end.line,
354
+ extensionConfiguration.inlayHints.maxLength,
355
+ ], msg);
356
+ return response;
357
+ }
358
+ function sendInlayHintsRefresh() {
359
+ let request = {
360
+ jsonrpc: c.jsonrpcVersion,
361
+ method: p.InlayHintRefreshRequest.method,
362
+ id: serverSentRequestIdCounter++,
363
+ };
364
+ send(request);
365
+ }
366
+ function codeLens(msg) {
367
+ const params = msg.params;
368
+ const filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
369
+ const response = utils.runAnalysisCommand(filePath, ["codeLens", filePath], msg);
370
+ return response;
371
+ }
372
+ function sendCodeLensRefresh() {
373
+ let request = {
374
+ jsonrpc: c.jsonrpcVersion,
375
+ method: p.CodeLensRefreshRequest.method,
376
+ id: serverSentRequestIdCounter++,
377
+ };
378
+ send(request);
379
+ }
380
+ function signatureHelp(msg) {
381
+ let params = msg.params;
382
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
383
+ let code = getOpenedFileContent(params.textDocument.uri);
384
+ let tmpname = utils.createFileInTempDir();
385
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
386
+ let response = utils.runAnalysisCommand(filePath, [
387
+ "signatureHelp",
388
+ filePath,
389
+ params.position.line,
390
+ params.position.character,
391
+ tmpname,
392
+ ], msg);
393
+ fs_1.default.unlink(tmpname, () => null);
394
+ return response;
395
+ }
396
+ function definition(msg) {
397
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
398
+ let params = msg.params;
399
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
400
+ let response = utils.runAnalysisCommand(filePath, ["definition", filePath, params.position.line, params.position.character], msg);
401
+ return response;
402
+ }
403
+ function typeDefinition(msg) {
404
+ // https://microsoft.github.io/language-server-protocol/specification/specification-current/#textDocument_typeDefinition
405
+ let params = msg.params;
406
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
407
+ let response = utils.runAnalysisCommand(filePath, [
408
+ "typeDefinition",
409
+ filePath,
410
+ params.position.line,
411
+ params.position.character,
412
+ ], msg);
413
+ return response;
414
+ }
415
+ function references(msg) {
416
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
417
+ let params = msg.params;
418
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
419
+ let result = utils.getReferencesForPosition(filePath, params.position);
420
+ let response = {
421
+ jsonrpc: c.jsonrpcVersion,
422
+ id: msg.id,
423
+ result,
424
+ // error: code and message set in case an exception happens during the definition request.
425
+ };
426
+ return response;
427
+ }
428
+ function prepareRename(msg) {
429
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_prepareRename
430
+ let params = msg.params;
431
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
432
+ let locations = utils.getReferencesForPosition(filePath, params.position);
433
+ let result = null;
434
+ if (locations !== null) {
435
+ locations.forEach((loc) => {
436
+ if (path.normalize((0, url_1.fileURLToPath)(loc.uri)) ===
437
+ path.normalize((0, url_1.fileURLToPath)(params.textDocument.uri))) {
438
+ let { start, end } = loc.range;
439
+ let pos = params.position;
440
+ if (start.character <= pos.character &&
441
+ start.line <= pos.line &&
442
+ end.character >= pos.character &&
443
+ end.line >= pos.line) {
444
+ result = loc.range;
445
+ }
446
+ }
447
+ });
448
+ }
449
+ return {
450
+ jsonrpc: c.jsonrpcVersion,
451
+ id: msg.id,
452
+ result,
453
+ };
454
+ }
455
+ function rename(msg) {
456
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
457
+ let params = msg.params;
458
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
459
+ let documentChanges = utils.runAnalysisAfterSanityCheck(filePath, [
460
+ "rename",
461
+ filePath,
462
+ params.position.line,
463
+ params.position.character,
464
+ params.newName,
465
+ ]);
466
+ let result = null;
467
+ if (documentChanges !== null) {
468
+ result = { documentChanges };
469
+ }
470
+ let response = {
471
+ jsonrpc: c.jsonrpcVersion,
472
+ id: msg.id,
473
+ result,
474
+ };
475
+ return response;
476
+ }
477
+ function documentSymbol(msg) {
478
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
479
+ let params = msg.params;
480
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
481
+ let extension = path.extname(params.textDocument.uri);
482
+ let code = getOpenedFileContent(params.textDocument.uri);
483
+ let tmpname = utils.createFileInTempDir(extension);
484
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
485
+ let response = utils.runAnalysisCommand(filePath, ["documentSymbol", tmpname], msg,
486
+ /* projectRequired */ false);
487
+ fs_1.default.unlink(tmpname, () => null);
488
+ return response;
489
+ }
490
+ function askForAllCurrentConfiguration() {
491
+ // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
492
+ let params = {
493
+ items: [
494
+ {
495
+ section: "rescript.settings",
496
+ },
497
+ ],
498
+ };
499
+ let req = {
500
+ jsonrpc: c.jsonrpcVersion,
501
+ id: c.configurationRequestId,
502
+ method: p.ConfigurationRequest.type.method,
503
+ params,
504
+ };
505
+ send(req);
506
+ }
507
+ function semanticTokens(msg) {
508
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
509
+ let params = msg.params;
510
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
511
+ let extension = path.extname(params.textDocument.uri);
512
+ let code = getOpenedFileContent(params.textDocument.uri);
513
+ let tmpname = utils.createFileInTempDir(extension);
514
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
515
+ let response = utils.runAnalysisCommand(filePath, ["semanticTokens", tmpname], msg,
516
+ /* projectRequired */ false);
517
+ fs_1.default.unlink(tmpname, () => null);
518
+ return response;
519
+ }
520
+ function completion(msg) {
521
+ // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
522
+ let params = msg.params;
523
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
524
+ let code = getOpenedFileContent(params.textDocument.uri);
525
+ let tmpname = utils.createFileInTempDir();
526
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
527
+ let response = utils.runAnalysisCommand(filePath, [
528
+ "completion",
529
+ filePath,
530
+ params.position.line,
531
+ params.position.character,
532
+ tmpname,
533
+ Boolean(extensionClientCapabilities.supportsSnippetSyntax),
534
+ ], msg);
535
+ fs_1.default.unlink(tmpname, () => null);
536
+ return response;
537
+ }
538
+ function codeAction(msg) {
539
+ var _a;
540
+ let params = msg.params;
541
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
542
+ let code = getOpenedFileContent(params.textDocument.uri);
543
+ let extension = path.extname(params.textDocument.uri);
544
+ let tmpname = utils.createFileInTempDir(extension);
545
+ // Check local code actions coming from the diagnostics.
546
+ let localResults = [];
547
+ (_a = codeActionsFromDiagnostics[params.textDocument.uri]) === null || _a === void 0 ? void 0 : _a.forEach(({ range, codeAction }) => {
548
+ if (utils.rangeContainsRange(range, params.range)) {
549
+ localResults.push(codeAction);
550
+ }
551
+ });
552
+ fs_1.default.writeFileSync(tmpname, code, { encoding: "utf-8" });
553
+ let response = utils.runAnalysisCommand(filePath, [
554
+ "codeAction",
555
+ filePath,
556
+ params.range.start.line,
557
+ params.range.start.character,
558
+ params.range.end.line,
559
+ params.range.end.character,
560
+ tmpname,
561
+ ], msg);
562
+ fs_1.default.unlink(tmpname, () => null);
563
+ let { result } = response;
564
+ // We must send `null` when there are no results, empty array isn't enough.
565
+ let codeActions = result != null && Array.isArray(result)
566
+ ? [...localResults, ...result]
567
+ : localResults;
568
+ let res = {
569
+ jsonrpc: c.jsonrpcVersion,
570
+ id: msg.id,
571
+ result: codeActions.length > 0 ? codeActions : null,
572
+ };
573
+ return res;
574
+ }
575
+ function format(msg) {
576
+ // technically, a formatting failure should reply with the error. Sadly
577
+ // the LSP alert box for these error replies sucks (e.g. doesn't actually
578
+ // display the message). In order to signal the client to display a proper
579
+ // alert box (sometime with actionable buttons), we need to first send
580
+ // back a fake success message (because each request mandates a
581
+ // response), then right away send a server notification to display a
582
+ // nicer alert. Ugh.
583
+ let fakeSuccessResponse = {
584
+ jsonrpc: c.jsonrpcVersion,
585
+ id: msg.id,
586
+ result: [],
587
+ };
588
+ let params = msg.params;
589
+ let filePath = (0, url_1.fileURLToPath)(params.textDocument.uri);
590
+ let extension = path.extname(params.textDocument.uri);
591
+ if (extension !== c.resExt && extension !== c.resiExt) {
592
+ let params = {
593
+ type: p.MessageType.Error,
594
+ message: `Not a ${c.resExt} or ${c.resiExt} file. Cannot format it.`,
595
+ };
596
+ let response = {
597
+ jsonrpc: c.jsonrpcVersion,
598
+ method: "window/showMessage",
599
+ params: params,
600
+ };
601
+ return [fakeSuccessResponse, response];
602
+ }
603
+ else {
604
+ // code will always be defined here, even though technically it can be undefined
605
+ let code = getOpenedFileContent(params.textDocument.uri);
606
+ let projectRootPath = utils.findProjectRootOfFile(filePath);
607
+ let bscExeBinaryPath = findBscExeBinary(projectRootPath);
608
+ let formattedResult = utils.formatCode(bscExeBinaryPath, filePath, code, extensionConfiguration.allowBuiltInFormatter);
609
+ if (formattedResult.kind === "success") {
610
+ let max = code.length;
611
+ let result = [
612
+ {
613
+ range: {
614
+ start: { line: 0, character: 0 },
615
+ end: { line: max, character: max },
616
+ },
617
+ newText: formattedResult.result,
618
+ },
619
+ ];
620
+ let response = {
621
+ jsonrpc: c.jsonrpcVersion,
622
+ id: msg.id,
623
+ result: result,
624
+ };
625
+ return [response];
626
+ }
627
+ else if (formattedResult.kind === "blocked-using-built-in-formatter") {
628
+ // Let's only prompt the user once about this, or things might become annoying.
629
+ if (hasPromptedAboutBuiltInFormatter) {
630
+ return [fakeSuccessResponse];
631
+ }
632
+ hasPromptedAboutBuiltInFormatter = true;
633
+ let params = {
634
+ type: p.MessageType.Warning,
635
+ message: `Formatting not applied! Could not find the ReScript compiler in the current project, and you haven't configured the extension to allow formatting using the built in formatter. To allow formatting files not strictly part of a ReScript project using the built in formatter, [please configure the extension to allow that.](command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify(["rescript.settings.allowBuiltInFormatter"]))})`,
636
+ };
637
+ let response = {
638
+ jsonrpc: c.jsonrpcVersion,
639
+ method: "window/showMessage",
640
+ params: params,
641
+ };
642
+ return [fakeSuccessResponse, response];
643
+ }
644
+ else {
645
+ // let the diagnostics logic display the updated syntax errors,
646
+ // from the build.
647
+ // Again, not sending the actual errors. See fakeSuccessResponse
648
+ // above for explanation
649
+ return [fakeSuccessResponse];
650
+ }
651
+ }
652
+ }
653
+ let updateDiagnosticSyntax = (fileUri, fileContent) => {
654
+ let filePath = (0, url_1.fileURLToPath)(fileUri);
655
+ let extension = path.extname(filePath);
656
+ let tmpname = utils.createFileInTempDir(extension);
657
+ fs_1.default.writeFileSync(tmpname, fileContent, { encoding: "utf-8" });
658
+ // We need to account for any existing diagnostics from the compiler for this
659
+ // file. If we don't we might accidentally clear the current file's compiler
660
+ // diagnostics if there's no syntax diagostics to send. This is because
661
+ // publishing an empty diagnostics array is equivalent to saying "clear all
662
+ // errors".
663
+ let compilerDiagnosticsForFile = getCurrentCompilerDiagnosticsForFile(fileUri);
664
+ let syntaxDiagnosticsForFile = utils.runAnalysisAfterSanityCheck(filePath, ["diagnosticSyntax", tmpname]);
665
+ let notification = {
666
+ jsonrpc: c.jsonrpcVersion,
667
+ method: "textDocument/publishDiagnostics",
668
+ params: {
669
+ uri: fileUri,
670
+ diagnostics: [...syntaxDiagnosticsForFile, ...compilerDiagnosticsForFile],
671
+ },
672
+ };
673
+ fs_1.default.unlink(tmpname, () => null);
674
+ send(notification);
675
+ };
676
+ function createInterface(msg) {
677
+ let params = msg.params;
678
+ let extension = path.extname(params.uri);
679
+ let filePath = (0, url_1.fileURLToPath)(params.uri);
680
+ let projDir = utils.findProjectRootOfFile(filePath);
681
+ if (projDir === null) {
682
+ let params = {
683
+ type: p.MessageType.Error,
684
+ message: `Cannot locate project directory to generate the interface file.`,
685
+ };
686
+ let response = {
687
+ jsonrpc: c.jsonrpcVersion,
688
+ method: "window/showMessage",
689
+ params: params,
690
+ };
691
+ return response;
692
+ }
693
+ if (extension !== c.resExt) {
694
+ let params = {
695
+ type: p.MessageType.Error,
696
+ message: `Not a ${c.resExt} file. Cannot create an interface for it.`,
697
+ };
698
+ let response = {
699
+ jsonrpc: c.jsonrpcVersion,
700
+ method: "window/showMessage",
701
+ params: params,
702
+ };
703
+ return response;
704
+ }
705
+ let resPartialPath = filePath.split(projDir)[1];
706
+ // The .cmi filename may have a namespace suffix appended.
707
+ let namespaceResult = utils.getNamespaceNameFromConfigFile(projDir);
708
+ if (namespaceResult.kind === "error") {
709
+ let params = {
710
+ type: p.MessageType.Error,
711
+ message: `Error reading ReScript config file.`,
712
+ };
713
+ let response = {
714
+ jsonrpc: c.jsonrpcVersion,
715
+ method: "window/showMessage",
716
+ params,
717
+ };
718
+ return response;
719
+ }
720
+ let namespace = namespaceResult.result;
721
+ let suffixToAppend = namespace.length > 0 ? "-" + namespace : "";
722
+ let cmiPartialPath = path.join(path.dirname(resPartialPath), path.basename(resPartialPath, c.resExt) + suffixToAppend + c.cmiExt);
723
+ let cmiPath = path.join(projDir, c.compilerDirPartialPath, cmiPartialPath);
724
+ let cmiAvailable = fs_1.default.existsSync(cmiPath);
725
+ if (!cmiAvailable) {
726
+ let params = {
727
+ type: p.MessageType.Error,
728
+ message: `No compiled interface file found. Please compile your project first.`,
729
+ };
730
+ let response = {
731
+ jsonrpc: c.jsonrpcVersion,
732
+ method: "window/showMessage",
733
+ params,
734
+ };
735
+ return response;
736
+ }
737
+ let response = utils.runAnalysisCommand(filePath, ["createInterface", filePath, cmiPath], msg);
738
+ let result = typeof response.result === "string" ? response.result : "";
739
+ try {
740
+ let resiPath = lookup.replaceFileExtension(filePath, c.resiExt);
741
+ fs_1.default.writeFileSync(resiPath, result, { encoding: "utf-8" });
742
+ let response = {
743
+ jsonrpc: c.jsonrpcVersion,
744
+ id: msg.id,
745
+ result: {
746
+ uri: utils.pathToURI(resiPath),
747
+ },
748
+ };
749
+ return response;
750
+ }
751
+ catch (e) {
752
+ let response = {
753
+ jsonrpc: c.jsonrpcVersion,
754
+ id: msg.id,
755
+ error: {
756
+ code: p.ErrorCodes.InternalError,
757
+ message: "Unable to create interface file.",
758
+ },
759
+ };
760
+ return response;
761
+ }
762
+ }
763
+ function openCompiledFile(msg) {
764
+ let params = msg.params;
765
+ let filePath = (0, url_1.fileURLToPath)(params.uri);
766
+ let projDir = utils.findProjectRootOfFile(filePath);
767
+ if (projDir === null) {
768
+ let params = {
769
+ type: p.MessageType.Error,
770
+ message: `Cannot locate project directory.`,
771
+ };
772
+ let response = {
773
+ jsonrpc: c.jsonrpcVersion,
774
+ method: "window/showMessage",
775
+ params: params,
776
+ };
777
+ return response;
778
+ }
779
+ let compiledFilePath = utils.getCompiledFilePath(filePath, projDir);
780
+ if (compiledFilePath.kind === "error" ||
781
+ !fs_1.default.existsSync(compiledFilePath.result)) {
782
+ let message = compiledFilePath.kind === "success"
783
+ ? `No compiled file found. Expected it at: ${compiledFilePath.result}`
784
+ : `No compiled file found. Please compile your project first.`;
785
+ let params = {
786
+ type: p.MessageType.Error,
787
+ message,
788
+ };
789
+ let response = {
790
+ jsonrpc: c.jsonrpcVersion,
791
+ method: "window/showMessage",
792
+ params,
793
+ };
794
+ return response;
795
+ }
796
+ let response = {
797
+ jsonrpc: c.jsonrpcVersion,
798
+ id: msg.id,
799
+ result: {
800
+ uri: utils.pathToURI(compiledFilePath.result),
801
+ },
802
+ };
803
+ return response;
804
+ }
805
+ function onMessage(msg) {
806
+ var _a, _b, _c, _d, _f, _g, _h;
807
+ if (p.Message.isNotification(msg)) {
808
+ // notification message, aka the client ends it and doesn't want a reply
809
+ if (!initialized && msg.method !== "exit") {
810
+ // From spec: "Notifications should be dropped, except for the exit notification. This will allow the exit of a server without an initialize request"
811
+ // For us: do nothing. We don't have anything we need to clean up right now
812
+ // TODO: we might have things we need to clean up now... like some watcher stuff
813
+ }
814
+ else if (msg.method === "exit") {
815
+ // The server should exit with success code 0 if the shutdown request has been received before; otherwise with error code 1
816
+ if (shutdownRequestAlreadyReceived) {
817
+ process_1.default.exit(0);
818
+ }
819
+ else {
820
+ process_1.default.exit(1);
821
+ }
822
+ }
823
+ else if (msg.method === vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.method) {
824
+ let params = msg.params;
825
+ openedFile(params.textDocument.uri, params.textDocument.text);
826
+ updateDiagnosticSyntax(params.textDocument.uri, params.textDocument.text);
827
+ }
828
+ else if (msg.method === vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.method) {
829
+ let params = msg.params;
830
+ let extName = path.extname(params.textDocument.uri);
831
+ if (extName === c.resExt || extName === c.resiExt) {
832
+ let changes = params.contentChanges;
833
+ if (changes.length === 0) {
834
+ // no change?
835
+ }
836
+ else {
837
+ // we currently only support full changes
838
+ updateOpenedFile(params.textDocument.uri, changes[changes.length - 1].text);
839
+ updateDiagnosticSyntax(params.textDocument.uri, changes[changes.length - 1].text);
840
+ }
841
+ }
842
+ }
843
+ else if (msg.method === vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.method) {
844
+ let params = msg.params;
845
+ closedFile(params.textDocument.uri);
846
+ }
847
+ else if (msg.method === vscode_languageserver_protocol_1.DidChangeConfigurationNotification.type.method) {
848
+ // Can't seem to get this notification to trigger, but if it does this will be here and ensure we're synced up at the server.
849
+ askForAllCurrentConfiguration();
850
+ }
851
+ }
852
+ else if (p.Message.isRequest(msg)) {
853
+ // request message, aka client sent request and waits for our mandatory reply
854
+ if (!initialized && msg.method !== "initialize") {
855
+ let response = {
856
+ jsonrpc: c.jsonrpcVersion,
857
+ id: msg.id,
858
+ error: {
859
+ code: p.ErrorCodes.ServerNotInitialized,
860
+ message: "Server not initialized.",
861
+ },
862
+ };
863
+ send(response);
864
+ }
865
+ else if (msg.method === "initialize") {
866
+ // Save initial configuration, if present
867
+ let initParams = msg.params;
868
+ let initialConfiguration = (_a = initParams.initializationOptions) === null || _a === void 0 ? void 0 : _a.extensionConfiguration;
869
+ if (initialConfiguration != null) {
870
+ extensionConfiguration = initialConfiguration;
871
+ }
872
+ // These are static configuration options the client can set to enable certain
873
+ let extensionClientCapabilitiesFromClient = (_b = initParams
874
+ .initializationOptions) === null || _b === void 0 ? void 0 : _b.extensionClientCapabilities;
875
+ if (extensionClientCapabilitiesFromClient != null) {
876
+ extensionClientCapabilities = extensionClientCapabilitiesFromClient;
877
+ }
878
+ extensionClientCapabilities.supportsSnippetSyntax = Boolean((_f = (_d = (_c = initParams.capabilities.textDocument) === null || _c === void 0 ? void 0 : _c.completion) === null || _d === void 0 ? void 0 : _d.completionItem) === null || _f === void 0 ? void 0 : _f.snippetSupport);
879
+ // send the list of features we support
880
+ let result = {
881
+ // This tells the client: "hey, we support the following operations".
882
+ // Example: we want to expose "jump-to-definition".
883
+ // By adding `definitionProvider: true`, the client will now send "jump-to-definition" requests.
884
+ capabilities: {
885
+ // TODO: incremental sync?
886
+ textDocumentSync: v.TextDocumentSyncKind.Full,
887
+ documentFormattingProvider: true,
888
+ hoverProvider: true,
889
+ definitionProvider: true,
890
+ typeDefinitionProvider: true,
891
+ referencesProvider: true,
892
+ codeActionProvider: true,
893
+ renameProvider: { prepareProvider: true },
894
+ documentSymbolProvider: true,
895
+ completionProvider: {
896
+ triggerCharacters: [".", ">", "@", "~", '"', "=", "("],
897
+ },
898
+ semanticTokensProvider: {
899
+ legend: {
900
+ tokenTypes: [
901
+ "operator",
902
+ "variable",
903
+ "type",
904
+ "modifier",
905
+ "namespace",
906
+ "enumMember",
907
+ "property",
908
+ "interface", // emit jsxlowercase, div in <div> as interface
909
+ ],
910
+ tokenModifiers: [],
911
+ },
912
+ documentSelector: [{ scheme: "file", language: "rescript" }],
913
+ // TODO: Support range for full, and add delta support
914
+ full: true,
915
+ },
916
+ inlayHintProvider: (_g = extensionConfiguration.inlayHints) === null || _g === void 0 ? void 0 : _g.enable,
917
+ codeLensProvider: extensionConfiguration.codeLens
918
+ ? {
919
+ workDoneProgress: false,
920
+ }
921
+ : undefined,
922
+ signatureHelpProvider: ((_h = extensionConfiguration.signatureHelp) === null || _h === void 0 ? void 0 : _h.enabled)
923
+ ? {
924
+ triggerCharacters: ["("],
925
+ retriggerCharacters: ["=", ","],
926
+ }
927
+ : undefined,
928
+ },
929
+ };
930
+ let response = {
931
+ jsonrpc: c.jsonrpcVersion,
932
+ id: msg.id,
933
+ result: result,
934
+ };
935
+ initialized = true;
936
+ // Periodically pull configuration from the client.
937
+ pullConfigurationPeriodically = setInterval(() => {
938
+ askForAllCurrentConfiguration();
939
+ }, c.pullConfigurationInterval);
940
+ send(response);
941
+ }
942
+ else if (msg.method === "initialized") {
943
+ // sent from client after initialize. Nothing to do for now
944
+ let response = {
945
+ jsonrpc: c.jsonrpcVersion,
946
+ id: msg.id,
947
+ result: null,
948
+ };
949
+ send(response);
950
+ }
951
+ else if (msg.method === "shutdown") {
952
+ // https://microsoft.github.io/language-server-protocol/specification#shutdown
953
+ if (shutdownRequestAlreadyReceived) {
954
+ let response = {
955
+ jsonrpc: c.jsonrpcVersion,
956
+ id: msg.id,
957
+ error: {
958
+ code: p.ErrorCodes.InvalidRequest,
959
+ message: `Language server already received the shutdown request`,
960
+ },
961
+ };
962
+ send(response);
963
+ }
964
+ else {
965
+ shutdownRequestAlreadyReceived = true;
966
+ // TODO: recheck logic around init/shutdown...
967
+ stopWatchingCompilerLog();
968
+ // TODO: delete bsb watchers
969
+ if (pullConfigurationPeriodically != null) {
970
+ clearInterval(pullConfigurationPeriodically);
971
+ }
972
+ let response = {
973
+ jsonrpc: c.jsonrpcVersion,
974
+ id: msg.id,
975
+ result: null,
976
+ };
977
+ send(response);
978
+ }
979
+ }
980
+ else if (msg.method === p.HoverRequest.method) {
981
+ send(hover(msg));
982
+ }
983
+ else if (msg.method === p.DefinitionRequest.method) {
984
+ send(definition(msg));
985
+ }
986
+ else if (msg.method === p.TypeDefinitionRequest.method) {
987
+ send(typeDefinition(msg));
988
+ }
989
+ else if (msg.method === p.ReferencesRequest.method) {
990
+ send(references(msg));
991
+ }
992
+ else if (msg.method === p.PrepareRenameRequest.method) {
993
+ send(prepareRename(msg));
994
+ }
995
+ else if (msg.method === p.RenameRequest.method) {
996
+ send(rename(msg));
997
+ }
998
+ else if (msg.method === p.DocumentSymbolRequest.method) {
999
+ send(documentSymbol(msg));
1000
+ }
1001
+ else if (msg.method === p.CompletionRequest.method) {
1002
+ send(completion(msg));
1003
+ }
1004
+ else if (msg.method === p.SemanticTokensRequest.method) {
1005
+ send(semanticTokens(msg));
1006
+ }
1007
+ else if (msg.method === p.CodeActionRequest.method) {
1008
+ send(codeAction(msg));
1009
+ }
1010
+ else if (msg.method === p.DocumentFormattingRequest.method) {
1011
+ let responses = format(msg);
1012
+ responses.forEach((response) => send(response));
1013
+ }
1014
+ else if (msg.method === createInterfaceRequest.method) {
1015
+ send(createInterface(msg));
1016
+ }
1017
+ else if (msg.method === openCompiledFileRequest.method) {
1018
+ send(openCompiledFile(msg));
1019
+ }
1020
+ else if (msg.method === p.InlayHintRequest.method) {
1021
+ let params = msg.params;
1022
+ let extName = path.extname(params.textDocument.uri);
1023
+ if (extName === c.resExt) {
1024
+ send(inlayHint(msg));
1025
+ }
1026
+ }
1027
+ else if (msg.method === p.CodeLensRequest.method) {
1028
+ let params = msg.params;
1029
+ let extName = path.extname(params.textDocument.uri);
1030
+ if (extName === c.resExt) {
1031
+ send(codeLens(msg));
1032
+ }
1033
+ }
1034
+ else if (msg.method === p.SignatureHelpRequest.method) {
1035
+ let params = msg.params;
1036
+ let extName = path.extname(params.textDocument.uri);
1037
+ if (extName === c.resExt) {
1038
+ send(signatureHelp(msg));
1039
+ }
1040
+ }
1041
+ else {
1042
+ let response = {
1043
+ jsonrpc: c.jsonrpcVersion,
1044
+ id: msg.id,
1045
+ error: {
1046
+ code: p.ErrorCodes.InvalidRequest,
1047
+ message: "Unrecognized editor request.",
1048
+ },
1049
+ };
1050
+ send(response);
1051
+ }
1052
+ }
1053
+ else if (p.Message.isResponse(msg)) {
1054
+ if (msg.id === c.configurationRequestId) {
1055
+ if (msg.result != null) {
1056
+ // This is a response from a request to get updated configuration. Note
1057
+ // that it seems to return the configuration in a way that lets the
1058
+ // current workspace settings override the user settings. This is good
1059
+ // as we get started, but _might_ be problematic further down the line
1060
+ // if we want to support having several projects open at the same time
1061
+ // without their settings overriding eachother. Not a problem now though
1062
+ // as we'll likely only have "global" settings starting out.
1063
+ let [configuration] = msg.result;
1064
+ if (configuration != null) {
1065
+ extensionConfiguration = configuration;
1066
+ }
1067
+ }
1068
+ }
1069
+ else if (msg.result != null &&
1070
+ // @ts-ignore
1071
+ msg.result.title != null &&
1072
+ // @ts-ignore
1073
+ msg.result.title === c.startBuildAction) {
1074
+ let msg_ = msg.result;
1075
+ let projectRootPath = msg_.projectRootPath;
1076
+ // TODO: sometime stale .bsb.lock dangling
1077
+ // TODO: close watcher when lang-server shuts down. However, by Node's
1078
+ // default, these subprocesses are automatically killed when this
1079
+ // language-server process exits
1080
+ let rescriptBinaryPath = findRescriptBinary(projectRootPath);
1081
+ if (rescriptBinaryPath != null) {
1082
+ let bsbProcess = utils.runBuildWatcherUsingValidBuildPath(rescriptBinaryPath, projectRootPath);
1083
+ let root = projectsFiles.get(projectRootPath);
1084
+ root.bsbWatcherByEditor = bsbProcess;
1085
+ // bsbProcess.on("message", (a) => console.log(a));
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ //# sourceMappingURL=server.js.map