stan-language-server 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -13
- package/dist/handlers/compilation/compilation.d.ts +2 -1
- package/dist/handlers/compilation/includes.d.ts +2 -2
- package/dist/handlers/diagnostics.d.ts +1 -1
- package/dist/handlers/hover.d.ts +3 -6
- package/dist/language/diagnostics/provider.d.ts +7 -3
- package/dist/server/index.js +103 -125
- package/dist/types/common.d.ts +0 -13
- package/dist/types/index.d.ts +0 -1
- package/package.json +3 -3
- package/dist/language/diagnostics/index.d.ts +0 -2
- package/dist/language/diagnostics/linter.d.ts +0 -5
- package/dist/types/diagnostics.d.ts +0 -18
- /package/dist/__tests__/language/diagnostics/{linter.test.d.ts → provider.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -10,28 +10,80 @@ A language server for the Stan probabilistic programming language written in Typ
|
|
|
10
10
|
- **Code formatting**: Using the official Stan compiler
|
|
11
11
|
- **Include file support**: Full `#include` resolution and compilation
|
|
12
12
|
|
|
13
|
-
To install dependencies:
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
bun install
|
|
17
|
-
```
|
|
14
|
+
## Editor-specific configuration
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
### VSCode:
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
Install the [extension](https://github.com/WardBrian/vscode-stan-extension)
|
|
19
|
+
from [Marketplace](https://marketplace.visualstudio.com/items?itemName=wardbrian.vscode-stan-extension)
|
|
20
|
+
or [open-vsx](https://open-vsx.org/extension/wardbrian/vscode-stan-extension).
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
### Neovim
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
There are many ways to install language servers in neovim. Here is one (if you have a different or better way, consider contributing it!):
|
|
25
|
+
|
|
26
|
+
Download the latest language server executable from [GitHub](https://github.com/tomatitito/stan-language-server/tags) and put it somewhere in your PATH. Then in your `nvim` config folder add `lua/lsp/init.lua` with:
|
|
27
|
+
```lua
|
|
28
|
+
local servers = {
|
|
29
|
+
stan_ls = "lsp.stan",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
local function setup_server(name, config_module)
|
|
33
|
+
local config = require(config_module)
|
|
34
|
+
|
|
35
|
+
vim.api.nvim_create_autocmd("FileType", {
|
|
36
|
+
pattern = config.filetypes,
|
|
37
|
+
callback = function()
|
|
38
|
+
if #vim.lsp.get_clients({ bufnr = 0, name = name }) > 0 then
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
local root_dir = vim.fs.root(0, config.root_markers)
|
|
43
|
+
print(string.format("Starting %s for buffer %d with root: %s", name, vim.api.nvim_get_current_buf(),
|
|
44
|
+
root_dir or "none"))
|
|
45
|
+
|
|
46
|
+
vim.lsp.start({
|
|
47
|
+
name = name,
|
|
48
|
+
cmd = config.cmd,
|
|
49
|
+
root_dir = root_dir,
|
|
50
|
+
initialization_options = config.settings or {},
|
|
51
|
+
on_exit = function(code, signal)
|
|
52
|
+
print(string.format("%s exited with code %d, signal %d", name, code, signal))
|
|
53
|
+
end,
|
|
54
|
+
})
|
|
55
|
+
end,
|
|
56
|
+
})
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
for server_name, config_path in pairs(servers) do
|
|
60
|
+
setup_server(server_name, config_path)
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then in `lua/lsp/stan.lua` add the following:
|
|
65
|
+
```lua
|
|
66
|
+
return {
|
|
67
|
+
cmd = { "stan-language-server", "--stdio" },
|
|
68
|
+
filetypes = { "stan" },
|
|
69
|
+
root_markers = { ".git" },
|
|
70
|
+
settings = {
|
|
71
|
+
maxLineLength = 78,
|
|
72
|
+
includePaths = {},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
29
75
|
```
|
|
30
76
|
|
|
31
|
-
|
|
77
|
+
### Zed
|
|
78
|
+
|
|
79
|
+
Install the [Stan extension](https://zed.dev/extensions/stan).
|
|
32
80
|
|
|
33
81
|
### Sublime Text 4
|
|
34
82
|
|
|
83
|
+
Using [LSP for Sublime Text](https://lsp.sublimetext.io/),
|
|
84
|
+
[download the latest release](https://github.com/tomatitito/stan-language-server/releases)
|
|
85
|
+
and add the following to the settings file:
|
|
86
|
+
|
|
35
87
|
```json
|
|
36
88
|
{
|
|
37
89
|
"clients": {
|
|
@@ -50,10 +102,41 @@ bun build server.ts --compile --outfile stan-language-server
|
|
|
50
102
|
|
|
51
103
|
### Emacs (eglot)
|
|
52
104
|
|
|
53
|
-
Assuming you are using stan-ts-mode
|
|
105
|
+
Assuming you are using [stan-ts-mode](https://github.com/WardBrian/stan-ts-mode),
|
|
106
|
+
[download the latest release](https://github.com/tomatitito/stan-language-server/releases)
|
|
107
|
+
and add the following to your `init.el`:
|
|
54
108
|
|
|
55
109
|
```elisp
|
|
110
|
+
; elgot is built in to emacs 29+, but a similar config would work for lsp-mode
|
|
56
111
|
(with-eval-after-load 'eglot
|
|
57
112
|
(add-to-list 'eglot-server-programs
|
|
58
113
|
'(stan-ts-mode . ("/YOUR/PATH/TO/stan-language-server" "--stdio"))))
|
|
59
114
|
```
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## For developers
|
|
118
|
+
|
|
119
|
+
Development uses [bun](https://bun.sh/)
|
|
120
|
+
|
|
121
|
+
To install dependencies:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bun install
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Building a binary executable:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bun build:binary
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
To run unit tests:
|
|
134
|
+
```bash
|
|
135
|
+
bun test
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To run end-to-end tests:
|
|
139
|
+
```bash
|
|
140
|
+
pip install pytest pytest-lsp
|
|
141
|
+
bun build:binary && pytest tests/
|
|
142
|
+
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
2
2
|
import { type TextDocuments, type WorkspaceFolder, type RemoteConsole } from "vscode-languageserver";
|
|
3
|
-
import type { FileSystemReader
|
|
3
|
+
import type { FileSystemReader } from "../../types/common";
|
|
4
|
+
import { type StancReturn } from "stanc3";
|
|
4
5
|
export interface Settings {
|
|
5
6
|
maxLineLength: number;
|
|
6
7
|
includePaths: string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TextDocuments, WorkspaceFolder, type RemoteConsole } from "vscode-languageserver";
|
|
2
|
-
import
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
3
|
import type { FileSystemReader } from "../../types";
|
|
4
4
|
export type Filename = string;
|
|
5
5
|
export type FileContent = string;
|
|
@@ -8,4 +8,4 @@ export type FilePathError = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function getFilenames(fileContent: string): Filename[];
|
|
10
10
|
export declare function isFilePathError(value: unknown): value is FilePathError;
|
|
11
|
-
export declare function handleIncludes(document: TextDocument, documentManager: TextDocuments<TextDocument>, workspaceFolders: WorkspaceFolder[], includePaths: string[], logger: RemoteConsole, reader?: FileSystemReader): Promise<Record<Filename, FileContent>>;
|
|
11
|
+
export declare function handleIncludes(document: TextDocument, documentManager: TextDocuments<TextDocument>, workspaceFolders: WorkspaceFolder[], includePaths: string[], logger: RemoteConsole, reader?: FileSystemReader, alreadyIncluded?: Set<Filename>): Promise<Record<Filename, FileContent>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Diagnostic, TextDocuments, WorkspaceFolder, type DocumentDiagnosticParams, type RemoteConsole } from "vscode-languageserver";
|
|
2
2
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
-
import { type Settings } from "./compilation/compilation";
|
|
4
3
|
import type { FileSystemReader } from "../types";
|
|
4
|
+
import { type Settings } from "./compilation/compilation";
|
|
5
5
|
export declare function handleDiagnostics(params: DocumentDiagnosticParams, documents: TextDocuments<TextDocument>, workspaceFolders: WorkspaceFolder[], settings: Settings, logger: RemoteConsole, reader?: FileSystemReader): Promise<Diagnostic[]>;
|
package/dist/handlers/hover.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { HoverParams, Hover
|
|
1
|
+
import type { HoverParams, Hover } from "vscode-languageserver";
|
|
2
2
|
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
3
|
export declare const manual_functions: string[];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare const handleHover: (getMarkupLookupFn: GetMarkupLookupMapFn) => (document: TextDocument, params: HoverParams) => Promise<Hover | null>;
|
|
7
|
-
declare const _default: (document: TextDocument, params: HoverParams) => Promise<Hover | null>;
|
|
8
|
-
export default _default;
|
|
4
|
+
declare function handleHover(document: TextDocument, params: HoverParams): Promise<Hover | null>;
|
|
5
|
+
export default handleHover;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
export declare function provideDiagnostics(compilerResult: StancReturn):
|
|
1
|
+
import type { StancReturn } from "stanc3";
|
|
2
|
+
import type { Range } from "vscode-languageserver";
|
|
3
|
+
export declare function provideDiagnostics(compilerResult: StancReturn): {
|
|
4
|
+
range: Range;
|
|
5
|
+
severity: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}[];
|
package/dist/server/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/server/index.ts
|
|
2
|
-
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
2
|
+
import { TextDocument as TextDocument2 } from "vscode-languageserver-textdocument";
|
|
3
3
|
import {
|
|
4
4
|
DiagnosticRefreshRequest,
|
|
5
5
|
DidChangeConfigurationNotification,
|
|
6
6
|
DocumentDiagnosticRequest,
|
|
7
|
+
MessageType,
|
|
7
8
|
TextDocumentSyncKind,
|
|
8
9
|
TextDocuments as TextDocuments3
|
|
9
10
|
} from "vscode-languageserver/node";
|
|
@@ -217,6 +218,7 @@ var setupDistributionMap = () => {
|
|
|
217
218
|
}
|
|
218
219
|
return distributionToFunctionMap;
|
|
219
220
|
};
|
|
221
|
+
var DISTRIBUTION_FUNCTION_MAP = setupDistributionMap();
|
|
220
222
|
var tildeBefore = (text, pos) => {
|
|
221
223
|
for (let i = pos - 1;i >= 0; i--) {
|
|
222
224
|
const char = text[i];
|
|
@@ -233,9 +235,8 @@ var tildeBefore = (text, pos) => {
|
|
|
233
235
|
var tryDistributionHover = (text, beginningOfWord, endOfWord) => {
|
|
234
236
|
if (!tildeBefore(text, beginningOfWord))
|
|
235
237
|
return null;
|
|
236
|
-
const distributionToFunctionMap = setupDistributionMap();
|
|
237
238
|
const dist = text.substring(beginningOfWord, endOfWord).trim();
|
|
238
|
-
const functionName =
|
|
239
|
+
const functionName = DISTRIBUTION_FUNCTION_MAP.get(dist);
|
|
239
240
|
if (!functionName)
|
|
240
241
|
return null;
|
|
241
242
|
return functionName;
|
|
@@ -325,6 +326,7 @@ var initializeFunctionMarkupMap = () => {
|
|
|
325
326
|
}
|
|
326
327
|
return markupLookupMap;
|
|
327
328
|
};
|
|
329
|
+
var FUNCTION_MARKUP_LOOKUP = initializeFunctionMarkupMap();
|
|
328
330
|
function markupContentToHover(content, document, beginningOfWord, endOfWord) {
|
|
329
331
|
return {
|
|
330
332
|
contents: content,
|
|
@@ -334,15 +336,7 @@ function markupContentToHover(content, document, beginningOfWord, endOfWord) {
|
|
|
334
336
|
}
|
|
335
337
|
};
|
|
336
338
|
}
|
|
337
|
-
|
|
338
|
-
const functionMarkupLookup = getMarkupLookupFn();
|
|
339
|
-
const currentLine = document.getText({
|
|
340
|
-
start: { line: params.position.line, character: 0 },
|
|
341
|
-
end: { line: params.position.line + 1, character: 0 }
|
|
342
|
-
}).trim();
|
|
343
|
-
if (!currentLine || !currentLine.includes("(")) {
|
|
344
|
-
return null;
|
|
345
|
-
}
|
|
339
|
+
async function handleHover(document, params) {
|
|
346
340
|
const text = document.getText();
|
|
347
341
|
const offset = document.offsetAt(params.position);
|
|
348
342
|
if (!isWordChar(text[offset])) {
|
|
@@ -355,14 +349,14 @@ var handleHover = (getMarkupLookupFn) => async (document, params) => {
|
|
|
355
349
|
const beginningOfWord = previousWordBoundary(text, offset);
|
|
356
350
|
const hoverName = provideHover(text, beginningOfWord, nextParen);
|
|
357
351
|
if (hoverName) {
|
|
358
|
-
const hoverContent =
|
|
352
|
+
const hoverContent = FUNCTION_MARKUP_LOOKUP.get(hoverName);
|
|
359
353
|
if (hoverContent !== undefined) {
|
|
360
354
|
return markupContentToHover(hoverContent, document, beginningOfWord, nextParen);
|
|
361
355
|
}
|
|
362
356
|
}
|
|
363
357
|
return null;
|
|
364
|
-
}
|
|
365
|
-
var hover_default = handleHover
|
|
358
|
+
}
|
|
359
|
+
var hover_default = handleHover;
|
|
366
360
|
|
|
367
361
|
// src/language/completion/providers/functions.ts
|
|
368
362
|
var provideFunctionCompletions = (text, position, functionSignatures) => {
|
|
@@ -457,31 +451,21 @@ function constraintToCompletionItem(constraint) {
|
|
|
457
451
|
kind: CompletionItemKind.Property
|
|
458
452
|
};
|
|
459
453
|
}
|
|
460
|
-
|
|
461
|
-
return {
|
|
462
|
-
line: position.line,
|
|
463
|
-
character: position.character
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function getDistributionData() {
|
|
467
|
-
return dump_stan_math_distributions2().split(`
|
|
454
|
+
var DISTRIBUTION_DATA = dump_stan_math_distributions2().split(`
|
|
468
455
|
`).map((line) => line.split(":")[0]?.trim() ?? "").filter((name) => name !== "");
|
|
469
|
-
|
|
470
|
-
function getFunctionData() {
|
|
471
|
-
return dump_stan_math_signatures2().split(`
|
|
456
|
+
var FUNCTION_DATA = dump_stan_math_signatures2().split(`
|
|
472
457
|
`);
|
|
473
|
-
}
|
|
474
458
|
function handleCompletion(params, documents) {
|
|
475
459
|
const document = documents.get(params.textDocument.uri);
|
|
476
460
|
if (!document) {
|
|
477
461
|
return [];
|
|
478
462
|
}
|
|
479
463
|
const text = document.getText();
|
|
480
|
-
const position =
|
|
464
|
+
const position = params.position;
|
|
481
465
|
const keywords = provideKeywordCompletions(text, position);
|
|
482
|
-
const distributions = provideDistributionCompletions(text, position,
|
|
466
|
+
const distributions = provideDistributionCompletions(text, position, DISTRIBUTION_DATA);
|
|
483
467
|
const datatypes = provideDatatypeCompletions(text, position);
|
|
484
|
-
const functions = provideFunctionCompletions(text, position,
|
|
468
|
+
const functions = provideFunctionCompletions(text, position, FUNCTION_DATA);
|
|
485
469
|
const constraints = provideConstraintCompletions(text, position);
|
|
486
470
|
const allItems = [
|
|
487
471
|
...keywords.map(keywordToCompletionItem),
|
|
@@ -494,14 +478,18 @@ function handleCompletion(params, documents) {
|
|
|
494
478
|
}
|
|
495
479
|
// src/handlers/diagnostics.ts
|
|
496
480
|
import {
|
|
497
|
-
DiagnosticSeverity
|
|
481
|
+
DiagnosticSeverity
|
|
498
482
|
} from "vscode-languageserver";
|
|
499
483
|
|
|
500
|
-
// src/
|
|
501
|
-
|
|
502
|
-
|
|
484
|
+
// src/constants/index.ts
|
|
485
|
+
var SERVER_ID = "stan-language-server";
|
|
486
|
+
|
|
487
|
+
// src/language/diagnostics/provider.ts
|
|
488
|
+
function getRangeFromMessage(message) {
|
|
489
|
+
const msg = String(message);
|
|
490
|
+
if (!msg)
|
|
503
491
|
return;
|
|
504
|
-
const start =
|
|
492
|
+
const start = msg.matchAll(/'.*', line (\d+), column (\d+)( to)?/g);
|
|
505
493
|
const lastMatch = Array.from(start).pop();
|
|
506
494
|
if (!lastMatch || !lastMatch[1] || !lastMatch[2]) {
|
|
507
495
|
return;
|
|
@@ -511,7 +499,7 @@ function rangeFromMessage(message) {
|
|
|
511
499
|
let endLine = startLine;
|
|
512
500
|
let endColumn = startColumn;
|
|
513
501
|
if (lastMatch[3]) {
|
|
514
|
-
const end =
|
|
502
|
+
const end = msg.match(/to (line (\d+), )?column (\d+)/);
|
|
515
503
|
if (end && end[3]) {
|
|
516
504
|
if (end[1] && end[2]) {
|
|
517
505
|
endLine = parseInt(end[2]) - 1;
|
|
@@ -525,64 +513,60 @@ function rangeFromMessage(message) {
|
|
|
525
513
|
};
|
|
526
514
|
}
|
|
527
515
|
function getWarningMessage(message) {
|
|
528
|
-
|
|
516
|
+
const msg = String(message);
|
|
517
|
+
let warning = msg.replace(/Warning.*column \d+: /s, "");
|
|
529
518
|
warning = warning.replace(/\s+/gs, " ");
|
|
530
519
|
warning = warning.trim();
|
|
531
|
-
warning =
|
|
520
|
+
warning = msg.includes("included from") ? `Warning in included file:
|
|
532
521
|
` + warning : warning;
|
|
533
522
|
return warning;
|
|
534
523
|
}
|
|
535
524
|
function getErrorMessage(message) {
|
|
536
|
-
|
|
537
|
-
|
|
525
|
+
const msg = String(message);
|
|
526
|
+
let error = msg;
|
|
527
|
+
if (msg.includes(`------
|
|
538
528
|
`)) {
|
|
539
529
|
error = error.split(`------
|
|
540
530
|
`)[2] ?? error;
|
|
541
531
|
}
|
|
542
532
|
error = error.trim();
|
|
543
|
-
error =
|
|
533
|
+
error = msg.includes("included from") ? `Error in included file:
|
|
544
534
|
` + error : error;
|
|
545
535
|
error = error.includes("given information about") ? error + `
|
|
546
536
|
Try opening the included file and making the Stan language server aware of it.` : error;
|
|
547
537
|
return error;
|
|
548
538
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
539
|
+
function provideErrorMessageAndRange(message) {
|
|
540
|
+
return { range: getRangeFromMessage(message), message: getErrorMessage(message) };
|
|
541
|
+
}
|
|
542
|
+
function provideWarningMessageAndRange(message) {
|
|
543
|
+
return { range: getRangeFromMessage(message), message: getWarningMessage(message) };
|
|
544
|
+
}
|
|
554
545
|
function provideDiagnostics(compilerResult) {
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
range,
|
|
575
|
-
severity: 2 /* Warning */,
|
|
576
|
-
message: getWarningMessage(warning),
|
|
577
|
-
source: SERVER_ID
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
return diagnostics;
|
|
546
|
+
const errorDiagnostics = compilerResult.errors?.map((msg) => {
|
|
547
|
+
return provideErrorMessageAndRange(msg);
|
|
548
|
+
}).filter((item) => item.range !== undefined && item.message !== undefined).map(({ range, message }) => {
|
|
549
|
+
return {
|
|
550
|
+
range,
|
|
551
|
+
severity: "error",
|
|
552
|
+
message
|
|
553
|
+
};
|
|
554
|
+
}) ?? [];
|
|
555
|
+
const warningDiagnostics = compilerResult.warnings?.map((msg) => {
|
|
556
|
+
return provideWarningMessageAndRange(msg);
|
|
557
|
+
}).filter((item) => item.range !== undefined).map(({ range, message }) => {
|
|
558
|
+
return {
|
|
559
|
+
range,
|
|
560
|
+
severity: "warning",
|
|
561
|
+
message
|
|
562
|
+
};
|
|
563
|
+
}) ?? [];
|
|
564
|
+
return [...errorDiagnostics, ...warningDiagnostics];
|
|
583
565
|
}
|
|
566
|
+
|
|
584
567
|
// src/handlers/compilation/includes.ts
|
|
585
568
|
import { join } from "path";
|
|
569
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
586
570
|
import { URI, Utils } from "vscode-uri";
|
|
587
571
|
function getFilenames(fileContent) {
|
|
588
572
|
const includePattern = /#include\s*[<"]?([^>"\s]*)[>"]?/g;
|
|
@@ -593,13 +577,16 @@ function getFilenames(fileContent) {
|
|
|
593
577
|
function isFilePathError(value) {
|
|
594
578
|
return typeof value === "object" && value !== null && "msg" in value;
|
|
595
579
|
}
|
|
596
|
-
async function handleIncludes(document, documentManager, workspaceFolders, includePaths, logger, reader) {
|
|
580
|
+
async function handleIncludes(document, documentManager, workspaceFolders, includePaths, logger, reader, alreadyIncluded = new Set) {
|
|
597
581
|
try {
|
|
598
582
|
const includeFilenames = getFilenames(document.getText());
|
|
599
583
|
if (includeFilenames.length === 0) {
|
|
600
584
|
return {};
|
|
601
585
|
}
|
|
602
586
|
const allResults = await Promise.all(includeFilenames.map(async (filename) => {
|
|
587
|
+
if (alreadyIncluded.has(filename)) {
|
|
588
|
+
return [filename, { msg: `File already included: ${filename}` }];
|
|
589
|
+
}
|
|
603
590
|
try {
|
|
604
591
|
const content = await readIncludedFile(document, documentManager, workspaceFolders, includePaths, filename, reader);
|
|
605
592
|
return [filename, content];
|
|
@@ -608,7 +595,10 @@ async function handleIncludes(document, documentManager, workspaceFolders, inclu
|
|
|
608
595
|
}
|
|
609
596
|
}));
|
|
610
597
|
const validResults = allResults.filter(([_, content]) => !isFilePathError(content));
|
|
611
|
-
|
|
598
|
+
const currentlyIncluded = new Set(validResults.map(([filename, _]) => filename)).union(alreadyIncluded);
|
|
599
|
+
const recursiveIncludes = await Promise.all(validResults.map(async ([_, content]) => await handleIncludes(content, documentManager, workspaceFolders, includePaths, logger, reader, currentlyIncluded)));
|
|
600
|
+
const results = validResults.map(([filename, contents]) => [filename, contents.getText()]).concat(recursiveIncludes.map(Object.entries).flat());
|
|
601
|
+
return Object.fromEntries(results);
|
|
612
602
|
} catch (error) {
|
|
613
603
|
logger.warn(`Resolving included files failed: ${error}`);
|
|
614
604
|
return Promise.resolve({});
|
|
@@ -621,10 +611,7 @@ var readIncludedFile = async (document, documentManager, workspaceFolders, inclu
|
|
|
621
611
|
return Promise.resolve(includedFileContent);
|
|
622
612
|
}
|
|
623
613
|
if (reader) {
|
|
624
|
-
includedFileContent = await readIncludedFileFromFileSystem(filename, [
|
|
625
|
-
currentDir.fsPath,
|
|
626
|
-
...includePaths
|
|
627
|
-
], reader);
|
|
614
|
+
includedFileContent = await readIncludedFileFromFileSystem(filename, [currentDir.fsPath, ...includePaths], reader);
|
|
628
615
|
}
|
|
629
616
|
if (!isFilePathError(includedFileContent)) {
|
|
630
617
|
return Promise.resolve(includedFileContent);
|
|
@@ -645,13 +632,14 @@ var readIncludedFileFromWorkspace = (documentManager, workspaceFolders, filename
|
|
|
645
632
|
if (!includedFile) {
|
|
646
633
|
return Promise.resolve({ msg: `File not found: ${filename}` });
|
|
647
634
|
}
|
|
648
|
-
return Promise.resolve(includedFile
|
|
635
|
+
return Promise.resolve(includedFile);
|
|
649
636
|
};
|
|
650
637
|
var readIncludedFileFromFileSystem = async (filename, dirs, fileSystemReader) => {
|
|
651
638
|
for (const currentDir of dirs) {
|
|
652
639
|
try {
|
|
653
640
|
const localPath = join(currentDir, filename);
|
|
654
|
-
|
|
641
|
+
const content = await fileSystemReader(localPath);
|
|
642
|
+
return TextDocument.create(URI.file(localPath).toString(), "stan", 0, content);
|
|
655
643
|
} catch (error) {}
|
|
656
644
|
}
|
|
657
645
|
return Promise.resolve({ msg: `File not found: ${filename}` });
|
|
@@ -682,48 +670,30 @@ async function handleCompilation(document, documentManager, workspaceFolders, se
|
|
|
682
670
|
}
|
|
683
671
|
|
|
684
672
|
// src/handlers/diagnostics.ts
|
|
685
|
-
function stanDiagnosticToLspDiagnostic(stanDiag) {
|
|
686
|
-
return {
|
|
687
|
-
range: domainRangeToLspRange(stanDiag.range),
|
|
688
|
-
severity: domainSeverityToLspSeverity(stanDiag.severity),
|
|
689
|
-
message: stanDiag.message,
|
|
690
|
-
source: stanDiag.source ?? SERVER_ID
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function domainRangeToLspRange(domainRange) {
|
|
694
|
-
return {
|
|
695
|
-
start: {
|
|
696
|
-
line: domainRange.start.line,
|
|
697
|
-
character: domainRange.start.character
|
|
698
|
-
},
|
|
699
|
-
end: {
|
|
700
|
-
line: domainRange.end.line,
|
|
701
|
-
character: domainRange.end.character
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
function domainSeverityToLspSeverity(domainSeverity) {
|
|
706
|
-
switch (domainSeverity) {
|
|
707
|
-
case 1:
|
|
708
|
-
return DiagnosticSeverity2.Error;
|
|
709
|
-
case 2:
|
|
710
|
-
return DiagnosticSeverity2.Warning;
|
|
711
|
-
case 3:
|
|
712
|
-
return DiagnosticSeverity2.Information;
|
|
713
|
-
case 4:
|
|
714
|
-
return DiagnosticSeverity2.Hint;
|
|
715
|
-
default:
|
|
716
|
-
return DiagnosticSeverity2.Error;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
673
|
async function handleDiagnostics(params, documents, workspaceFolders, settings, logger, reader) {
|
|
720
674
|
const document = documents.get(params.textDocument.uri);
|
|
721
675
|
if (!document) {
|
|
722
676
|
return [];
|
|
723
677
|
}
|
|
724
678
|
const compilerResult = await handleCompilation(document, documents, workspaceFolders, settings, logger, reader);
|
|
725
|
-
const
|
|
726
|
-
|
|
679
|
+
const diagnostics = provideDiagnostics(compilerResult).map((diagnostic) => {
|
|
680
|
+
if (diagnostic.severity === "error") {
|
|
681
|
+
return {
|
|
682
|
+
range: diagnostic.range,
|
|
683
|
+
severity: DiagnosticSeverity.Error,
|
|
684
|
+
message: diagnostic.message,
|
|
685
|
+
source: SERVER_ID
|
|
686
|
+
};
|
|
687
|
+
} else if (diagnostic.severity === "warning") {
|
|
688
|
+
return {
|
|
689
|
+
range: diagnostic.range,
|
|
690
|
+
severity: DiagnosticSeverity.Warning,
|
|
691
|
+
message: diagnostic.message,
|
|
692
|
+
source: SERVER_ID
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}).filter((diagnostic) => diagnostic !== undefined);
|
|
696
|
+
return diagnostics;
|
|
727
697
|
}
|
|
728
698
|
// src/handlers/formatting.ts
|
|
729
699
|
async function handleFormatting(params, documents, workspaceFolders, settings, logger, reader) {
|
|
@@ -755,11 +725,13 @@ async function handleFormatting(params, documents, workspaceFolders, settings, l
|
|
|
755
725
|
var startLanguageServer = (connection, reader) => {
|
|
756
726
|
let hasConfigurationCapability = false;
|
|
757
727
|
let hasWorkspaceFolderCapability = false;
|
|
728
|
+
let hasDynamicConfigurationRequestCapability = false;
|
|
758
729
|
connection.onInitialize((params) => {
|
|
759
730
|
connection.console.info("Initializing Stan language server...");
|
|
760
731
|
let capabilities = params.capabilities;
|
|
761
|
-
hasConfigurationCapability =
|
|
762
|
-
|
|
732
|
+
hasConfigurationCapability = Boolean(capabilities.workspace?.configuration);
|
|
733
|
+
hasDynamicConfigurationRequestCapability = Boolean(capabilities.workspace?.configuration && capabilities.workspace?.didChangeConfiguration?.dynamicRegistration);
|
|
734
|
+
hasWorkspaceFolderCapability = Boolean(capabilities.workspace?.workspaceFolders);
|
|
763
735
|
return {
|
|
764
736
|
capabilities: {
|
|
765
737
|
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
@@ -768,6 +740,7 @@ var startLanguageServer = (connection, reader) => {
|
|
|
768
740
|
resolveProvider: false
|
|
769
741
|
},
|
|
770
742
|
documentFormattingProvider: true,
|
|
743
|
+
documentRangeFormattingProvider: false,
|
|
771
744
|
workspace: {
|
|
772
745
|
workspaceFolders: {
|
|
773
746
|
supported: hasWorkspaceFolderCapability
|
|
@@ -781,9 +754,10 @@ var startLanguageServer = (connection, reader) => {
|
|
|
781
754
|
}
|
|
782
755
|
};
|
|
783
756
|
});
|
|
784
|
-
connection.onInitialized(() => {
|
|
785
|
-
if (
|
|
786
|
-
connection.client.register(DidChangeConfigurationNotification.type);
|
|
757
|
+
connection.onInitialized(async () => {
|
|
758
|
+
if (hasDynamicConfigurationRequestCapability) {
|
|
759
|
+
await connection.client.register(DidChangeConfigurationNotification.type);
|
|
760
|
+
connection.console.info("Registered for didChangeConfiguration");
|
|
787
761
|
}
|
|
788
762
|
connection.console.info("Stan language server is initialized!");
|
|
789
763
|
});
|
|
@@ -793,7 +767,7 @@ var startLanguageServer = (connection, reader) => {
|
|
|
793
767
|
let globalSettings = defaultSettings;
|
|
794
768
|
let documentSettings = new Map;
|
|
795
769
|
connection.onDidChangeConfiguration((change) => {
|
|
796
|
-
if (
|
|
770
|
+
if (hasDynamicConfigurationRequestCapability) {
|
|
797
771
|
documentSettings.clear();
|
|
798
772
|
} else {
|
|
799
773
|
const incomingSettings = change.settings[SERVER_ID] || {};
|
|
@@ -817,7 +791,7 @@ var startLanguageServer = (connection, reader) => {
|
|
|
817
791
|
documentSettings.set(resource, docSettings);
|
|
818
792
|
return docSettings;
|
|
819
793
|
};
|
|
820
|
-
const documents = new TextDocuments3(
|
|
794
|
+
const documents = new TextDocuments3(TextDocument2);
|
|
821
795
|
connection.onCompletion((params) => {
|
|
822
796
|
return handleCompletion(params, documents);
|
|
823
797
|
});
|
|
@@ -846,6 +820,10 @@ var startLanguageServer = (connection, reader) => {
|
|
|
846
820
|
for (const error of formattingResult.errors) {
|
|
847
821
|
connection.console.error(error);
|
|
848
822
|
}
|
|
823
|
+
connection.sendNotification("window/showMessage", {
|
|
824
|
+
type: MessageType.Error,
|
|
825
|
+
message: "Formatting failed due to compile errors. See diagnostics for details."
|
|
826
|
+
});
|
|
849
827
|
return [];
|
|
850
828
|
}
|
|
851
829
|
});
|
package/dist/types/common.d.ts
CHANGED
|
@@ -3,17 +3,4 @@ export interface Position {
|
|
|
3
3
|
line: number;
|
|
4
4
|
character: number;
|
|
5
5
|
}
|
|
6
|
-
type StancSuccess = {
|
|
7
|
-
errors: undefined;
|
|
8
|
-
result: string;
|
|
9
|
-
warnings?: string[];
|
|
10
|
-
};
|
|
11
|
-
type StancFailure = {
|
|
12
|
-
errors: string[];
|
|
13
|
-
result: undefined;
|
|
14
|
-
warnings?: string[];
|
|
15
|
-
};
|
|
16
|
-
export type StancReturn = StancSuccess | StancFailure;
|
|
17
|
-
export type StancFunction = (filename: string, code: string, options: string[], includes?: Record<string, string>) => StancReturn;
|
|
18
6
|
export type FileSystemReader = (filename: Filename) => Promise<FileContent>;
|
|
19
|
-
export {};
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stan-language-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Language Server Protocol implementation for the Stan probabilistic programming language",
|
|
5
5
|
"main": "dist/server/index.js",
|
|
6
6
|
"module": "src/server/index.ts",
|
|
@@ -64,11 +64,11 @@
|
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/bun": "latest",
|
|
67
|
-
"@types/node": "24.
|
|
67
|
+
"@types/node": "24.8.1",
|
|
68
68
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
69
69
|
"@typescript-eslint/parser": "^8.0.0",
|
|
70
70
|
"eslint": "^9.0.0",
|
|
71
|
-
"typescript": "5.9.
|
|
71
|
+
"typescript": "5.9.3"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"stanc3": "^2.37.0",
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { Range } from "../../types/diagnostics";
|
|
2
|
-
declare function rangeFromMessage(message: string): Range | undefined;
|
|
3
|
-
declare function getWarningMessage(message: string): string;
|
|
4
|
-
declare function getErrorMessage(message: string): string;
|
|
5
|
-
export { rangeFromMessage, getWarningMessage, getErrorMessage };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Position } from "./common";
|
|
2
|
-
export type { Position };
|
|
3
|
-
export interface Range {
|
|
4
|
-
start: Position;
|
|
5
|
-
end: Position;
|
|
6
|
-
}
|
|
7
|
-
export declare enum DiagnosticSeverity {
|
|
8
|
-
Error = 1,
|
|
9
|
-
Warning = 2,
|
|
10
|
-
Information = 3,
|
|
11
|
-
Hint = 4
|
|
12
|
-
}
|
|
13
|
-
export interface StanDiagnostic {
|
|
14
|
-
range: Range;
|
|
15
|
-
severity: DiagnosticSeverity;
|
|
16
|
-
message: string;
|
|
17
|
-
source?: string;
|
|
18
|
-
}
|
|
File without changes
|