stan-language-server 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +73 -14
  2. package/dist/handlers/completion/constraints.d.ts +2 -0
  3. package/dist/handlers/completion/datatypes.d.ts +2 -0
  4. package/dist/handlers/completion/distributions.d.ts +2 -0
  5. package/dist/handlers/completion/functions.d.ts +2 -0
  6. package/dist/handlers/completion/keywords.d.ts +2 -0
  7. package/dist/handlers/completion/snippets.d.ts +2 -0
  8. package/dist/handlers/completion.d.ts +3 -2
  9. package/dist/language/hover/util.d.ts +1 -1
  10. package/dist/server/index.js +268 -208
  11. package/dist/types/common.d.ts +0 -4
  12. package/dist/types/index.d.ts +1 -2
  13. package/package.json +5 -2
  14. package/dist/__tests__/language/completion/providers/datatypes.test.d.ts +0 -1
  15. package/dist/__tests__/language/completion/providers/distributions.test.d.ts +0 -1
  16. package/dist/__tests__/language/completion/providers/functions.test.d.ts +0 -1
  17. package/dist/__tests__/language/completion/providers/keywords.test.d.ts +0 -1
  18. package/dist/__tests__/language/completion/util.test.d.ts +0 -1
  19. package/dist/language/completion/providers/constraints.d.ts +0 -3
  20. package/dist/language/completion/providers/datatypes.d.ts +0 -3
  21. package/dist/language/completion/providers/distributions.d.ts +0 -2
  22. package/dist/language/completion/providers/functions.d.ts +0 -2
  23. package/dist/language/completion/providers/keywords.d.ts +0 -2
  24. package/dist/language/completion/util.d.ts +0 -4
  25. package/dist/types/completion.d.ts +0 -15
  26. /package/dist/__tests__/{language/completion/providers/constraints.test.d.ts → handlers/completion.test.d.ts} +0 -0
package/README.md CHANGED
@@ -10,7 +10,6 @@ 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
-
14
13
  ## Editor-specific configuration
15
14
 
16
15
  ### VSCode:
