stan-language-server 0.1.0 → 0.2.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/README.md +15 -10
- package/dist/__tests__/handlers/compilation.test.d.ts +1 -0
- package/dist/__tests__/handlers/diagnostics.test.d.ts +1 -0
- package/dist/__tests__/handlers/includes.test.d.ts +1 -0
- package/dist/__tests__/language/completion/providers/constraints.test.d.ts +1 -0
- package/dist/__tests__/language/completion/providers/datatypes.test.d.ts +1 -0
- package/dist/__tests__/language/completion/providers/distributions.test.d.ts +1 -0
- package/dist/__tests__/language/completion/providers/functions.test.d.ts +1 -0
- package/dist/__tests__/language/completion/providers/keywords.test.d.ts +1 -0
- package/dist/__tests__/language/completion/util.test.d.ts +1 -0
- package/dist/__tests__/language/diagnostics/linter.test.d.ts +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/handlers/compilation/compilation.d.ts +9 -0
- package/dist/handlers/compilation/includes.d.ts +11 -0
- package/dist/handlers/completion.d.ts +3 -0
- package/dist/handlers/diagnostics.d.ts +5 -0
- package/dist/handlers/formatting.d.ts +7 -0
- package/dist/handlers/hover.d.ts +8 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/language/completion/providers/constraints.d.ts +3 -0
- package/dist/language/completion/providers/datatypes.d.ts +3 -0
- package/dist/language/completion/providers/distributions.d.ts +2 -0
- package/dist/language/completion/providers/functions.d.ts +2 -0
- package/dist/language/completion/providers/keywords.d.ts +2 -0
- package/dist/language/completion/util.d.ts +4 -0
- package/dist/language/diagnostics/index.d.ts +2 -0
- package/dist/language/diagnostics/linter.d.ts +5 -0
- package/dist/language/diagnostics/provider.d.ts +3 -0
- package/dist/language/hover/distributions.d.ts +1 -0
- package/dist/language/hover/functions.d.ts +1 -0
- package/dist/language/hover/index.d.ts +1 -0
- package/dist/language/hover/provider.d.ts +1 -0
- package/dist/language/hover/util.d.ts +4 -0
- package/dist/server/cli.d.ts +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +865 -0
- package/dist/types/common.d.ts +19 -0
- package/dist/types/completion.d.ts +15 -0
- package/dist/types/diagnostics.d.ts +18 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +19 -8
- package/dist/server.js +0 -89754
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
+
import {
|
|
4
|
+
DiagnosticRefreshRequest,
|
|
5
|
+
DidChangeConfigurationNotification,
|
|
6
|
+
DocumentDiagnosticRequest,
|
|
7
|
+
TextDocumentSyncKind,
|
|
8
|
+
TextDocuments as TextDocuments3
|
|
9
|
+
} from "vscode-languageserver/node";
|
|
10
|
+
|
|
11
|
+
// src/handlers/completion.ts
|
|
12
|
+
import {
|
|
13
|
+
CompletionItemKind
|
|
14
|
+
} from "vscode-languageserver";
|
|
15
|
+
|
|
16
|
+
// src/language/completion/util.ts
|
|
17
|
+
import TrieSearch from "trie-search";
|
|
18
|
+
var getSearchableItems = (xs, options = {}) => {
|
|
19
|
+
const searchableItem = new TrieSearch("name", options);
|
|
20
|
+
searchableItem.addAll(xs);
|
|
21
|
+
return searchableItem;
|
|
22
|
+
};
|
|
23
|
+
var getTextUpToCursor = (text, position) => {
|
|
24
|
+
const lines = text.split(`
|
|
25
|
+
`);
|
|
26
|
+
const currentLine = lines[position.line] || "";
|
|
27
|
+
return currentLine.substring(0, position.character);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/language/completion/providers/keywords.ts
|
|
31
|
+
var ALL_KEYWORDS = [
|
|
32
|
+
"for",
|
|
33
|
+
"in",
|
|
34
|
+
"while",
|
|
35
|
+
"repeat",
|
|
36
|
+
"until",
|
|
37
|
+
"if",
|
|
38
|
+
"then",
|
|
39
|
+
"else",
|
|
40
|
+
"break",
|
|
41
|
+
"continue",
|
|
42
|
+
"return",
|
|
43
|
+
"true",
|
|
44
|
+
"false",
|
|
45
|
+
"target",
|
|
46
|
+
"functions",
|
|
47
|
+
"data",
|
|
48
|
+
"transformed",
|
|
49
|
+
"parameters",
|
|
50
|
+
"model",
|
|
51
|
+
"generated",
|
|
52
|
+
"quantities",
|
|
53
|
+
"print",
|
|
54
|
+
"reject",
|
|
55
|
+
"fatal_error",
|
|
56
|
+
"profile",
|
|
57
|
+
"get_lp",
|
|
58
|
+
"struct",
|
|
59
|
+
"typedef",
|
|
60
|
+
"export",
|
|
61
|
+
"auto",
|
|
62
|
+
"extern",
|
|
63
|
+
"var",
|
|
64
|
+
"static",
|
|
65
|
+
"array",
|
|
66
|
+
"lower",
|
|
67
|
+
"upper",
|
|
68
|
+
"offset",
|
|
69
|
+
"multiplier",
|
|
70
|
+
"tuple",
|
|
71
|
+
"truncate",
|
|
72
|
+
"jacobian"
|
|
73
|
+
];
|
|
74
|
+
var getKeywords = () => {
|
|
75
|
+
return ALL_KEYWORDS.map((keyword) => ({
|
|
76
|
+
name: keyword
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
var provideKeywordCompletions = (text, position) => {
|
|
80
|
+
const textUpToCursor = getTextUpToCursor(text, position);
|
|
81
|
+
const keywords = getKeywords();
|
|
82
|
+
const searchableKeywords = getSearchableItems(keywords, {
|
|
83
|
+
splitOnRegEx: /[\s_]/g,
|
|
84
|
+
min: 0
|
|
85
|
+
});
|
|
86
|
+
const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
|
|
87
|
+
if (match) {
|
|
88
|
+
const keywordName = match[1] || "";
|
|
89
|
+
const completionProposals = searchableKeywords.search(keywordName);
|
|
90
|
+
return completionProposals;
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/language/completion/providers/distributions.ts
|
|
96
|
+
var provideDistributionCompletions = (text, position, distributions) => {
|
|
97
|
+
const textUpToCursor = getTextUpToCursor(text, position);
|
|
98
|
+
const distributionItems = distributions.filter((name) => name !== "").map((name) => ({ name }));
|
|
99
|
+
const searchableDistributions = getSearchableItems(distributionItems, {
|
|
100
|
+
splitOnRegEx: /[\s_]/g,
|
|
101
|
+
min: 0
|
|
102
|
+
});
|
|
103
|
+
const match = textUpToCursor.match(/.*~\s*([\w_]*)$/);
|
|
104
|
+
if (match) {
|
|
105
|
+
const distName = match[1] || "";
|
|
106
|
+
let completionProposals;
|
|
107
|
+
if (distName === "") {
|
|
108
|
+
completionProposals = distributionItems;
|
|
109
|
+
} else {
|
|
110
|
+
completionProposals = searchableDistributions.search(distName);
|
|
111
|
+
}
|
|
112
|
+
return completionProposals;
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/language/completion/providers/datatypes.ts
|
|
118
|
+
var DATATYPES = [
|
|
119
|
+
"void",
|
|
120
|
+
"int",
|
|
121
|
+
"real",
|
|
122
|
+
"complex",
|
|
123
|
+
"vector",
|
|
124
|
+
"row_vector",
|
|
125
|
+
"matrix",
|
|
126
|
+
"complex_vector",
|
|
127
|
+
"complex_row_vector",
|
|
128
|
+
"complex_matrix",
|
|
129
|
+
"ordered",
|
|
130
|
+
"positive_ordered",
|
|
131
|
+
"simplex",
|
|
132
|
+
"unit_vector",
|
|
133
|
+
"sum_to_zero_vector",
|
|
134
|
+
"cholesky_factor_corr",
|
|
135
|
+
"cholesky_factor_cov",
|
|
136
|
+
"corr_matrix",
|
|
137
|
+
"cov_matrix",
|
|
138
|
+
"stochastic_column_matrix",
|
|
139
|
+
"stochastic_row_matrix"
|
|
140
|
+
];
|
|
141
|
+
var getDatatypes = () => {
|
|
142
|
+
return DATATYPES.map((datatype) => ({
|
|
143
|
+
name: datatype
|
|
144
|
+
}));
|
|
145
|
+
};
|
|
146
|
+
var provideDatatypeCompletions = (text, position) => {
|
|
147
|
+
const textUpToCursor = getTextUpToCursor(text, position);
|
|
148
|
+
const datatypes = getDatatypes();
|
|
149
|
+
const searchableDatatypes = getSearchableItems(datatypes, {
|
|
150
|
+
splitOnRegEx: /[\s_]/g,
|
|
151
|
+
min: 0
|
|
152
|
+
});
|
|
153
|
+
const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
|
|
154
|
+
if (match) {
|
|
155
|
+
const typeName = match[1] || "";
|
|
156
|
+
const completionProposals = searchableDatatypes.search(typeName);
|
|
157
|
+
return completionProposals;
|
|
158
|
+
}
|
|
159
|
+
return [];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/handlers/hover.ts
|
|
163
|
+
import { dump_stan_math_signatures } from "stanc3";
|
|
164
|
+
|
|
165
|
+
// src/language/hover/util.ts
|
|
166
|
+
var isWordChar = (char) => {
|
|
167
|
+
const code = char.charCodeAt(0);
|
|
168
|
+
return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 95;
|
|
169
|
+
};
|
|
170
|
+
var isWhitespace = (char) => {
|
|
171
|
+
return char === " " || char === "\t" || char === `
|
|
172
|
+
` || char === "\r";
|
|
173
|
+
};
|
|
174
|
+
var previousWordBoundary = (text, pos) => {
|
|
175
|
+
for (let i = pos - 1;i >= 0; i--) {
|
|
176
|
+
const char = text[i];
|
|
177
|
+
if (!isWordChar(char)) {
|
|
178
|
+
return i + 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return 0;
|
|
182
|
+
};
|
|
183
|
+
var wordUntilNextParenthesis = (text, pos) => {
|
|
184
|
+
let seenSpace = false;
|
|
185
|
+
for (let i = pos;i < text.length; i++) {
|
|
186
|
+
const char = text[i];
|
|
187
|
+
if (char === "(") {
|
|
188
|
+
return i;
|
|
189
|
+
}
|
|
190
|
+
if (isWordChar(char)) {
|
|
191
|
+
if (seenSpace) {
|
|
192
|
+
return -1;
|
|
193
|
+
}
|
|
194
|
+
} else if (isWhitespace(char)) {
|
|
195
|
+
seenSpace = true;
|
|
196
|
+
} else {
|
|
197
|
+
return -1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return -1;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/language/hover/distributions.ts
|
|
204
|
+
import { dump_stan_math_distributions } from "stanc3";
|
|
205
|
+
var setupDistributionMap = () => {
|
|
206
|
+
const distributionToFunctionMap = new Map;
|
|
207
|
+
const mathDistributions = dump_stan_math_distributions();
|
|
208
|
+
const distLines = mathDistributions.split(`
|
|
209
|
+
`);
|
|
210
|
+
for (const line of distLines) {
|
|
211
|
+
const [name, extensions] = line.split(":", 2);
|
|
212
|
+
if (!name || !extensions) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const extension = extensions.split(",", 1)[0]?.trim();
|
|
216
|
+
distributionToFunctionMap.set(name, `${name}_${extension}`);
|
|
217
|
+
}
|
|
218
|
+
return distributionToFunctionMap;
|
|
219
|
+
};
|
|
220
|
+
var tildeBefore = (text, pos) => {
|
|
221
|
+
for (let i = pos - 1;i >= 0; i--) {
|
|
222
|
+
const char = text[i];
|
|
223
|
+
if (char === "~") {
|
|
224
|
+
return true;
|
|
225
|
+
} else if (isWhitespace(char)) {
|
|
226
|
+
continue;
|
|
227
|
+
} else {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
};
|
|
233
|
+
var tryDistributionHover = (text, beginningOfWord, endOfWord) => {
|
|
234
|
+
if (!tildeBefore(text, beginningOfWord))
|
|
235
|
+
return null;
|
|
236
|
+
const distributionToFunctionMap = setupDistributionMap();
|
|
237
|
+
const dist = text.substring(beginningOfWord, endOfWord).trim();
|
|
238
|
+
const functionName = distributionToFunctionMap.get(dist);
|
|
239
|
+
if (!functionName)
|
|
240
|
+
return null;
|
|
241
|
+
return functionName;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/language/hover/functions.ts
|
|
245
|
+
var tryFunctionHover = (text, beginningOfWord, endOfWord) => {
|
|
246
|
+
const funcName = text.substring(beginningOfWord, endOfWord).trim();
|
|
247
|
+
if (!funcName)
|
|
248
|
+
return null;
|
|
249
|
+
return funcName;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/language/hover/provider.ts
|
|
253
|
+
function provideHover(text, beginningOfWord, endOfWord) {
|
|
254
|
+
const distributionHover = tryDistributionHover(text, beginningOfWord, endOfWord);
|
|
255
|
+
if (distributionHover) {
|
|
256
|
+
return distributionHover;
|
|
257
|
+
}
|
|
258
|
+
const functionHover = tryFunctionHover(text, beginningOfWord, endOfWord);
|
|
259
|
+
if (functionHover) {
|
|
260
|
+
return functionHover;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
// src/handlers/hover.ts
|
|
265
|
+
var getDocumentationForFunction = (name) => {
|
|
266
|
+
return {
|
|
267
|
+
kind: "markdown",
|
|
268
|
+
value: `[Jump to Stan Functions Reference index entry for ${name}](https://mc-stan.org/docs/functions-reference/functions_index.html#${name})`
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
var appendCodeblock = (content, code) => {
|
|
272
|
+
if (!content) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
content.value += "\n```stan\n";
|
|
276
|
+
content.value += code;
|
|
277
|
+
content.value += "\n```";
|
|
278
|
+
};
|
|
279
|
+
var manual_functions = [
|
|
280
|
+
"print",
|
|
281
|
+
"reject",
|
|
282
|
+
"fatal_error",
|
|
283
|
+
"target",
|
|
284
|
+
"dae",
|
|
285
|
+
"dae_tol",
|
|
286
|
+
"ode_adams",
|
|
287
|
+
"ode_adams_tol",
|
|
288
|
+
"ode_adjoint_tol_ctl",
|
|
289
|
+
"ode_bdf",
|
|
290
|
+
"ode_bdf_tol",
|
|
291
|
+
"ode_ckrk",
|
|
292
|
+
"ode_ckrk_tol",
|
|
293
|
+
"ode_rk45",
|
|
294
|
+
"ode_rk45_tol",
|
|
295
|
+
"solve_newton",
|
|
296
|
+
"solve_newton_tol",
|
|
297
|
+
"solve_powell",
|
|
298
|
+
"solve_powell_tol",
|
|
299
|
+
"reduce_sum",
|
|
300
|
+
"reduce_sum_static"
|
|
301
|
+
];
|
|
302
|
+
var initializeFunctionMarkupMap = () => {
|
|
303
|
+
const markupLookupMap = new Map;
|
|
304
|
+
const mathSignatures = dump_stan_math_signatures();
|
|
305
|
+
const lines = mathSignatures.split(`
|
|
306
|
+
`);
|
|
307
|
+
for (const line of lines) {
|
|
308
|
+
const [name] = line.split("(", 1);
|
|
309
|
+
if (!name) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (markupLookupMap.has(name)) {
|
|
313
|
+
appendCodeblock(markupLookupMap.get(name), line);
|
|
314
|
+
} else {
|
|
315
|
+
const doc = getDocumentationForFunction(name);
|
|
316
|
+
doc.value += `
|
|
317
|
+
|
|
318
|
+
**Available signatures**:`;
|
|
319
|
+
appendCodeblock(doc, line);
|
|
320
|
+
markupLookupMap.set(name, doc);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const func of manual_functions) {
|
|
324
|
+
markupLookupMap.set(func, getDocumentationForFunction(func));
|
|
325
|
+
}
|
|
326
|
+
return markupLookupMap;
|
|
327
|
+
};
|
|
328
|
+
function markupContentToHover(content, document, beginningOfWord, endOfWord) {
|
|
329
|
+
return {
|
|
330
|
+
contents: content,
|
|
331
|
+
range: {
|
|
332
|
+
start: document.positionAt(beginningOfWord),
|
|
333
|
+
end: document.positionAt(endOfWord)
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
var handleHover = (getMarkupLookupFn) => async (document, params) => {
|
|
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
|
+
}
|
|
346
|
+
const text = document.getText();
|
|
347
|
+
const offset = document.offsetAt(params.position);
|
|
348
|
+
if (!isWordChar(text[offset])) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const nextParen = wordUntilNextParenthesis(text, offset);
|
|
352
|
+
if (nextParen === -1) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const beginningOfWord = previousWordBoundary(text, offset);
|
|
356
|
+
const hoverName = provideHover(text, beginningOfWord, nextParen);
|
|
357
|
+
if (hoverName) {
|
|
358
|
+
const hoverContent = functionMarkupLookup.get(hoverName);
|
|
359
|
+
if (hoverContent !== undefined) {
|
|
360
|
+
return markupContentToHover(hoverContent, document, beginningOfWord, nextParen);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
};
|
|
365
|
+
var hover_default = handleHover(initializeFunctionMarkupMap);
|
|
366
|
+
|
|
367
|
+
// src/language/completion/providers/functions.ts
|
|
368
|
+
var provideFunctionCompletions = (text, position, functionSignatures) => {
|
|
369
|
+
const textUpToCursor = getTextUpToCursor(text, position);
|
|
370
|
+
const functionNames = functionSignatures.map((line) => line.split("(", 1)[0]?.trim() ?? "").filter((name) => name !== "");
|
|
371
|
+
const allFunctionNames = [...new Set([...functionNames, ...manual_functions])];
|
|
372
|
+
const functionItems = allFunctionNames.map((name) => ({ name }));
|
|
373
|
+
const searchableFunctions = getSearchableItems(functionItems, {
|
|
374
|
+
splitOnRegEx: /[\s_]/g,
|
|
375
|
+
min: 0
|
|
376
|
+
});
|
|
377
|
+
const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
|
|
378
|
+
if (match) {
|
|
379
|
+
const functionName = match[1] || "";
|
|
380
|
+
const completionProposals = searchableFunctions.search(functionName);
|
|
381
|
+
return completionProposals;
|
|
382
|
+
}
|
|
383
|
+
return [];
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/language/completion/providers/constraints.ts
|
|
387
|
+
var CONSTRAINTS = [
|
|
388
|
+
"lower",
|
|
389
|
+
"upper",
|
|
390
|
+
"offset",
|
|
391
|
+
"multiplier",
|
|
392
|
+
"ordered",
|
|
393
|
+
"positive_ordered",
|
|
394
|
+
"simplex",
|
|
395
|
+
"unit_vector",
|
|
396
|
+
"sum_to_zero_vector",
|
|
397
|
+
"cholesky_factor_corr",
|
|
398
|
+
"cholesky_factor_cov",
|
|
399
|
+
"corr_matrix",
|
|
400
|
+
"cov_matrix",
|
|
401
|
+
"stochastic_column_matrix",
|
|
402
|
+
"stochastic_row_matrix"
|
|
403
|
+
];
|
|
404
|
+
var getConstraints = () => {
|
|
405
|
+
return CONSTRAINTS.map((constraint) => ({
|
|
406
|
+
name: constraint
|
|
407
|
+
}));
|
|
408
|
+
};
|
|
409
|
+
var provideConstraintCompletions = (text, position) => {
|
|
410
|
+
const textUpToCursor = getTextUpToCursor(text, position);
|
|
411
|
+
const constraints = getConstraints();
|
|
412
|
+
const searchableConstraints = getSearchableItems(constraints, {
|
|
413
|
+
splitOnRegEx: /[\s_]/g,
|
|
414
|
+
min: 0
|
|
415
|
+
});
|
|
416
|
+
const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
|
|
417
|
+
if (match) {
|
|
418
|
+
const constraintName = match[1] || "";
|
|
419
|
+
const completionProposals = searchableConstraints.search(constraintName);
|
|
420
|
+
return completionProposals;
|
|
421
|
+
}
|
|
422
|
+
return [];
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/handlers/completion.ts
|
|
426
|
+
import {
|
|
427
|
+
dump_stan_math_distributions as dump_stan_math_distributions2,
|
|
428
|
+
dump_stan_math_signatures as dump_stan_math_signatures2
|
|
429
|
+
} from "stanc3";
|
|
430
|
+
function keywordToCompletionItem(keyword) {
|
|
431
|
+
return {
|
|
432
|
+
label: keyword.name,
|
|
433
|
+
kind: CompletionItemKind.Keyword
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function distributionToCompletionItem(distribution) {
|
|
437
|
+
return {
|
|
438
|
+
label: distribution.name,
|
|
439
|
+
kind: CompletionItemKind.Function
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function datatypeToCompletionItem(datatype) {
|
|
443
|
+
return {
|
|
444
|
+
label: datatype.name,
|
|
445
|
+
kind: CompletionItemKind.Class
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function functionToCompletionItem(func) {
|
|
449
|
+
return {
|
|
450
|
+
label: func.name,
|
|
451
|
+
kind: CompletionItemKind.Function
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function constraintToCompletionItem(constraint) {
|
|
455
|
+
return {
|
|
456
|
+
label: constraint.name,
|
|
457
|
+
kind: CompletionItemKind.Property
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function convertPosition(position) {
|
|
461
|
+
return {
|
|
462
|
+
line: position.line,
|
|
463
|
+
character: position.character
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function getDistributionData() {
|
|
467
|
+
return dump_stan_math_distributions2().split(`
|
|
468
|
+
`).map((line) => line.split(":")[0]?.trim() ?? "").filter((name) => name !== "");
|
|
469
|
+
}
|
|
470
|
+
function getFunctionData() {
|
|
471
|
+
return dump_stan_math_signatures2().split(`
|
|
472
|
+
`);
|
|
473
|
+
}
|
|
474
|
+
function handleCompletion(params, documents) {
|
|
475
|
+
const document = documents.get(params.textDocument.uri);
|
|
476
|
+
if (!document) {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
const text = document.getText();
|
|
480
|
+
const position = convertPosition(params.position);
|
|
481
|
+
const keywords = provideKeywordCompletions(text, position);
|
|
482
|
+
const distributions = provideDistributionCompletions(text, position, getDistributionData());
|
|
483
|
+
const datatypes = provideDatatypeCompletions(text, position);
|
|
484
|
+
const functions = provideFunctionCompletions(text, position, getFunctionData());
|
|
485
|
+
const constraints = provideConstraintCompletions(text, position);
|
|
486
|
+
const allItems = [
|
|
487
|
+
...keywords.map(keywordToCompletionItem),
|
|
488
|
+
...distributions.map(distributionToCompletionItem),
|
|
489
|
+
...datatypes.map(datatypeToCompletionItem),
|
|
490
|
+
...functions.map(functionToCompletionItem),
|
|
491
|
+
...constraints.map(constraintToCompletionItem)
|
|
492
|
+
];
|
|
493
|
+
return allItems;
|
|
494
|
+
}
|
|
495
|
+
// src/handlers/diagnostics.ts
|
|
496
|
+
import {
|
|
497
|
+
DiagnosticSeverity as DiagnosticSeverity2
|
|
498
|
+
} from "vscode-languageserver";
|
|
499
|
+
|
|
500
|
+
// src/language/diagnostics/linter.ts
|
|
501
|
+
function rangeFromMessage(message) {
|
|
502
|
+
if (!message)
|
|
503
|
+
return;
|
|
504
|
+
const start = message.matchAll(/'.*', line (\d+), column (\d+)( to)?/g);
|
|
505
|
+
const lastMatch = Array.from(start).pop();
|
|
506
|
+
if (!lastMatch || !lastMatch[1] || !lastMatch[2]) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const startLine = parseInt(lastMatch[1]) - 1;
|
|
510
|
+
const startColumn = parseInt(lastMatch[2]);
|
|
511
|
+
let endLine = startLine;
|
|
512
|
+
let endColumn = startColumn;
|
|
513
|
+
if (lastMatch[3]) {
|
|
514
|
+
const end = message.match(/to (line (\d+), )?column (\d+)/);
|
|
515
|
+
if (end && end[3]) {
|
|
516
|
+
if (end[1] && end[2]) {
|
|
517
|
+
endLine = parseInt(end[2]) - 1;
|
|
518
|
+
}
|
|
519
|
+
endColumn = parseInt(end[3]);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
start: { line: startLine, character: startColumn },
|
|
524
|
+
end: { line: endLine, character: endColumn }
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function getWarningMessage(message) {
|
|
528
|
+
let warning = message.replace(/Warning.*column \d+: /s, "");
|
|
529
|
+
warning = warning.replace(/\s+/gs, " ");
|
|
530
|
+
warning = warning.trim();
|
|
531
|
+
warning = message.includes("included from") ? `Warning in included file:
|
|
532
|
+
` + warning : warning;
|
|
533
|
+
return warning;
|
|
534
|
+
}
|
|
535
|
+
function getErrorMessage(message) {
|
|
536
|
+
let error = message;
|
|
537
|
+
if (message.includes(`------
|
|
538
|
+
`)) {
|
|
539
|
+
error = error.split(`------
|
|
540
|
+
`)[2] ?? error;
|
|
541
|
+
}
|
|
542
|
+
error = error.trim();
|
|
543
|
+
error = message.includes("included from") ? `Error in included file:
|
|
544
|
+
` + error : error;
|
|
545
|
+
error = error.includes("given information about") ? error + `
|
|
546
|
+
Try opening the included file and making the Stan language server aware of it.` : error;
|
|
547
|
+
return error;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/constants/index.ts
|
|
551
|
+
var SERVER_ID = "stan-language-server";
|
|
552
|
+
|
|
553
|
+
// src/language/diagnostics/provider.ts
|
|
554
|
+
function provideDiagnostics(compilerResult) {
|
|
555
|
+
const diagnostics = [];
|
|
556
|
+
if (compilerResult.errors) {
|
|
557
|
+
for (const error of compilerResult.errors) {
|
|
558
|
+
const range = rangeFromMessage(error);
|
|
559
|
+
if (range) {
|
|
560
|
+
diagnostics.push({
|
|
561
|
+
range,
|
|
562
|
+
severity: 1 /* Error */,
|
|
563
|
+
message: getErrorMessage(error),
|
|
564
|
+
source: SERVER_ID
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (compilerResult.warnings) {
|
|
570
|
+
for (const warning of compilerResult.warnings) {
|
|
571
|
+
const range = rangeFromMessage(warning);
|
|
572
|
+
if (range) {
|
|
573
|
+
diagnostics.push({
|
|
574
|
+
range,
|
|
575
|
+
severity: 2 /* Warning */,
|
|
576
|
+
message: getWarningMessage(warning),
|
|
577
|
+
source: SERVER_ID
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return diagnostics;
|
|
583
|
+
}
|
|
584
|
+
// src/handlers/compilation/includes.ts
|
|
585
|
+
import { join } from "path";
|
|
586
|
+
import { URI, Utils } from "vscode-uri";
|
|
587
|
+
function getFilenames(fileContent) {
|
|
588
|
+
const includePattern = /#include\s*[<"]?([^>"\s]*)[>"]?/g;
|
|
589
|
+
const matches = Array.from(fileContent.matchAll(includePattern));
|
|
590
|
+
const results = matches.map((match) => match[1] || "");
|
|
591
|
+
return results;
|
|
592
|
+
}
|
|
593
|
+
function isFilePathError(value) {
|
|
594
|
+
return typeof value === "object" && value !== null && "msg" in value;
|
|
595
|
+
}
|
|
596
|
+
async function handleIncludes(document, documentManager, workspaceFolders, includePaths, logger, reader) {
|
|
597
|
+
try {
|
|
598
|
+
const includeFilenames = getFilenames(document.getText());
|
|
599
|
+
if (includeFilenames.length === 0) {
|
|
600
|
+
return {};
|
|
601
|
+
}
|
|
602
|
+
const allResults = await Promise.all(includeFilenames.map(async (filename) => {
|
|
603
|
+
try {
|
|
604
|
+
const content = await readIncludedFile(document, documentManager, workspaceFolders, includePaths, filename, reader);
|
|
605
|
+
return [filename, content];
|
|
606
|
+
} catch (err) {
|
|
607
|
+
return [filename, { msg: `${err.message}` }];
|
|
608
|
+
}
|
|
609
|
+
}));
|
|
610
|
+
const validResults = allResults.filter(([_, content]) => !isFilePathError(content));
|
|
611
|
+
return Object.fromEntries(validResults);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
logger.warn(`Resolving included files failed: ${error}`);
|
|
614
|
+
return Promise.resolve({});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
var readIncludedFile = async (document, documentManager, workspaceFolders, includePaths, filename, reader) => {
|
|
618
|
+
const currentDir = Utils.dirname(URI.parse(document.uri));
|
|
619
|
+
let includedFileContent = await readIncludedFileFromWorkspace(documentManager, workspaceFolders, filename, currentDir);
|
|
620
|
+
if (!isFilePathError(includedFileContent)) {
|
|
621
|
+
return Promise.resolve(includedFileContent);
|
|
622
|
+
}
|
|
623
|
+
if (reader) {
|
|
624
|
+
includedFileContent = await readIncludedFileFromFileSystem(filename, [
|
|
625
|
+
currentDir.fsPath,
|
|
626
|
+
...includePaths
|
|
627
|
+
], reader);
|
|
628
|
+
}
|
|
629
|
+
if (!isFilePathError(includedFileContent)) {
|
|
630
|
+
return Promise.resolve(includedFileContent);
|
|
631
|
+
}
|
|
632
|
+
return Promise.resolve({ msg: `File not found: ${filename}` });
|
|
633
|
+
};
|
|
634
|
+
var readIncludedFileFromWorkspace = (documentManager, workspaceFolders, filename, currentDir) => {
|
|
635
|
+
const searchFolders = [
|
|
636
|
+
{ uri: currentDir.toString(), name: "stan file directory" },
|
|
637
|
+
...workspaceFolders
|
|
638
|
+
];
|
|
639
|
+
const paths = searchFolders.map((folder) => folder.uri + "/" + filename);
|
|
640
|
+
const documents = paths.map((path) => {
|
|
641
|
+
const doc = documentManager.get(path);
|
|
642
|
+
return { path, doc };
|
|
643
|
+
});
|
|
644
|
+
const includedFile = documents.filter(({ doc }) => doc !== undefined).map(({ doc }) => doc)[0];
|
|
645
|
+
if (!includedFile) {
|
|
646
|
+
return Promise.resolve({ msg: `File not found: ${filename}` });
|
|
647
|
+
}
|
|
648
|
+
return Promise.resolve(includedFile.getText());
|
|
649
|
+
};
|
|
650
|
+
var readIncludedFileFromFileSystem = async (filename, dirs, fileSystemReader) => {
|
|
651
|
+
for (const currentDir of dirs) {
|
|
652
|
+
try {
|
|
653
|
+
const localPath = join(currentDir, filename);
|
|
654
|
+
return await fileSystemReader(localPath);
|
|
655
|
+
} catch (error) {}
|
|
656
|
+
}
|
|
657
|
+
return Promise.resolve({ msg: `File not found: ${filename}` });
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/handlers/compilation/compilation.ts
|
|
661
|
+
import { URI as URI2 } from "vscode-uri";
|
|
662
|
+
import { stanc } from "stanc3";
|
|
663
|
+
var defaultSettings = {
|
|
664
|
+
maxLineLength: 78,
|
|
665
|
+
includePaths: []
|
|
666
|
+
};
|
|
667
|
+
async function handleCompilation(document, documentManager, workspaceFolders, settings, logger, reader) {
|
|
668
|
+
const filename = URI2.parse(document.uri).fsPath;
|
|
669
|
+
const code = document.getText();
|
|
670
|
+
const includes = await handleIncludes(document, documentManager, workspaceFolders, settings.includePaths, logger, reader);
|
|
671
|
+
const stanc_args = [
|
|
672
|
+
"auto-format",
|
|
673
|
+
`filename-in-msg=${filename}`,
|
|
674
|
+
`max-line-length=${settings.maxLineLength}`,
|
|
675
|
+
"canonicalze=deprecations",
|
|
676
|
+
"allow-undefined"
|
|
677
|
+
];
|
|
678
|
+
if (filename.endsWith(".stanfunctions")) {
|
|
679
|
+
stanc_args.push("functions-only");
|
|
680
|
+
}
|
|
681
|
+
return Promise.resolve(stanc(filename, code, stanc_args, includes));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// 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
|
+
async function handleDiagnostics(params, documents, workspaceFolders, settings, logger, reader) {
|
|
720
|
+
const document = documents.get(params.textDocument.uri);
|
|
721
|
+
if (!document) {
|
|
722
|
+
return [];
|
|
723
|
+
}
|
|
724
|
+
const compilerResult = await handleCompilation(document, documents, workspaceFolders, settings, logger, reader);
|
|
725
|
+
const stanDiagnostics = provideDiagnostics(compilerResult);
|
|
726
|
+
return stanDiagnostics.map(stanDiagnosticToLspDiagnostic);
|
|
727
|
+
}
|
|
728
|
+
// src/handlers/formatting.ts
|
|
729
|
+
async function handleFormatting(params, documents, workspaceFolders, settings, logger, reader) {
|
|
730
|
+
const document = documents.get(params.textDocument.uri);
|
|
731
|
+
if (!document) {
|
|
732
|
+
return [];
|
|
733
|
+
}
|
|
734
|
+
const result = await handleCompilation(document, documents, workspaceFolders, settings, logger, reader);
|
|
735
|
+
if (result.errors && result.errors.length > 0) {
|
|
736
|
+
return { errors: result.errors };
|
|
737
|
+
} else if (result.result) {
|
|
738
|
+
const range = {
|
|
739
|
+
start: { line: 0, character: 0 },
|
|
740
|
+
end: {
|
|
741
|
+
line: document.lineCount - 1,
|
|
742
|
+
character: document.getText().length
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
return [
|
|
746
|
+
{
|
|
747
|
+
range,
|
|
748
|
+
newText: result.result
|
|
749
|
+
}
|
|
750
|
+
];
|
|
751
|
+
}
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
// src/server/index.ts
|
|
755
|
+
var startLanguageServer = (connection, reader) => {
|
|
756
|
+
let hasConfigurationCapability = false;
|
|
757
|
+
let hasWorkspaceFolderCapability = false;
|
|
758
|
+
connection.onInitialize((params) => {
|
|
759
|
+
connection.console.info("Initializing Stan language server...");
|
|
760
|
+
let capabilities = params.capabilities;
|
|
761
|
+
hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
|
|
762
|
+
hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
|
|
763
|
+
return {
|
|
764
|
+
capabilities: {
|
|
765
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
766
|
+
completionProvider: {
|
|
767
|
+
triggerCharacters: ["~"],
|
|
768
|
+
resolveProvider: false
|
|
769
|
+
},
|
|
770
|
+
documentFormattingProvider: true,
|
|
771
|
+
workspace: {
|
|
772
|
+
workspaceFolders: {
|
|
773
|
+
supported: hasWorkspaceFolderCapability
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
hoverProvider: true,
|
|
777
|
+
diagnosticProvider: {
|
|
778
|
+
interFileDependencies: true,
|
|
779
|
+
workspaceDiagnostics: false
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
});
|
|
784
|
+
connection.onInitialized(() => {
|
|
785
|
+
if (hasConfigurationCapability) {
|
|
786
|
+
connection.client.register(DidChangeConfigurationNotification.type);
|
|
787
|
+
}
|
|
788
|
+
connection.console.info("Stan language server is initialized!");
|
|
789
|
+
});
|
|
790
|
+
connection.onExit(() => {
|
|
791
|
+
connection.console.info("Stan language server is exiting...");
|
|
792
|
+
});
|
|
793
|
+
let globalSettings = defaultSettings;
|
|
794
|
+
let documentSettings = new Map;
|
|
795
|
+
connection.onDidChangeConfiguration((change) => {
|
|
796
|
+
if (hasConfigurationCapability) {
|
|
797
|
+
documentSettings.clear();
|
|
798
|
+
} else {
|
|
799
|
+
const incomingSettings = change.settings[SERVER_ID] || {};
|
|
800
|
+
globalSettings = { ...globalSettings, ...incomingSettings };
|
|
801
|
+
}
|
|
802
|
+
connection.sendRequest(DiagnosticRefreshRequest.type);
|
|
803
|
+
});
|
|
804
|
+
const getDocumentSettings = async (resource) => {
|
|
805
|
+
if (!hasConfigurationCapability) {
|
|
806
|
+
return Promise.resolve(globalSettings);
|
|
807
|
+
}
|
|
808
|
+
let result = documentSettings.get(resource);
|
|
809
|
+
if (result !== undefined) {
|
|
810
|
+
return result;
|
|
811
|
+
}
|
|
812
|
+
let clientSettings = await connection.workspace.getConfiguration({
|
|
813
|
+
scopeUri: resource,
|
|
814
|
+
section: SERVER_ID
|
|
815
|
+
}) || {};
|
|
816
|
+
const docSettings = { ...defaultSettings, ...clientSettings };
|
|
817
|
+
documentSettings.set(resource, docSettings);
|
|
818
|
+
return docSettings;
|
|
819
|
+
};
|
|
820
|
+
const documents = new TextDocuments3(TextDocument);
|
|
821
|
+
connection.onCompletion((params) => {
|
|
822
|
+
return handleCompletion(params, documents);
|
|
823
|
+
});
|
|
824
|
+
const getWorkspaceFolders = async () => {
|
|
825
|
+
if (hasWorkspaceFolderCapability) {
|
|
826
|
+
return await connection.workspace.getWorkspaceFolders() || [];
|
|
827
|
+
}
|
|
828
|
+
return [];
|
|
829
|
+
};
|
|
830
|
+
connection.onRequest(DocumentDiagnosticRequest.method, async (params) => {
|
|
831
|
+
const folders = await getWorkspaceFolders();
|
|
832
|
+
const settings = await getDocumentSettings(params.textDocument.uri);
|
|
833
|
+
return {
|
|
834
|
+
kind: "full",
|
|
835
|
+
items: await handleDiagnostics(params, documents, folders, settings, connection.console, reader)
|
|
836
|
+
};
|
|
837
|
+
});
|
|
838
|
+
connection.onDocumentFormatting(async (params) => {
|
|
839
|
+
const folders = await getWorkspaceFolders();
|
|
840
|
+
const settings = await getDocumentSettings(params.textDocument.uri);
|
|
841
|
+
const formattingResult = await handleFormatting(params, documents, folders, settings, connection.console, reader);
|
|
842
|
+
if (Array.isArray(formattingResult)) {
|
|
843
|
+
return formattingResult;
|
|
844
|
+
} else {
|
|
845
|
+
connection.console.error("Formatting errors:");
|
|
846
|
+
for (const error of formattingResult.errors) {
|
|
847
|
+
connection.console.error(error);
|
|
848
|
+
}
|
|
849
|
+
return [];
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
connection.onHover((params) => {
|
|
853
|
+
const document = documents.get(params.textDocument.uri);
|
|
854
|
+
if (!document) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
return hover_default(document, params);
|
|
858
|
+
});
|
|
859
|
+
documents.listen(connection);
|
|
860
|
+
connection.listen();
|
|
861
|
+
};
|
|
862
|
+
var server_default = startLanguageServer;
|
|
863
|
+
export {
|
|
864
|
+
server_default as default
|
|
865
|
+
};
|