@@ -21,7 +20,19 @@ or [open-vsx](https://open-vsx.org/extension/wardbrian/vscode-stan-extension).
21
20
 
22
21
  ### Neovim
23
22
 
24
- Download the latest language server executable from [GitHub](https://github.com/tomatitito/stan-language-server/releases) and put it somewhere in your `PATH`.
23
+ #### Mason
24
+
25
+ The Stan language server is installable with
26
+ [Mason](https://github.com/mason-org/mason.nvim) by running `:MasonInstall
27
+ stan-language-server`.
28
+
29
+ #### Manual install
30
+
31
+ The language server can also be configured manually by first installing
32
+ the language server executable. This can be done directly via `npm install -g
33
+ stan-language-server-bin` or by downloading the latest executable from
34
+ [GitHub](https://github.com/tomatitito/stan-language-server/releases) and putting
35
+ it somewhere in your `PATH`.
25
36
 
26
37
  #### Neovim 0.11+ (built-in LSP)
27
38
 
@@ -95,27 +106,74 @@ and add the following to the settings file:
95
106
  ### Emacs (eglot)
96
107
 
97
108
  Assuming you are using [stan-ts-mode](https://github.com/WardBrian/stan-ts-mode),
98
- [download the latest release](https://github.com/tomatitito/stan-language-server/releases)
99
- and add the following to your `init.el`:
109
+ add the following to your `init.el`.
110
+ This will download the latest release the first time you load a Stan file.
100
111
 
101
112
  ```elisp
102
- ; elgot is built in to emacs 29+, but some features work better if you use the
103
- ;; latest version from GNU ELPA
104
113
  (require 'package)
105
- (add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/") t)
114
+ ;; elgot is built in to emacs 29+, but some features work better if you use the
115
+ ;; latest version from GNU ELPA
116
+ (add-to-list 'package-archives '("gnu-devel" . "https://elpa.gnu.org/devel/"))
106
117
  (package-initialize)
107
118
 
119
+ ;; work around https://debbugs.gnu.org/cgi/bugreport.cgi?bug=69423
120
+ (assq-delete-all 'eglot package--builtins)
121
+ (assq-delete-all 'eglot package--builtin-versions)
122
+
123
+ (defcustom bmw/stan-language-server-location
124
+ (expand-file-name (concat "bin/stan-language-server" (car exec-suffixes)) user-emacs-directory)
125
+ "Location to download the stan-language-server binary to."
126
+ :type 'file
127
+ :group 'stan)
128
+
129
+ (use-package url)
130
+
131
+ (defun bmw/download-stan-language-server (&optional force)
132
+ "Download the latest copy of the stan-language-server.
133
+ The location is determined by stan-ts-mode-language-server-location.
134
+ Argument FORCE will make the download proceed even if the file exists."
135
+ (interactive "P")
136
+ (when (or force (not (file-exists-p bmw/stan-language-server-location)))
137
+ (let*
138
+ ((version
139
+ (with-temp-buffer
140
+ (url-insert-file-contents
141
+ "https://api.github.com/repos/tomatitito/stan-language-server/releases/latest")
142
+ (let ((json (json-parse-buffer)))
143
+ (gethash "tag_name" json))))
144
+ (os-tag
145
+ (pcase system-type
146
+ ((or 'windows-nt 'cygwin 'ms-dos) "windows-x86_64")
147
+ ('darwin (concat "macos-"
148
+ (if (string-match-p "aarch64\\|arm" system-configuration ) "aarch64" "x86_64") ))
149
+ (_ (concat "linux-"
150
+ (if (string-match-p "aarch64\\|arm" system-configuration ) "arm64" "x86_64")))))
151
+ (url
152
+ (concat
153
+ "https://github.com/tomatitito/stan-language-server/releases/download/"
154
+ version
155
+ "/stan-ls-"
156
+ version
157
+ "-"
158
+ os-tag
159
+ (car exec-suffixes)))
160
+ (file bmw/stan-language-server-location))
161
+ (make-empty-file file t)
162
+ (delete-file file)
163
+ (url-copy-file url file)
164
+ (chmod file 500))))
165
+
108
166
  (use-package eglot
109
167
  :ensure t
110
- :demand t
111
- :pin gnu
112
- :hook (stan-ts-mode . eglot-ensure)
168
+ :pin gnu-devel
169
+ :hook ((stan-ts-base-mode . bmw/download-stan-language-server)
170
+ (stan-ts-base-mode . eglot-ensure))
113
171
  :config
114
- (add-to-list 'eglot-server-programs '(stan-ts-mode .
115
- ("PATH/TO/stan-language-server" "--stdio"))))
172
+ (add-to-list
173
+ 'eglot-server-programs
174
+ `(stan-ts-base-mode . (,bmw/stan-language-server-location "--stdio"))))
116
175
  ```
117
176
 
118
-
119
177
  ## For developers
120
178
 
121
179
  Development uses [bun](https://bun.sh/)
@@ -133,12 +191,13 @@ bun build:binary
133
191
  ```
134
192
 
135
193
  To run unit tests:
194
+
136
195
  ```bash
137
196
  bun test
138
197
  ```
139
198
 
140
199
  To run end-to-end tests:
200
+
141
201
  ```bash
142
202
  bun test:e2e
143
203
  ```
144
-
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const CONSTRAINTS: CompletionItem[];
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const DATATYPES: CompletionItem[];
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const DISTRIBUTIONS: CompletionItem[];
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const FUNCTIONS: CompletionItem[];
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const KEYWORDS: CompletionItem[];
@@ -0,0 +1,2 @@
1
+ import { CompletionItem } from "vscode-languageserver";
2
+ export declare const SNIPPETS: CompletionItem[];
@@ -1,3 +1,4 @@
1
1
  import { type CompletionParams, TextDocuments, CompletionItem } from "vscode-languageserver";
2
- import { TextDocument } from "vscode-languageserver-textdocument";
3
- export declare function handleCompletion(params: CompletionParams, documents: TextDocuments<TextDocument>): CompletionItem[];
2
+ import { TextDocument, type Position } from "vscode-languageserver-textdocument";
3
+ export declare const getTextUpToCursor: (text: string, position: Position) => string;
4
+ export declare function handleCompletion(params: CompletionParams, documents: TextDocuments<TextDocument>, supportsSnippets: boolean): CompletionItem[];
@@ -1,4 +1,4 @@
1
1
  export declare const isWordChar: (char: string) => boolean;
2
- export declare const isWhitespace: (char: string) => char is " " | "\n" | "\t" | "\r";
2
+ export declare const isWhitespace: (char: string) => char is "\n" | " " | "\t" | "\r";
3
3
  export declare const previousWordBoundary: (text: string, pos: number) => number;
4
4
  export declare const wordUntilNextParenthesis: (text: string, pos: number) => number;
@@ -11,25 +11,61 @@ import {
11
11
 
12
12
  // src/handlers/completion.ts
13
13
  import {
14
- CompletionItemKind
14
+ CompletionItemKind as CompletionItemKind7
15
15
  } from "vscode-languageserver";
16
-
17
- // src/language/completion/util.ts
18
16
  import TrieSearch from "trie-search";
19
- var getSearchableItems = (xs, options = {}) => {
20
- const searchableItem = new TrieSearch("name", options);
21
- searchableItem.addAll(xs);
22
- return searchableItem;
23
- };
24
- var getTextUpToCursor = (text, position) => {
25
- const lines = text.split(`
26
- `);
27
- const currentLine = lines[position.line] || "";
28
- return currentLine.substring(0, position.character);
29
- };
30
17
 
31
- // src/language/completion/providers/keywords.ts
32
- var ALL_KEYWORDS = [
18
+ // src/handlers/completion/constraints.ts
19
+ import { CompletionItemKind } from "vscode-languageserver";
20
+ function constraintToCompletionItem(constraint) {
21
+ return {
22
+ label: constraint,
23
+ kind: CompletionItemKind.Property
24
+ };
25
+ }
26
+ var CONSTRAINTS = ["lower", "upper", "offset", "multiplier"].map(constraintToCompletionItem);
27
+
28
+ // src/handlers/completion/datatypes.ts
29
+ import { CompletionItemKind as CompletionItemKind2 } from "vscode-languageserver";
30
+ function datatypeToCompletionItem(datatype) {
31
+ return {
32
+ label: datatype,
33
+ kind: CompletionItemKind2.Class
34
+ };
35
+ }
36
+ var DATATYPES = [
37
+ "void",
38
+ "int",
39
+ "real",
40
+ "complex",
41
+ "vector",
42
+ "row_vector",
43
+ "matrix",
44
+ "complex_vector",
45
+ "complex_row_vector",
46
+ "complex_matrix",
47
+ "ordered",
48
+ "positive_ordered",
49
+ "simplex",
50
+ "unit_vector",
51
+ "sum_to_zero_vector",
52
+ "cholesky_factor_corr",
53
+ "cholesky_factor_cov",
54
+ "corr_matrix",
55
+ "cov_matrix",
56
+ "stochastic_column_matrix",
57
+ "stochastic_row_matrix"
58
+ ].map(datatypeToCompletionItem);
59
+
60
+ // src/handlers/completion/keywords.ts
61
+ import { CompletionItemKind as CompletionItemKind3 } from "vscode-languageserver";
62
+ function keywordToCompletionItem(keyword) {
63
+ return {
64
+ label: keyword,
65
+ kind: CompletionItemKind3.Keyword
66
+ };
67
+ }
68
+ var KEYWORDS = [
33
69
  "for",
34
70
  "in",
35
71
  "while",
@@ -71,94 +107,158 @@ var ALL_KEYWORDS = [
71
107
  "tuple",
72
108
  "truncate",
73
109
  "jacobian"
74
- ];
75
- var getKeywords = () => {
76
- return ALL_KEYWORDS.map((keyword) => ({
77
- name: keyword
78
- }));
79
- };
80
- var provideKeywordCompletions = (text, position) => {
81
- const textUpToCursor = getTextUpToCursor(text, position);
82
- const keywords = getKeywords();
83
- const searchableKeywords = getSearchableItems(keywords, {
84
- splitOnRegEx: /[\s_]/g,
85
- min: 0
86
- });
87
- const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
88
- if (match) {
89
- const keywordName = match[1] || "";
90
- const completionProposals = searchableKeywords.search(keywordName);
91
- return completionProposals;
92
- }
93
- return [];
94
- };
110
+ ].map(keywordToCompletionItem);
95
111
 
96
- // src/language/completion/providers/distributions.ts
97
- var provideDistributionCompletions = (text, position, distributions) => {
98
- const textUpToCursor = getTextUpToCursor(text, position);
99
- const distributionItems = distributions.filter((name) => name !== "").map((name) => ({ name }));
100
- const searchableDistributions = getSearchableItems(distributionItems, {
101
- splitOnRegEx: /[\s_]/g,
102
- min: 0
103
- });
104
- const match = textUpToCursor.match(/.*~\s*([\w_]*)$/);
105
- if (match) {
106
- const distName = match[1] || "";
107
- let completionProposals;
108
- if (distName === "") {
109
- completionProposals = distributionItems;
110
- } else {
111
- completionProposals = searchableDistributions.search(distName);
112
- }
113
- return completionProposals;
112
+ // src/handlers/completion/snippets.ts
113
+ import {
114
+ CompletionItemKind as CompletionItemKind4,
115
+ InsertTextFormat,
116
+ InsertTextMode
117
+ } from "vscode-languageserver";
118
+ function snippetToCompletionItem(snippet) {
119
+ return {
120
+ label: snippet.name,
121
+ kind: CompletionItemKind4.Snippet,
122
+ insertText: snippet.body.join(`
123
+ `),
124
+ insertTextFormat: InsertTextFormat.Snippet,
125
+ insertTextMode: InsertTextMode.adjustIndentation,
126
+ detail: snippet.description
127
+ };
128
+ }
129
+ var SNIPPETS = [
130
+ {
131
+ name: "else",
132
+ body: ["else {", " ${0:/* code */}", "}"],
133
+ description: "Code snippet for 'else' conditional"
134
+ },
135
+ {
136
+ name: "elseif",
137
+ body: ["else if (${1:/* condition */}) {", " ${0:/* code */}", "}"],
138
+ description: "Code snippet for 'else if' conditional"
139
+ },
140
+ {
141
+ name: "for",
142
+ body: [
143
+ "for (${1:identifier} in ${2:collection}) {",
144
+ " ${0:/* code */}",
145
+ "}"
146
+ ],
147
+ description: "Code snippet for 'for' loop"
148
+ },
149
+ {
150
+ name: "if",
151
+ body: ["if (${1:/* condition */}) {", " ${0:/* code */}", "}"],
152
+ description: "Code snippet for 'if' conditional"
153
+ },
154
+ {
155
+ name: "ifelse",
156
+ body: [
157
+ "if (${1:/* condition */}) {",
158
+ " ${2:/* code */}",
159
+ "} else {",
160
+ " ${0:/* code */}",
161
+ "}"
162
+ ],
163
+ description: "Code snippet for 'if-else' conditional block"
164
+ },
165
+ {
166
+ name: "while",
167
+ body: ["while (${1:/* condition */}) {", " ${0:/* code */}", "}"],
168
+ description: "Code snippet for 'while' loop"
169
+ },
170
+ {
171
+ name: "profile",
172
+ body: ['profile("${1:name}") {', " ${0:/* code to be profiled */}", "}"],
173
+ description: "Code snippet for 'profile' block"
174
+ },
175
+ {
176
+ name: "data",
177
+ body: ["data {", " ${0:/* ... declarations ... */}", "}"],
178
+ description: "Code snippet for 'data' block"
179
+ },
180
+ {
181
+ name: "transformed data",
182
+ body: [
183
+ "transformed data {",
184
+ " ${0:/* ... declarations ... statements ... */}",
185
+ "}"
186
+ ],
187
+ description: "Code snippet for 'transformed data' block"
188
+ },
189
+ {
190
+ name: "parameters",
191
+ body: ["parameters {", " ${0:/* ... declarations ... */}", "}"],
192
+ description: "Code snippet for 'parameters' block"
193
+ },
194
+ {
195
+ name: "transformed parameters",
196
+ body: [
197
+ "transformed parameters {",
198
+ " ${0:/* ... declarations ... statements ... */}",
199
+ "}"
200
+ ],
201
+ description: "Code snippet for 'transformed parameters' block"
202
+ },
203
+ {
204
+ name: "model",
205
+ body: ["model {", " ${0:/* ... declarations ... statements ... */}", "}"],
206
+ description: "Code snippet for 'model' block"
207
+ },
208
+ {
209
+ name: "generated quantities",
210
+ body: [
211
+ "generated quantities {",
212
+ " ${0:/* ... declarations ... statements ... */}",
213
+ "}"
214
+ ],
215
+ description: "Code snippet for 'generated quantities' block"
216
+ },
217
+ {
218
+ name: "functions",
219
+ body: [
220
+ "functions {",
221
+ " ${0:/* ... function declarations and definitions ... */}",
222
+ "}"
223
+ ],
224
+ description: "Code snippet for 'functions' block"
225
+ },
226
+ {
227
+ name: "include",
228
+ body: ['#include "${1:filename}"'],
229
+ description: "Code snippet for 'include' preprocessor"
230
+ },
231
+ {
232
+ name: "target",
233
+ body: ["target += ${1};"],
234
+ description: "Code snippet for 'target +=' statement"
235
+ },
236
+ {
237
+ name: "jacobian",
238
+ body: ["jacobian += ${1};"],
239
+ description: "Code snippet for 'jacobian +=' statement"
114
240
  }
115
- return [];
116
- };
241
+ ].map(snippetToCompletionItem);
117
242
 
118
- // src/language/completion/providers/datatypes.ts
119
- var DATATYPES = [
120
- "void",
121
- "int",
122
- "real",
123
- "complex",
124
- "vector",
125
- "row_vector",
126
- "matrix",
127
- "complex_vector",
128
- "complex_row_vector",
129
- "complex_matrix",
130
- "ordered",
131
- "positive_ordered",
132
- "simplex",
133
- "unit_vector",
134
- "sum_to_zero_vector",
135
- "cholesky_factor_corr",
136
- "cholesky_factor_cov",
137
- "corr_matrix",
138
- "cov_matrix",
139
- "stochastic_column_matrix",
140
- "stochastic_row_matrix"
141
- ];
142
- var getDatatypes = () => {
143
- return DATATYPES.map((datatype) => ({
144
- name: datatype
145
- }));
146
- };
147
- var provideDatatypeCompletions = (text, position) => {
148
- const textUpToCursor = getTextUpToCursor(text, position);
149
- const datatypes = getDatatypes();
150
- const searchableDatatypes = getSearchableItems(datatypes, {
151
- splitOnRegEx: /[\s_]/g,
152
- min: 0
153
- });
154
- const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
155
- if (match) {
156
- const typeName = match[1] || "";
157
- const completionProposals = searchableDatatypes.search(typeName);
158
- return completionProposals;
159
- }
160
- return [];
243
+ // src/handlers/completion/distributions.ts
244
+ import { CompletionItemKind as CompletionItemKind5 } from "vscode-languageserver";
245
+ import { dump_stan_math_distributions } from "stanc3";
246
+ function distributionToCompletionItem(distribution) {
247
+ return {
248
+ label: distribution,
249
+ kind: CompletionItemKind5.Function
250
+ };
251
+ }
252
+ var getDistributions = () => {
253
+ const distributions = dump_stan_math_distributions().split(`
254
+ `).map((line) => line.split(":")[0]?.trim() ?? "").filter((name) => name !== "");
255
+ return distributions.map(distributionToCompletionItem);
161
256
  };
257
+ var DISTRIBUTIONS = getDistributions();
258
+
259
+ // src/handlers/completion/functions.ts
260
+ import { CompletionItemKind as CompletionItemKind6 } from "vscode-languageserver";
261
+ import { dump_stan_math_signatures as dump_stan_math_signatures2 } from "stanc3";
162
262
 
163
263
  // src/handlers/hover.ts
164
264
  import { dump_stan_math_signatures } from "stanc3";
@@ -202,10 +302,10 @@ var wordUntilNextParenthesis = (text, pos) => {
202
302
  };
203
303
 
204
304
  // src/language/hover/distributions.ts
205
- import { dump_stan_math_distributions } from "stanc3";
305
+ import { dump_stan_math_distributions as dump_stan_math_distributions2 } from "stanc3";
206
306
  var setupDistributionMap = () => {
207
307
  const distributionToFunctionMap = new Map;
208
- const mathDistributions = dump_stan_math_distributions();
308
+ const mathDistributions = dump_stan_math_distributions2();
209
309
  const distLines = mathDistributions.split(`
210
310
  `);
211
311
  for (const line of distLines) {
@@ -358,123 +458,81 @@ async function handleHover(document, params) {
358
458
  }
359
459
  var hover_default = handleHover;
360
460
 
361
- // src/language/completion/providers/functions.ts
362
- var provideFunctionCompletions = (text, position, functionSignatures) => {
363
- const textUpToCursor = getTextUpToCursor(text, position);
364
- const functionNames = functionSignatures.map((line) => line.split("(", 1)[0]?.trim() ?? "").filter((name) => name !== "");
365
- const allFunctionNames = [...new Set([...functionNames, ...manual_functions])];
366
- const functionItems = allFunctionNames.map((name) => ({ name }));
367
- const searchableFunctions = getSearchableItems(functionItems, {
368
- splitOnRegEx: /[\s_]/g,
369
- min: 0
370
- });
461
+ // src/handlers/completion/functions.ts
462
+ function functionToCompletionItem(func) {
463
+ return {
464
+ label: func,
465
+ kind: CompletionItemKind6.Function
466
+ };
467
+ }
468
+ var getFunctions = () => {
469
+ const functions = dump_stan_math_signatures2().split(`
470
+ `);
471
+ const functionNames = functions.map((line) => line.split("(", 1)[0]?.trim() ?? "").filter((name) => name !== "");
472
+ const uniqueFunctions = [...new Set([...functionNames, ...manual_functions])];
473
+ return uniqueFunctions.map(functionToCompletionItem);
474
+ };
475
+ var FUNCTIONS = getFunctions();
476
+
477
+ // src/handlers/completion.ts
478
+ var COMPLETION_TRIE = new TrieSearch("label", {
479
+ splitOnRegEx: /[\s_]/g,
480
+ min: 0
481
+ });
482
+ COMPLETION_TRIE.addAll([
483
+ ...CONSTRAINTS,
484
+ ...DATATYPES,
485
+ ...KEYWORDS,
486
+ ...FUNCTIONS,
487
+ ...SNIPPETS
488
+ ]);
489
+ var searchWords = (textUpToCursor, supportsSnippets) => {
371
490
  const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
372
491
  if (match) {
373
- const functionName = match[1] || "";
374
- const completionProposals = searchableFunctions.search(functionName);
492
+ const word = match[1] || "";
493
+ const completionProposals = COMPLETION_TRIE.search(word);
494
+ if (!supportsSnippets) {
495
+ return completionProposals.filter((item) => item.kind !== CompletionItemKind7.Snippet);
496
+ }
375
497
  return completionProposals;
376
498
  }
377
499
  return [];
378
500
  };
379
-
380
- // src/language/completion/providers/constraints.ts
381
- var CONSTRAINTS = [
382
- "lower",
383
- "upper",
384
- "offset",
385
- "multiplier",
386
- "ordered",
387
- "positive_ordered",
388
- "simplex",
389
- "unit_vector",
390
- "sum_to_zero_vector",
391
- "cholesky_factor_corr",
392
- "cholesky_factor_cov",
393
- "corr_matrix",
394
- "cov_matrix",
395
- "stochastic_column_matrix",
396
- "stochastic_row_matrix"
397
- ];
398
- var getConstraints = () => {
399
- return CONSTRAINTS.map((constraint) => ({
400
- name: constraint
401
- }));
402
- };
403
- var provideConstraintCompletions = (text, position) => {
404
- const textUpToCursor = getTextUpToCursor(text, position);
405
- const constraints = getConstraints();
406
- const searchableConstraints = getSearchableItems(constraints, {
407
- splitOnRegEx: /[\s_]/g,
408
- min: 0
409
- });
410
- const match = textUpToCursor.match(/(?:^|\s)([\w_]+)$/);
501
+ var DISTRIBUTION_TRIE = new TrieSearch("label", {
502
+ splitOnRegEx: /[\s_]/g,
503
+ min: 0
504
+ });
505
+ DISTRIBUTION_TRIE.addAll(DISTRIBUTIONS);
506
+ var searchDistributions = (textUpToCursor) => {
507
+ const match = textUpToCursor.match(/.*~\s*([\w_]*)$/);
411
508
  if (match) {
412
- const constraintName = match[1] || "";
413
- const completionProposals = searchableConstraints.search(constraintName);
509
+ const distName = match[1] || "";
510
+ let completionProposals;
511
+ if (distName === "") {
512
+ completionProposals = DISTRIBUTIONS;
513
+ } else {
514
+ completionProposals = DISTRIBUTION_TRIE.search(distName);
515
+ }
414
516
  return completionProposals;
415
517
  }
416
518
  return [];
417
519
  };
418
-
419
- // src/handlers/completion.ts
420
- import {
421
- dump_stan_math_distributions as dump_stan_math_distributions2,
422
- dump_stan_math_signatures as dump_stan_math_signatures2
423
- } from "stanc3";
424
- function keywordToCompletionItem(keyword) {
425
- return {
426
- label: keyword.name,
427
- kind: CompletionItemKind.Keyword
428
- };
429
- }
430
- function distributionToCompletionItem(distribution) {
431
- return {
432
- label: distribution.name,
433
- kind: CompletionItemKind.Function
434
- };
435
- }
436
- function datatypeToCompletionItem(datatype) {
437
- return {
438
- label: datatype.name,
439
- kind: CompletionItemKind.Class
440
- };
441
- }
442
- function functionToCompletionItem(func) {
443
- return {
444
- label: func.name,
445
- kind: CompletionItemKind.Function
446
- };
447
- }
448
- function constraintToCompletionItem(constraint) {
449
- return {
450
- label: constraint.name,
451
- kind: CompletionItemKind.Property
452
- };
453
- }
454
- var DISTRIBUTION_DATA = dump_stan_math_distributions2().split(`
455
- `).map((line) => line.split(":")[0]?.trim() ?? "").filter((name) => name !== "");
456
- var FUNCTION_DATA = dump_stan_math_signatures2().split(`
520
+ var getTextUpToCursor = (text, position) => {
521
+ const lines = text.split(`
457
522
  `);
458
- function handleCompletion(params, documents) {
523
+ const currentLine = lines[position.line] || "";
524
+ return currentLine.substring(0, position.character);
525
+ };
526
+ function handleCompletion(params, documents, supportsSnippets) {
459
527
  const document = documents.get(params.textDocument.uri);
460
528
  if (!document) {
461
529
  return [];
462
530
  }
463
- const text = document.getText();
464
- const position = params.position;
465
- const keywords = provideKeywordCompletions(text, position);
466
- const distributions = provideDistributionCompletions(text, position, DISTRIBUTION_DATA);
467
- const datatypes = provideDatatypeCompletions(text, position);
468
- const functions = provideFunctionCompletions(text, position, FUNCTION_DATA);
469
- const constraints = provideConstraintCompletions(text, position);
470
- const allItems = [
471
- ...keywords.map(keywordToCompletionItem),
472
- ...distributions.map(distributionToCompletionItem),
473
- ...datatypes.map(datatypeToCompletionItem),
474
- ...functions.map(functionToCompletionItem),
475
- ...constraints.map(constraintToCompletionItem)
531
+ const textUpToCursor = getTextUpToCursor(document.getText(), params.position);
532
+ return [
533
+ ...searchDistributions(textUpToCursor),
534
+ ...searchWords(textUpToCursor, supportsSnippets)
476
535
  ];
477
- return allItems;
478
536
  }
479
537
  // src/handlers/diagnostics.ts
480
538
  import {
@@ -640,7 +698,7 @@ var readIncludedFileFromFileSystem = async (filename, dirs, fileSystemReader) =>
640
698
  const localPath = join(currentDir, filename);
641
699
  const content = await fileSystemReader(localPath);
642
700
  return TextDocument.create(URI.file(localPath).toString(), "stan", 0, content);
643
- } catch (error) {}
701
+ } catch (_error) {}
644
702
  }
645
703
  return Promise.resolve({ msg: `File not found: ${filename}` });
646
704
  };
@@ -726,12 +784,14 @@ var startLanguageServer = (connection, reader) => {
726
784
  let hasConfigurationCapability = false;
727
785
  let hasWorkspaceFolderCapability = false;
728
786
  let hasDynamicConfigurationRequestCapability = false;
787
+ let hasSnippetSupport = false;
729
788
  connection.onInitialize((params) => {
730
789
  connection.console.info("Initializing Stan language server...");
731
- let capabilities = params.capabilities;
790
+ const capabilities = params.capabilities;
732
791
  hasConfigurationCapability = Boolean(capabilities.workspace?.configuration);
733
792
  hasDynamicConfigurationRequestCapability = Boolean(capabilities.workspace?.configuration && capabilities.workspace?.didChangeConfiguration?.dynamicRegistration);
734
793
  hasWorkspaceFolderCapability = Boolean(capabilities.workspace?.workspaceFolders);
794
+ hasSnippetSupport = Boolean(capabilities.textDocument?.completion?.completionItem?.snippetSupport);
735
795
  return {
736
796
  capabilities: {
737
797
  textDocumentSync: TextDocumentSyncKind.Incremental,
@@ -765,7 +825,7 @@ var startLanguageServer = (connection, reader) => {
765
825
  connection.console.info("Stan language server is exiting...");
766
826
  });
767
827
  let globalSettings = defaultSettings;
768
- let documentSettings = new Map;
828
+ const documentSettings = new Map;
769
829
  connection.onDidChangeConfiguration((change) => {
770
830
  if (hasDynamicConfigurationRequestCapability) {
771
831
  documentSettings.clear();
@@ -779,11 +839,11 @@ var startLanguageServer = (connection, reader) => {
779
839
  if (!hasConfigurationCapability) {
780
840
  return Promise.resolve(globalSettings);
781
841
  }
782
- let result = documentSettings.get(resource);
842
+ const result = documentSettings.get(resource);
783
843
  if (result !== undefined) {
784
844
  return result;
785
845
  }
786
- let clientSettings = await connection.workspace.getConfiguration({
846
+ const clientSettings = await connection.workspace.getConfiguration({
787
847
  scopeUri: resource,
788
848
  section: SERVER_ID
789
849
  }) || {};
@@ -793,7 +853,7 @@ var startLanguageServer = (connection, reader) => {
793
853
  };
794
854
  const documents = new TextDocuments3(TextDocument2);
795
855
  connection.onCompletion((params) => {
796
- return handleCompletion(params, documents);
856
+ return handleCompletion(params, documents, hasSnippetSupport);
797
857
  });
798
858
  const getWorkspaceFolders = async () => {
799
859
  if (hasWorkspaceFolderCapability) {
@@ -1,6 +1,2 @@
1
1
  import type { FileContent, Filename } from "../handlers/compilation/includes";
2
- export interface Position {
3
- line: number;
4
- character: number;
5
- }
6
2
  export type FileSystemReader = (filename: Filename) => Promise<FileContent>;
@@ -1,2 +1 @@
1
- export * from './common';
2
- export * from './completion';
1
+ export * from "./common";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stan-language-server",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "scripts": {
49
49
  "build": "bun build src/server/index.ts --outdir dist/server --packages external --target browser --format esm && tsc --emitDeclarationOnly --outDir dist",
50
- "build:binary": "bun build src/server/cli.ts --drop=console --compile --outfile bin/stan-language-server",
50
+ "build:binary": "bun build src/server/cli.ts --compile --outfile bin/stan-language-server",
51
51
  "build:bin-package": "bun run scripts/build-bin-package.ts",
52
52
  "prepublishOnly": "bun run build",
53
53
  "version:set": "bun run scripts/set-version.ts",
@@ -72,6 +72,9 @@
72
72
  "@typescript-eslint/eslint-plugin": "8.57.1",
73
73
  "@typescript-eslint/parser": "8.57.1",
74
74
  "eslint": "10.0.3",
75
+ "globals": "^17.3.0",
76
+ "@eslint/js": "^10.0.1",
77
+ "typescript-eslint": "^8.57.0",
75
78
  "typescript": "5.9.3"
76
79
  },
77
80
  "dependencies": {
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- import type { Constraint, Position } from "../../../types/completion";
2
- export declare const CONSTRAINTS: string[];
3
- export declare const provideConstraintCompletions: (text: string, position: Position) => Constraint[];
@@ -1,3 +0,0 @@
1
- import type { Datatype, Position } from "../../../types/completion";
2
- export declare const DATATYPES: string[];
3
- export declare const provideDatatypeCompletions: (text: string, position: Position) => Datatype[];
@@ -1,2 +0,0 @@
1
- import type { Distribution, Position } from "../../../types/completion";
2
- export declare const provideDistributionCompletions: (text: string, position: Position, distributions: string[]) => Distribution[];
@@ -1,2 +0,0 @@
1
- import type { StanFunction, Position } from "../../../types/completion";
2
- export declare const provideFunctionCompletions: (text: string, position: Position, functionSignatures: string[]) => StanFunction[];
@@ -1,2 +0,0 @@
1
- import type { Keyword, Position } from "../../../types/completion";
2
- export declare const provideKeywordCompletions: (text: string, position: Position) => Keyword[];
@@ -1,4 +0,0 @@
1
- import TrieSearch, { type TrieSearchOptions } from "trie-search";
2
- import type { Searchable, Position } from "../../types/completion";
3
- export declare const getSearchableItems: <T extends Searchable>(xs: T[], options?: TrieSearchOptions<T>) => TrieSearch<T>;
4
- export declare const getTextUpToCursor: (text: string, position: Position) => string;
@@ -1,15 +0,0 @@
1
- import type { Position } from "./common";
2
- export type { Position };
3
- export interface Searchable {
4
- name: string;
5
- }
6
- export interface Distribution extends Searchable {
7
- }
8
- export interface StanFunction extends Searchable {
9
- }
10
- export interface Keyword extends Searchable {
11
- }
12
- export interface Datatype extends Searchable {
13
- }
14
- export interface Constraint extends Searchable {
15
- }