├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── editors
└── code
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── icon
│ └── bend.png
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── src
│ ├── binaries.ts
│ └── extension.ts
│ ├── syntaxes
│ ├── bend.tmLanguage.json
│ └── language-configuration.json
│ ├── tsconfig.json
│ └── webpack.config.js
└── src
├── bin
└── bend-language-server.rs
├── core
├── diagnostics.rs
├── document.rs
├── mod.rs
└── semantic_token.rs
├── language
└── mod.rs
├── lib.rs
├── server
└── mod.rs
└── utils
├── color_wrapper.rs
├── lsp_log.rs
├── mod.rs
└── rope.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | node_modules
3 | out/
4 | .pnpm-debug.log
5 | *.ast
6 | dist/
7 | *.vsix
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.1.0",
4 | "configurations": [
5 | {
6 | "type": "extensionHost",
7 | "request": "launch",
8 | "name": "Launch Client",
9 | "runtimeExecutable": "${execPath}",
10 | "args": [
11 | "--extensionDevelopmentPath=${workspaceRoot}/editors/code"
12 | ],
13 | "outFiles": [
14 | "${workspaceRoot}/editors/code/out/**/*.js"
15 | ],
16 | "preLaunchTask": {
17 | "type": "npm",
18 | "script": "watch"
19 | },
20 | "env": {
21 | "BEND_LS_PATH": "${workspaceRoot}/target/debug/bend-language-server"
22 | }
23 | },
24 | // {
25 | // "type": "node",
26 | // "request": "attach",
27 | // "name": "Attach to Server",
28 | // "port": 6009,
29 | // "restart": true,
30 | // "outFiles": ["${workspaceRoot}/server/out/**/*.js"]
31 | // },
32 | {
33 | "name": "Language Server E2E Test",
34 | "type": "extensionHost",
35 | "request": "launch",
36 | "runtimeExecutable": "${execPath}",
37 | "args": [
38 | "--extensionDevelopmentPath=${workspaceRoot}",
39 | "--extensionTestsPath=${workspaceRoot}/editors/code/out/test/index",
40 | "${workspaceRoot}/editors/code/testFixture"
41 | ],
42 | "outFiles": [
43 | "${workspaceRoot}/editors/code/out/test/**/*.js"
44 | ]
45 | }
46 | ],
47 | "compounds": [
48 | {
49 | "name": "Client + Server",
50 | "configurations": [
51 | "Launch Client",
52 | // "Attach to Server"
53 | ]
54 | }
55 | ]
56 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "files.associations": {
12 | ".fantomasignore": "ignore",
13 | "unistd.h": "c"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "compile",
9 | "group": "build",
10 | "presentation": {
11 | "panel": "dedicated",
12 | "reveal": "never"
13 | },
14 | "problemMatcher": [
15 | "$tsc"
16 | ]
17 | },
18 | {
19 | "type": "npm",
20 | "script": "watch",
21 | "isBackground": true,
22 | "group": {
23 | "kind": "build",
24 | "isDefault": true
25 | },
26 | "presentation": {
27 | "panel": "dedicated",
28 | "reveal": "never"
29 | },
30 | "problemMatcher": [
31 | "$tsc-watch"
32 | ]
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to the `bend-language-server` Rust project will be documented in this file.
4 | To see changes related to the VSCode extension, see [editors/code/CHANGELOG.md](./editors/code/CHANGELOG.md).
5 |
6 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7 | and both this changelog and the crate's versioning scheme follow what the
8 | [Bend repository](https://github.com/HigherOrderCO/Bend) is using at the moment.
9 |
10 | ## [Unreleased]
11 |
12 | ## [0.2.37] - 2024-10-18
13 |
14 | - First full release
15 |
16 | ## [0.2.37-alpha.4] - 2024-09-02
17 |
18 | ### Fixed
19 |
20 | - Multi-byte characters highlighting other characters
21 |
22 | ## [0.2.37-alpha.3] - 2024-08-30
23 |
24 | ### Added
25 |
26 | - Multi-line (block) comments
27 |
28 | ### Fixed
29 |
30 | - Single-line comments
31 |
32 | ## [0.2.37-alpha.2] - 2024-08-27
33 |
34 | ### Added
35 |
36 | - `--version` command to executable
37 |
38 | ## [0.2.37-alpha.1] - 2024-08-23
39 |
40 | First release!
41 |
42 | ### Added
43 |
44 | - Semantic token highlighting through tree-sitter
45 | - Diagnostic reporting
46 |
47 |
48 | [0.2.37-alpha.3]: https://github.com/HigherOrderCO/bend-language-server/
49 | [0.2.37-alpha.2]: https://github.com/HigherOrderCO/bend-language-server/
50 | [0.2.37-alpha.1]: https://github.com/HigherOrderCO/bend-language-server/
51 | [Unreleased]: https://github.com/HigherOrderCO/bend-language-server/
52 |
53 | # Collaborators
54 |
55 | This project got inspiration from [Pedro Braga](https://github.com/mrpedrobraga)'s initial language server implementation and [Rohan Vashisht](https://github.com/RohanVashisht1234)'s VSCode extension. Thank you for your collaboration!
56 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "TSPL"
7 | version = "0.0.13"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "fe639519d49b56c98fd4fde7a5a7be01b5563862341a783b9bc2eb58f5120d8b"
10 | dependencies = [
11 | "highlight_error",
12 | ]
13 |
14 | [[package]]
15 | name = "addr2line"
16 | version = "0.22.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
19 | dependencies = [
20 | "gimli",
21 | ]
22 |
23 | [[package]]
24 | name = "adler"
25 | version = "1.0.2"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
28 |
29 | [[package]]
30 | name = "aho-corasick"
31 | version = "1.1.3"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
34 | dependencies = [
35 | "memchr",
36 | ]
37 |
38 | [[package]]
39 | name = "anstream"
40 | version = "0.6.15"
41 | source = "registry+https://github.com/rust-lang/crates.io-index"
42 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
43 | dependencies = [
44 | "anstyle",
45 | "anstyle-parse",
46 | "anstyle-query",
47 | "anstyle-wincon",
48 | "colorchoice",
49 | "is_terminal_polyfill",
50 | "utf8parse",
51 | ]
52 |
53 | [[package]]
54 | name = "anstyle"
55 | version = "1.0.8"
56 | source = "registry+https://github.com/rust-lang/crates.io-index"
57 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
58 |
59 | [[package]]
60 | name = "anstyle-parse"
61 | version = "0.2.5"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
64 | dependencies = [
65 | "utf8parse",
66 | ]
67 |
68 | [[package]]
69 | name = "anstyle-query"
70 | version = "1.1.1"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
73 | dependencies = [
74 | "windows-sys 0.52.0",
75 | ]
76 |
77 | [[package]]
78 | name = "anstyle-wincon"
79 | version = "3.0.4"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
82 | dependencies = [
83 | "anstyle",
84 | "windows-sys 0.52.0",
85 | ]
86 |
87 | [[package]]
88 | name = "anyhow"
89 | version = "1.0.86"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
92 |
93 | [[package]]
94 | name = "async-trait"
95 | version = "0.1.81"
96 | source = "registry+https://github.com/rust-lang/crates.io-index"
97 | checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
98 | dependencies = [
99 | "proc-macro2",
100 | "quote",
101 | "syn",
102 | ]
103 |
104 | [[package]]
105 | name = "auto_impl"
106 | version = "1.2.0"
107 | source = "registry+https://github.com/rust-lang/crates.io-index"
108 | checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
109 | dependencies = [
110 | "proc-macro2",
111 | "quote",
112 | "syn",
113 | ]
114 |
115 | [[package]]
116 | name = "autocfg"
117 | version = "1.3.0"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
120 |
121 | [[package]]
122 | name = "backtrace"
123 | version = "0.3.73"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
126 | dependencies = [
127 | "addr2line",
128 | "cc",
129 | "cfg-if",
130 | "libc",
131 | "miniz_oxide",
132 | "object",
133 | "rustc-demangle",
134 | ]
135 |
136 | [[package]]
137 | name = "bend-lang"
138 | version = "0.2.37"
139 | source = "registry+https://github.com/rust-lang/crates.io-index"
140 | checksum = "d6da1f56cfece78d2bf35587675f925ebc14672b160af952abed52d34668574e"
141 | dependencies = [
142 | "TSPL",
143 | "clap",
144 | "highlight_error",
145 | "hvm",
146 | "indexmap",
147 | "interner",
148 | "itertools 0.11.0",
149 | "loaned",
150 | "stacker",
151 | ]
152 |
153 | [[package]]
154 | name = "bend-language-server"
155 | version = "0.2.37"
156 | dependencies = [
157 | "anyhow",
158 | "bend-lang",
159 | "dashmap 6.0.1",
160 | "env_logger",
161 | "itertools 0.13.0",
162 | "lazy_static",
163 | "log",
164 | "regex",
165 | "ropey",
166 | "serde",
167 | "serde_json",
168 | "tokio",
169 | "tower-lsp",
170 | "tree-sitter",
171 | "tree-sitter-bend",
172 | "tree-sitter-highlight",
173 | ]
174 |
175 | [[package]]
176 | name = "bitflags"
177 | version = "1.3.2"
178 | source = "registry+https://github.com/rust-lang/crates.io-index"
179 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
180 |
181 | [[package]]
182 | name = "bitflags"
183 | version = "2.6.0"
184 | source = "registry+https://github.com/rust-lang/crates.io-index"
185 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
186 |
187 | [[package]]
188 | name = "bytes"
189 | version = "1.7.1"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
192 |
193 | [[package]]
194 | name = "cc"
195 | version = "1.1.14"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
198 | dependencies = [
199 | "shlex",
200 | ]
201 |
202 | [[package]]
203 | name = "cfg-if"
204 | version = "1.0.0"
205 | source = "registry+https://github.com/rust-lang/crates.io-index"
206 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
207 |
208 | [[package]]
209 | name = "clap"
210 | version = "4.5.16"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
213 | dependencies = [
214 | "clap_builder",
215 | "clap_derive",
216 | ]
217 |
218 | [[package]]
219 | name = "clap_builder"
220 | version = "4.5.15"
221 | source = "registry+https://github.com/rust-lang/crates.io-index"
222 | checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
223 | dependencies = [
224 | "anstream",
225 | "anstyle",
226 | "clap_lex",
227 | "strsim",
228 | ]
229 |
230 | [[package]]
231 | name = "clap_derive"
232 | version = "4.5.13"
233 | source = "registry+https://github.com/rust-lang/crates.io-index"
234 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
235 | dependencies = [
236 | "heck",
237 | "proc-macro2",
238 | "quote",
239 | "syn",
240 | ]
241 |
242 | [[package]]
243 | name = "clap_lex"
244 | version = "0.7.2"
245 | source = "registry+https://github.com/rust-lang/crates.io-index"
246 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
247 |
248 | [[package]]
249 | name = "colorchoice"
250 | version = "1.0.2"
251 | source = "registry+https://github.com/rust-lang/crates.io-index"
252 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
253 |
254 | [[package]]
255 | name = "crossbeam-utils"
256 | version = "0.8.20"
257 | source = "registry+https://github.com/rust-lang/crates.io-index"
258 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
259 |
260 | [[package]]
261 | name = "dashmap"
262 | version = "5.5.3"
263 | source = "registry+https://github.com/rust-lang/crates.io-index"
264 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
265 | dependencies = [
266 | "cfg-if",
267 | "hashbrown",
268 | "lock_api",
269 | "once_cell",
270 | "parking_lot_core",
271 | ]
272 |
273 | [[package]]
274 | name = "dashmap"
275 | version = "6.0.1"
276 | source = "registry+https://github.com/rust-lang/crates.io-index"
277 | checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28"
278 | dependencies = [
279 | "cfg-if",
280 | "crossbeam-utils",
281 | "hashbrown",
282 | "lock_api",
283 | "once_cell",
284 | "parking_lot_core",
285 | ]
286 |
287 | [[package]]
288 | name = "either"
289 | version = "1.13.0"
290 | source = "registry+https://github.com/rust-lang/crates.io-index"
291 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
292 |
293 | [[package]]
294 | name = "env_filter"
295 | version = "0.1.2"
296 | source = "registry+https://github.com/rust-lang/crates.io-index"
297 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
298 | dependencies = [
299 | "log",
300 | "regex",
301 | ]
302 |
303 | [[package]]
304 | name = "env_logger"
305 | version = "0.11.5"
306 | source = "registry+https://github.com/rust-lang/crates.io-index"
307 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
308 | dependencies = [
309 | "anstream",
310 | "anstyle",
311 | "env_filter",
312 | "humantime",
313 | "log",
314 | ]
315 |
316 | [[package]]
317 | name = "equivalent"
318 | version = "1.0.1"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
321 |
322 | [[package]]
323 | name = "form_urlencoded"
324 | version = "1.2.1"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
327 | dependencies = [
328 | "percent-encoding",
329 | ]
330 |
331 | [[package]]
332 | name = "futures"
333 | version = "0.3.30"
334 | source = "registry+https://github.com/rust-lang/crates.io-index"
335 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
336 | dependencies = [
337 | "futures-channel",
338 | "futures-core",
339 | "futures-io",
340 | "futures-sink",
341 | "futures-task",
342 | "futures-util",
343 | ]
344 |
345 | [[package]]
346 | name = "futures-channel"
347 | version = "0.3.30"
348 | source = "registry+https://github.com/rust-lang/crates.io-index"
349 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
350 | dependencies = [
351 | "futures-core",
352 | "futures-sink",
353 | ]
354 |
355 | [[package]]
356 | name = "futures-core"
357 | version = "0.3.30"
358 | source = "registry+https://github.com/rust-lang/crates.io-index"
359 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
360 |
361 | [[package]]
362 | name = "futures-io"
363 | version = "0.3.30"
364 | source = "registry+https://github.com/rust-lang/crates.io-index"
365 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
366 |
367 | [[package]]
368 | name = "futures-macro"
369 | version = "0.3.30"
370 | source = "registry+https://github.com/rust-lang/crates.io-index"
371 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
372 | dependencies = [
373 | "proc-macro2",
374 | "quote",
375 | "syn",
376 | ]
377 |
378 | [[package]]
379 | name = "futures-sink"
380 | version = "0.3.30"
381 | source = "registry+https://github.com/rust-lang/crates.io-index"
382 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
383 |
384 | [[package]]
385 | name = "futures-task"
386 | version = "0.3.30"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
389 |
390 | [[package]]
391 | name = "futures-util"
392 | version = "0.3.30"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
395 | dependencies = [
396 | "futures-channel",
397 | "futures-core",
398 | "futures-io",
399 | "futures-macro",
400 | "futures-sink",
401 | "futures-task",
402 | "memchr",
403 | "pin-project-lite",
404 | "pin-utils",
405 | "slab",
406 | ]
407 |
408 | [[package]]
409 | name = "gimli"
410 | version = "0.29.0"
411 | source = "registry+https://github.com/rust-lang/crates.io-index"
412 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
413 |
414 | [[package]]
415 | name = "hashbrown"
416 | version = "0.14.5"
417 | source = "registry+https://github.com/rust-lang/crates.io-index"
418 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
419 |
420 | [[package]]
421 | name = "heck"
422 | version = "0.5.0"
423 | source = "registry+https://github.com/rust-lang/crates.io-index"
424 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
425 |
426 | [[package]]
427 | name = "hermit-abi"
428 | version = "0.3.9"
429 | source = "registry+https://github.com/rust-lang/crates.io-index"
430 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
431 |
432 | [[package]]
433 | name = "highlight_error"
434 | version = "0.1.1"
435 | source = "registry+https://github.com/rust-lang/crates.io-index"
436 | checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03"
437 |
438 | [[package]]
439 | name = "httparse"
440 | version = "1.9.4"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
443 |
444 | [[package]]
445 | name = "humantime"
446 | version = "2.1.0"
447 | source = "registry+https://github.com/rust-lang/crates.io-index"
448 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
449 |
450 | [[package]]
451 | name = "hvm"
452 | version = "2.0.22"
453 | source = "registry+https://github.com/rust-lang/crates.io-index"
454 | checksum = "3fafa02949c005e70869074ac9db489ad92eaf92c78b4dcf6c0b45d98982c08d"
455 | dependencies = [
456 | "TSPL",
457 | "cc",
458 | "clap",
459 | "highlight_error",
460 | "num_cpus",
461 | ]
462 |
463 | [[package]]
464 | name = "idna"
465 | version = "0.5.0"
466 | source = "registry+https://github.com/rust-lang/crates.io-index"
467 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
468 | dependencies = [
469 | "unicode-bidi",
470 | "unicode-normalization",
471 | ]
472 |
473 | [[package]]
474 | name = "indexmap"
475 | version = "2.4.0"
476 | source = "registry+https://github.com/rust-lang/crates.io-index"
477 | checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
478 | dependencies = [
479 | "equivalent",
480 | "hashbrown",
481 | ]
482 |
483 | [[package]]
484 | name = "interner"
485 | version = "0.2.1"
486 | source = "registry+https://github.com/rust-lang/crates.io-index"
487 | checksum = "e8c60687056b35a996f2213287048a7092d801b61df5fee3bd5bd9bf6f17a2d0"
488 |
489 | [[package]]
490 | name = "is_terminal_polyfill"
491 | version = "1.70.1"
492 | source = "registry+https://github.com/rust-lang/crates.io-index"
493 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
494 |
495 | [[package]]
496 | name = "itertools"
497 | version = "0.11.0"
498 | source = "registry+https://github.com/rust-lang/crates.io-index"
499 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
500 | dependencies = [
501 | "either",
502 | ]
503 |
504 | [[package]]
505 | name = "itertools"
506 | version = "0.13.0"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
509 | dependencies = [
510 | "either",
511 | ]
512 |
513 | [[package]]
514 | name = "itoa"
515 | version = "1.0.11"
516 | source = "registry+https://github.com/rust-lang/crates.io-index"
517 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
518 |
519 | [[package]]
520 | name = "lazy_static"
521 | version = "1.5.0"
522 | source = "registry+https://github.com/rust-lang/crates.io-index"
523 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
524 |
525 | [[package]]
526 | name = "libc"
527 | version = "0.2.158"
528 | source = "registry+https://github.com/rust-lang/crates.io-index"
529 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
530 |
531 | [[package]]
532 | name = "loaned"
533 | version = "0.1.2"
534 | source = "registry+https://github.com/rust-lang/crates.io-index"
535 | checksum = "a4c980a418236e2d8f7c239f73e49afc38e7f71772fcd3fc723d95c3d93a7591"
536 |
537 | [[package]]
538 | name = "lock_api"
539 | version = "0.4.12"
540 | source = "registry+https://github.com/rust-lang/crates.io-index"
541 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
542 | dependencies = [
543 | "autocfg",
544 | "scopeguard",
545 | ]
546 |
547 | [[package]]
548 | name = "log"
549 | version = "0.4.22"
550 | source = "registry+https://github.com/rust-lang/crates.io-index"
551 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
552 |
553 | [[package]]
554 | name = "lsp-types"
555 | version = "0.94.1"
556 | source = "registry+https://github.com/rust-lang/crates.io-index"
557 | checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
558 | dependencies = [
559 | "bitflags 1.3.2",
560 | "serde",
561 | "serde_json",
562 | "serde_repr",
563 | "url",
564 | ]
565 |
566 | [[package]]
567 | name = "memchr"
568 | version = "2.7.4"
569 | source = "registry+https://github.com/rust-lang/crates.io-index"
570 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
571 |
572 | [[package]]
573 | name = "miniz_oxide"
574 | version = "0.7.4"
575 | source = "registry+https://github.com/rust-lang/crates.io-index"
576 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
577 | dependencies = [
578 | "adler",
579 | ]
580 |
581 | [[package]]
582 | name = "num_cpus"
583 | version = "1.16.0"
584 | source = "registry+https://github.com/rust-lang/crates.io-index"
585 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
586 | dependencies = [
587 | "hermit-abi",
588 | "libc",
589 | ]
590 |
591 | [[package]]
592 | name = "object"
593 | version = "0.36.3"
594 | source = "registry+https://github.com/rust-lang/crates.io-index"
595 | checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
596 | dependencies = [
597 | "memchr",
598 | ]
599 |
600 | [[package]]
601 | name = "once_cell"
602 | version = "1.19.0"
603 | source = "registry+https://github.com/rust-lang/crates.io-index"
604 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
605 |
606 | [[package]]
607 | name = "parking_lot_core"
608 | version = "0.9.10"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
611 | dependencies = [
612 | "cfg-if",
613 | "libc",
614 | "redox_syscall",
615 | "smallvec",
616 | "windows-targets",
617 | ]
618 |
619 | [[package]]
620 | name = "percent-encoding"
621 | version = "2.3.1"
622 | source = "registry+https://github.com/rust-lang/crates.io-index"
623 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
624 |
625 | [[package]]
626 | name = "pin-project"
627 | version = "1.1.5"
628 | source = "registry+https://github.com/rust-lang/crates.io-index"
629 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
630 | dependencies = [
631 | "pin-project-internal",
632 | ]
633 |
634 | [[package]]
635 | name = "pin-project-internal"
636 | version = "1.1.5"
637 | source = "registry+https://github.com/rust-lang/crates.io-index"
638 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
639 | dependencies = [
640 | "proc-macro2",
641 | "quote",
642 | "syn",
643 | ]
644 |
645 | [[package]]
646 | name = "pin-project-lite"
647 | version = "0.2.14"
648 | source = "registry+https://github.com/rust-lang/crates.io-index"
649 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
650 |
651 | [[package]]
652 | name = "pin-utils"
653 | version = "0.1.0"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
656 |
657 | [[package]]
658 | name = "proc-macro2"
659 | version = "1.0.86"
660 | source = "registry+https://github.com/rust-lang/crates.io-index"
661 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
662 | dependencies = [
663 | "unicode-ident",
664 | ]
665 |
666 | [[package]]
667 | name = "psm"
668 | version = "0.1.21"
669 | source = "registry+https://github.com/rust-lang/crates.io-index"
670 | checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
671 | dependencies = [
672 | "cc",
673 | ]
674 |
675 | [[package]]
676 | name = "quote"
677 | version = "1.0.37"
678 | source = "registry+https://github.com/rust-lang/crates.io-index"
679 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
680 | dependencies = [
681 | "proc-macro2",
682 | ]
683 |
684 | [[package]]
685 | name = "redox_syscall"
686 | version = "0.5.3"
687 | source = "registry+https://github.com/rust-lang/crates.io-index"
688 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
689 | dependencies = [
690 | "bitflags 2.6.0",
691 | ]
692 |
693 | [[package]]
694 | name = "regex"
695 | version = "1.10.6"
696 | source = "registry+https://github.com/rust-lang/crates.io-index"
697 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
698 | dependencies = [
699 | "aho-corasick",
700 | "memchr",
701 | "regex-automata",
702 | "regex-syntax",
703 | ]
704 |
705 | [[package]]
706 | name = "regex-automata"
707 | version = "0.4.7"
708 | source = "registry+https://github.com/rust-lang/crates.io-index"
709 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
710 | dependencies = [
711 | "aho-corasick",
712 | "memchr",
713 | "regex-syntax",
714 | ]
715 |
716 | [[package]]
717 | name = "regex-syntax"
718 | version = "0.8.4"
719 | source = "registry+https://github.com/rust-lang/crates.io-index"
720 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
721 |
722 | [[package]]
723 | name = "ropey"
724 | version = "1.6.1"
725 | source = "registry+https://github.com/rust-lang/crates.io-index"
726 | checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
727 | dependencies = [
728 | "smallvec",
729 | "str_indices",
730 | ]
731 |
732 | [[package]]
733 | name = "rustc-demangle"
734 | version = "0.1.24"
735 | source = "registry+https://github.com/rust-lang/crates.io-index"
736 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
737 |
738 | [[package]]
739 | name = "ryu"
740 | version = "1.0.18"
741 | source = "registry+https://github.com/rust-lang/crates.io-index"
742 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
743 |
744 | [[package]]
745 | name = "scopeguard"
746 | version = "1.2.0"
747 | source = "registry+https://github.com/rust-lang/crates.io-index"
748 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
749 |
750 | [[package]]
751 | name = "serde"
752 | version = "1.0.208"
753 | source = "registry+https://github.com/rust-lang/crates.io-index"
754 | checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
755 | dependencies = [
756 | "serde_derive",
757 | ]
758 |
759 | [[package]]
760 | name = "serde_derive"
761 | version = "1.0.208"
762 | source = "registry+https://github.com/rust-lang/crates.io-index"
763 | checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
764 | dependencies = [
765 | "proc-macro2",
766 | "quote",
767 | "syn",
768 | ]
769 |
770 | [[package]]
771 | name = "serde_json"
772 | version = "1.0.125"
773 | source = "registry+https://github.com/rust-lang/crates.io-index"
774 | checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
775 | dependencies = [
776 | "itoa",
777 | "memchr",
778 | "ryu",
779 | "serde",
780 | ]
781 |
782 | [[package]]
783 | name = "serde_repr"
784 | version = "0.1.19"
785 | source = "registry+https://github.com/rust-lang/crates.io-index"
786 | checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
787 | dependencies = [
788 | "proc-macro2",
789 | "quote",
790 | "syn",
791 | ]
792 |
793 | [[package]]
794 | name = "shlex"
795 | version = "1.3.0"
796 | source = "registry+https://github.com/rust-lang/crates.io-index"
797 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
798 |
799 | [[package]]
800 | name = "slab"
801 | version = "0.4.9"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
804 | dependencies = [
805 | "autocfg",
806 | ]
807 |
808 | [[package]]
809 | name = "smallvec"
810 | version = "1.13.2"
811 | source = "registry+https://github.com/rust-lang/crates.io-index"
812 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
813 |
814 | [[package]]
815 | name = "stacker"
816 | version = "0.1.16"
817 | source = "registry+https://github.com/rust-lang/crates.io-index"
818 | checksum = "95a5daa25ea337c85ed954c0496e3bdd2c7308cc3b24cf7b50d04876654c579f"
819 | dependencies = [
820 | "cc",
821 | "cfg-if",
822 | "libc",
823 | "psm",
824 | "windows-sys 0.36.1",
825 | ]
826 |
827 | [[package]]
828 | name = "str_indices"
829 | version = "0.4.3"
830 | source = "registry+https://github.com/rust-lang/crates.io-index"
831 | checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c"
832 |
833 | [[package]]
834 | name = "strsim"
835 | version = "0.11.1"
836 | source = "registry+https://github.com/rust-lang/crates.io-index"
837 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
838 |
839 | [[package]]
840 | name = "syn"
841 | version = "2.0.75"
842 | source = "registry+https://github.com/rust-lang/crates.io-index"
843 | checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
844 | dependencies = [
845 | "proc-macro2",
846 | "quote",
847 | "unicode-ident",
848 | ]
849 |
850 | [[package]]
851 | name = "thiserror"
852 | version = "1.0.63"
853 | source = "registry+https://github.com/rust-lang/crates.io-index"
854 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
855 | dependencies = [
856 | "thiserror-impl",
857 | ]
858 |
859 | [[package]]
860 | name = "thiserror-impl"
861 | version = "1.0.63"
862 | source = "registry+https://github.com/rust-lang/crates.io-index"
863 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
864 | dependencies = [
865 | "proc-macro2",
866 | "quote",
867 | "syn",
868 | ]
869 |
870 | [[package]]
871 | name = "tinyvec"
872 | version = "1.8.0"
873 | source = "registry+https://github.com/rust-lang/crates.io-index"
874 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
875 | dependencies = [
876 | "tinyvec_macros",
877 | ]
878 |
879 | [[package]]
880 | name = "tinyvec_macros"
881 | version = "0.1.1"
882 | source = "registry+https://github.com/rust-lang/crates.io-index"
883 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
884 |
885 | [[package]]
886 | name = "tokio"
887 | version = "1.39.3"
888 | source = "registry+https://github.com/rust-lang/crates.io-index"
889 | checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
890 | dependencies = [
891 | "backtrace",
892 | "bytes",
893 | "pin-project-lite",
894 | "tokio-macros",
895 | ]
896 |
897 | [[package]]
898 | name = "tokio-macros"
899 | version = "2.4.0"
900 | source = "registry+https://github.com/rust-lang/crates.io-index"
901 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
902 | dependencies = [
903 | "proc-macro2",
904 | "quote",
905 | "syn",
906 | ]
907 |
908 | [[package]]
909 | name = "tokio-util"
910 | version = "0.7.11"
911 | source = "registry+https://github.com/rust-lang/crates.io-index"
912 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
913 | dependencies = [
914 | "bytes",
915 | "futures-core",
916 | "futures-sink",
917 | "pin-project-lite",
918 | "tokio",
919 | ]
920 |
921 | [[package]]
922 | name = "tower"
923 | version = "0.4.13"
924 | source = "registry+https://github.com/rust-lang/crates.io-index"
925 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
926 | dependencies = [
927 | "futures-core",
928 | "futures-util",
929 | "pin-project",
930 | "pin-project-lite",
931 | "tower-layer",
932 | "tower-service",
933 | ]
934 |
935 | [[package]]
936 | name = "tower-layer"
937 | version = "0.3.3"
938 | source = "registry+https://github.com/rust-lang/crates.io-index"
939 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
940 |
941 | [[package]]
942 | name = "tower-lsp"
943 | version = "0.20.0"
944 | source = "registry+https://github.com/rust-lang/crates.io-index"
945 | checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508"
946 | dependencies = [
947 | "async-trait",
948 | "auto_impl",
949 | "bytes",
950 | "dashmap 5.5.3",
951 | "futures",
952 | "httparse",
953 | "lsp-types",
954 | "memchr",
955 | "serde",
956 | "serde_json",
957 | "tokio",
958 | "tokio-util",
959 | "tower",
960 | "tower-lsp-macros",
961 | "tracing",
962 | ]
963 |
964 | [[package]]
965 | name = "tower-lsp-macros"
966 | version = "0.9.0"
967 | source = "registry+https://github.com/rust-lang/crates.io-index"
968 | checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
969 | dependencies = [
970 | "proc-macro2",
971 | "quote",
972 | "syn",
973 | ]
974 |
975 | [[package]]
976 | name = "tower-service"
977 | version = "0.3.3"
978 | source = "registry+https://github.com/rust-lang/crates.io-index"
979 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
980 |
981 | [[package]]
982 | name = "tracing"
983 | version = "0.1.40"
984 | source = "registry+https://github.com/rust-lang/crates.io-index"
985 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
986 | dependencies = [
987 | "pin-project-lite",
988 | "tracing-attributes",
989 | "tracing-core",
990 | ]
991 |
992 | [[package]]
993 | name = "tracing-attributes"
994 | version = "0.1.27"
995 | source = "registry+https://github.com/rust-lang/crates.io-index"
996 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
997 | dependencies = [
998 | "proc-macro2",
999 | "quote",
1000 | "syn",
1001 | ]
1002 |
1003 | [[package]]
1004 | name = "tracing-core"
1005 | version = "0.1.32"
1006 | source = "registry+https://github.com/rust-lang/crates.io-index"
1007 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
1008 | dependencies = [
1009 | "once_cell",
1010 | ]
1011 |
1012 | [[package]]
1013 | name = "tree-sitter"
1014 | version = "0.22.6"
1015 | source = "registry+https://github.com/rust-lang/crates.io-index"
1016 | checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca"
1017 | dependencies = [
1018 | "cc",
1019 | "regex",
1020 | ]
1021 |
1022 | [[package]]
1023 | name = "tree-sitter-bend"
1024 | version = "0.2.37"
1025 | source = "registry+https://github.com/rust-lang/crates.io-index"
1026 | checksum = "681baa666ba4448f3de953a94c3ef3593d9cf3684626bddeee665610feed61fc"
1027 | dependencies = [
1028 | "cc",
1029 | "tree-sitter",
1030 | ]
1031 |
1032 | [[package]]
1033 | name = "tree-sitter-highlight"
1034 | version = "0.22.6"
1035 | source = "registry+https://github.com/rust-lang/crates.io-index"
1036 | checksum = "eaca0fe34fa96eec6aaa8e63308dbe1bafe65a6317487c287f93938959b21907"
1037 | dependencies = [
1038 | "lazy_static",
1039 | "regex",
1040 | "thiserror",
1041 | "tree-sitter",
1042 | ]
1043 |
1044 | [[package]]
1045 | name = "unicode-bidi"
1046 | version = "0.3.15"
1047 | source = "registry+https://github.com/rust-lang/crates.io-index"
1048 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
1049 |
1050 | [[package]]
1051 | name = "unicode-ident"
1052 | version = "1.0.12"
1053 | source = "registry+https://github.com/rust-lang/crates.io-index"
1054 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
1055 |
1056 | [[package]]
1057 | name = "unicode-normalization"
1058 | version = "0.1.23"
1059 | source = "registry+https://github.com/rust-lang/crates.io-index"
1060 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
1061 | dependencies = [
1062 | "tinyvec",
1063 | ]
1064 |
1065 | [[package]]
1066 | name = "url"
1067 | version = "2.5.2"
1068 | source = "registry+https://github.com/rust-lang/crates.io-index"
1069 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
1070 | dependencies = [
1071 | "form_urlencoded",
1072 | "idna",
1073 | "percent-encoding",
1074 | "serde",
1075 | ]
1076 |
1077 | [[package]]
1078 | name = "utf8parse"
1079 | version = "0.2.2"
1080 | source = "registry+https://github.com/rust-lang/crates.io-index"
1081 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1082 |
1083 | [[package]]
1084 | name = "windows-sys"
1085 | version = "0.36.1"
1086 | source = "registry+https://github.com/rust-lang/crates.io-index"
1087 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
1088 | dependencies = [
1089 | "windows_aarch64_msvc 0.36.1",
1090 | "windows_i686_gnu 0.36.1",
1091 | "windows_i686_msvc 0.36.1",
1092 | "windows_x86_64_gnu 0.36.1",
1093 | "windows_x86_64_msvc 0.36.1",
1094 | ]
1095 |
1096 | [[package]]
1097 | name = "windows-sys"
1098 | version = "0.52.0"
1099 | source = "registry+https://github.com/rust-lang/crates.io-index"
1100 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1101 | dependencies = [
1102 | "windows-targets",
1103 | ]
1104 |
1105 | [[package]]
1106 | name = "windows-targets"
1107 | version = "0.52.6"
1108 | source = "registry+https://github.com/rust-lang/crates.io-index"
1109 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1110 | dependencies = [
1111 | "windows_aarch64_gnullvm",
1112 | "windows_aarch64_msvc 0.52.6",
1113 | "windows_i686_gnu 0.52.6",
1114 | "windows_i686_gnullvm",
1115 | "windows_i686_msvc 0.52.6",
1116 | "windows_x86_64_gnu 0.52.6",
1117 | "windows_x86_64_gnullvm",
1118 | "windows_x86_64_msvc 0.52.6",
1119 | ]
1120 |
1121 | [[package]]
1122 | name = "windows_aarch64_gnullvm"
1123 | version = "0.52.6"
1124 | source = "registry+https://github.com/rust-lang/crates.io-index"
1125 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1126 |
1127 | [[package]]
1128 | name = "windows_aarch64_msvc"
1129 | version = "0.36.1"
1130 | source = "registry+https://github.com/rust-lang/crates.io-index"
1131 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
1132 |
1133 | [[package]]
1134 | name = "windows_aarch64_msvc"
1135 | version = "0.52.6"
1136 | source = "registry+https://github.com/rust-lang/crates.io-index"
1137 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1138 |
1139 | [[package]]
1140 | name = "windows_i686_gnu"
1141 | version = "0.36.1"
1142 | source = "registry+https://github.com/rust-lang/crates.io-index"
1143 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
1144 |
1145 | [[package]]
1146 | name = "windows_i686_gnu"
1147 | version = "0.52.6"
1148 | source = "registry+https://github.com/rust-lang/crates.io-index"
1149 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1150 |
1151 | [[package]]
1152 | name = "windows_i686_gnullvm"
1153 | version = "0.52.6"
1154 | source = "registry+https://github.com/rust-lang/crates.io-index"
1155 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1156 |
1157 | [[package]]
1158 | name = "windows_i686_msvc"
1159 | version = "0.36.1"
1160 | source = "registry+https://github.com/rust-lang/crates.io-index"
1161 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
1162 |
1163 | [[package]]
1164 | name = "windows_i686_msvc"
1165 | version = "0.52.6"
1166 | source = "registry+https://github.com/rust-lang/crates.io-index"
1167 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1168 |
1169 | [[package]]
1170 | name = "windows_x86_64_gnu"
1171 | version = "0.36.1"
1172 | source = "registry+https://github.com/rust-lang/crates.io-index"
1173 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
1174 |
1175 | [[package]]
1176 | name = "windows_x86_64_gnu"
1177 | version = "0.52.6"
1178 | source = "registry+https://github.com/rust-lang/crates.io-index"
1179 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1180 |
1181 | [[package]]
1182 | name = "windows_x86_64_gnullvm"
1183 | version = "0.52.6"
1184 | source = "registry+https://github.com/rust-lang/crates.io-index"
1185 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1186 |
1187 | [[package]]
1188 | name = "windows_x86_64_msvc"
1189 | version = "0.36.1"
1190 | source = "registry+https://github.com/rust-lang/crates.io-index"
1191 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
1192 |
1193 | [[package]]
1194 | name = "windows_x86_64_msvc"
1195 | version = "0.52.6"
1196 | source = "registry+https://github.com/rust-lang/crates.io-index"
1197 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1198 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bend-language-server"
3 | version = "0.2.37"
4 | description = "Language server for the Bend programming language"
5 | edition = "2021"
6 | license-file = "LICENSE"
7 | repository = "https://github.com/HigherOrderCO/bend-language-server"
8 | homepage = "https://higherorderco.com/"
9 | readme = "README.md"
10 | keywords = ["bend", "language-server", "lsp"]
11 | categories = ["text-editors", "development-tools"]
12 | exclude = [
13 | ".vscode",
14 | "editors",
15 | ".vscodeignore",
16 | "package.json",
17 | "pnpm-lock.yaml",
18 | "tsconfig.json",
19 | "webpack.config.js",
20 | ]
21 |
22 | [dependencies]
23 | bend-lang = { version = "0.2.37" }
24 | tree-sitter-bend = { version = "0.2.37" }
25 | tower-lsp = { version = "0.20", features = ["proposed"] }
26 | tokio = { version = "1.39", features = [
27 | "sync",
28 | "macros",
29 | "io-util",
30 | "rt",
31 | "rt-multi-thread",
32 | "io-std",
33 | "time",
34 | ] }
35 | serde = { version = "1.0", features = ["derive"] }
36 | anyhow = "1.0"
37 | tree-sitter = "0.22"
38 | tree-sitter-highlight = "0.22"
39 | serde_json = "1.0"
40 | log = "0.4"
41 | env_logger = "0.11"
42 | dashmap = "6.0"
43 | lazy_static = "1.5"
44 | itertools = "0.13"
45 | ropey = "1.6"
46 | regex = "1.7"
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 HigherOrderCO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bend-language-server
2 |
3 | Language server for the Bend programming language based on the Language Server Protocol (LSP).
4 |
5 | ## Features
6 |
7 | The current features implemented in this language server are
8 |
9 | - Semantic token highlighting
10 | - Code highlighting using the [Bend tree sitter grammar](https://github.com/higherOrderCO/tree-sitter-bend)
11 | - Diagnostic reporting
12 | - Reports compilation warnings, errors, and other information
13 |
14 | We accept contributions and feature requests!
15 |
16 | ## Installation
17 |
18 | ### VSCode extension
19 |
20 | As of this alpha version, we still don't publish compiled releases of the language server, so you'll need to install the [Rust toolchain](https://rustup.rs) to compile it. Afterwards, install the VSCode extension and open a `.bend` file.
21 |
22 | On startup, the extension will ask you if you want it to automatically install the language server executable or if you want to set it up manually with the PATH environment variable. If you choose automatically, the extension will use `cargo` to install it to its local storage. If you want to install it manually, run the following command:
23 |
24 | ```
25 | cargo install bend-language-server --version 0.2.37
26 | ```
27 |
28 | Managing the language server manually will require you to update the language server yourself as new versions are published.
29 |
30 | ### Other editors
31 |
32 | We are still not officially supporting other editors; however, if your editor of choice has support for the Language Server Protocol (LSP), you can try plugging it and our language server together. To install the LSP-compliant language server binary, use the Cargo command from the [Rust toolchain](https://rustup.rs):
33 |
34 | ```
35 | cargo install bend-language-server --version 0.2.37
36 | ```
37 |
38 | If the toolchain is correctly installed, `bend-language-server` should now be in your path.
39 |
40 | We also have a [tree-sitter grammar](https://github.com/HigherOrderCO/tree-sitter-bend) for syntax highlighting with configuration instructions for Neovim.
41 |
42 | ## Development
43 |
44 | Currently, the language server is only developed and tested for VSCode. Feel free to add contributions specific to other code editors!
45 |
46 | ### VSCode extension
47 |
48 | This project requires the [Rust toolchain](https://rustup.rs) and [pnpm](https://pnpm.io) to build.
49 |
50 | 1. Navigate to the extension directory with `cd editors/code`.
51 | 2. Prepare the Javascript environment with `pnpm i`.
52 | 3. Build the language server with `cargo build`.
53 | 4. Open the project with VSCode. Press F5 or click Launch Client in the Debug panel.
54 |
55 | This should result in a new VSCode window being opened with the `bend-language-server` extension loaded.
56 |
--------------------------------------------------------------------------------
/editors/code/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project does not currently adhere to a particular versioning scheme.
7 |
8 | ## [0.3.1] - 2024-09-02
9 |
10 | From this version on, the VSCode extension version will not directly correlate to a specific Bend version.
11 |
12 | ### Added
13 |
14 | - Multi-line (block) comments
15 |
16 | ### Fixed
17 |
18 | - Single line comments
19 |
20 | ## [0.2.37-alpha.1] - 2024-08-23
21 |
22 | First release!
23 |
24 | ### Added
25 |
26 | - Semantic token highlighting through tree-sitter
27 | - Diagnostic reporting
28 |
29 |
30 | [0.3.1]: https://github.com/HigherOrderCO/bend-language-server/
31 | [0.2.37-alpha.1]: https://github.com/HigherOrderCO/bend-language-server/
32 | [Unreleased]: https://github.com/HigherOrderCO/bend-language-server/
33 |
34 | # Collaborators
35 |
36 | This project got inspiration from [Pedro Braga](https://github.com/mrpedrobraga)'s initial language server implementation and [Rohan Vashisht](https://github.com/RohanVashisht1234)'s VSCode extension. Thank you for your collaboration!
37 |
--------------------------------------------------------------------------------
/editors/code/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 HigherOrderCO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/editors/code/README.md:
--------------------------------------------------------------------------------
1 | # bend-language-server
2 |
3 | Language server for the Bend programming language based on the Language Server Protocol (LSP).
4 |
5 | ## Features
6 |
7 | The current features implemented in this language server are
8 |
9 | - Semantic token highlighting
10 | - Code highlighting using the [Bend tree sitter grammar](https://github.com/higherOrderCO/tree-sitter-bend)
11 | - Diagnostic reporting
12 | - Reports compilation warnings, errors, and other information
13 |
14 | We accept contributions and feature requests!
15 |
16 | ## Installation
17 |
18 | ### VSCode extension
19 |
20 | As of this alpha version, we still don't publish compiled releases of the language server, so you'll need to install the [Rust toolchain](https://rustup.rs) to compile it. Afterwards, install the VSCode extension and open a `.bend` file.
21 |
22 | On startup, the extension will ask you if you want it to automatically install the language server executable or if you want to set it up manually with the PATH environment variable. If you choose automatically, the extension will use `cargo` to install it to its local storage. If you want to install it manually, run the following command:
23 |
24 | ```
25 | cargo install bend-language-server --version 0.2.37
26 | ```
27 |
28 | Managing the language server manually will require you to update the language server yourself as new versions are published.
29 |
30 | ### Other editors
31 |
32 | We are still not officially supporting other editors; however, if your editor of choice has support for the Language Server Protocol (LSP), you can try plugging it and our language server together. To install the LSP-compliant language server binary, use the Cargo command from the [Rust toolchain](https://rustup.rs):
33 |
34 | ```
35 | cargo install bend-language-server --version 0.2.37
36 | ```
37 |
38 | If the toolchain is correctly installed, `bend-language-server` should now be in your path.
39 |
40 | We also have a [tree-sitter grammar](https://github.com/HigherOrderCO/tree-sitter-bend) for syntax highlighting with configuration instructions for Neovim.
41 |
42 | ## Development
43 |
44 | Currently, the language server is only developed and tested for VSCode. Feel free to add contributions specific to other code editors!
45 |
46 | ### VSCode extension
47 |
48 | This project requires the [Rust toolchain](https://rustup.rs) and [pnpm](https://pnpm.io) to build.
49 |
50 | 1. Navigate to the extension directory with `cd editors/code`.
51 | 2. Prepare the Javascript environment with `pnpm i`.
52 | 3. Build the language server with `cargo build`.
53 | 4. Open the project with VSCode. Press F5 or click Launch Client in the Debug panel.
54 |
55 | This should result in a new VSCode window being opened with the `bend-language-server` extension loaded.
56 |
--------------------------------------------------------------------------------
/editors/code/icon/bend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HigherOrderCO/bend-language-server/a1cfbf54f4ca261182a4c25496ff4d77c64dcb77/editors/code/icon/bend.png
--------------------------------------------------------------------------------
/editors/code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bend-language-server",
3 | "description": "Language server for the Bend programming language",
4 | "publisher": "HigherOrderCompany",
5 | "license": "MIT",
6 | "version": "0.3.1",
7 | "categories": [],
8 | "icon": "icon/bend.png",
9 | "keywords": [
10 | "bend",
11 | "language-server",
12 | "bend-language-server",
13 | "bend-lsp"
14 | ],
15 | "repository": {
16 | "url": "https://github.com/HigherOrderCO/bend-language-server"
17 | },
18 | "engines": {
19 | "vscode": "^1.85.0"
20 | },
21 | "activationEvents": [
22 | "onLanguage:bend"
23 | ],
24 | "main": "./dist/extension.js",
25 | "contributes": {
26 | "languages": [
27 | {
28 | "id": "bend",
29 | "extensions": [
30 | ".bend"
31 | ],
32 | "configuration": "syntaxes/language-configuration.json"
33 | }
34 | ],
35 | "configuration": {
36 | "type": "object",
37 | "title": "bend-language-server",
38 | "properties": {
39 | "bend-language-server.trace.server": {
40 | "type": "string",
41 | "scope": "window",
42 | "enum": [
43 | "off",
44 | "messages",
45 | "verbose"
46 | ],
47 | "enumDescriptions": [
48 | "No traces",
49 | "Error only",
50 | "Full log"
51 | ],
52 | "default": "off",
53 | "description": "Traces the communication between VS Code and the language server."
54 | },
55 | "bend.manageLanguageServer": {
56 | "scope": "resource",
57 | "type": "string",
58 | "default": "automatic",
59 | "markdownDescription": "Manager for the `bend-language-server` binary. Can be overridden by `'serverExecutablePath'`",
60 | "enum": [
61 | "automatic",
62 | "PATH"
63 | ],
64 | "markdownEnumDescriptions": [
65 | "Automatically download and compile the newest version of `bend-language-server`.",
66 | "Get `bend-language-server` from the PATH environment variable."
67 | ]
68 | },
69 | "bend.serverExecutablePath": {
70 | "scope": "machine-overridable",
71 | "type": "string",
72 | "default": "",
73 | "markdownDescription": "Manually set a language server executable location."
74 | },
75 | "bend.cargoExecutablePath": {
76 | "scope": "resource",
77 | "type": "string",
78 | "default": "",
79 | "markdownDescription": "Manually set a Cargo executable path."
80 | }
81 | }
82 | },
83 | "commands": [
84 | {
85 | "command": "bend.commands.restartServer",
86 | "title": "Bend: Restart Language Server",
87 | "description": "Restarts the Bend language server extension"
88 | },
89 | {
90 | "command": "bend.commands.stopServer",
91 | "title": "Bend: Stop Language Server",
92 | "description": "Stops the Bend language server extension"
93 | }
94 | ],
95 | "grammars": [
96 | {
97 | "language": "bend",
98 | "scopeName": "source.bend",
99 | "path": "syntaxes/bend.tmLanguage.json"
100 | }
101 | ]
102 | },
103 | "scripts": {
104 | "vscode:prepublish": "npm run build",
105 | "test-compile": "tsc -p ./",
106 | "compile": "cross-env NODE_ENV=production tsc -b",
107 | "watch": "rm -rf dist && tsc -b -w",
108 | "lint": "eslint src --ext ts",
109 | "pretest": "npm run compile && npm run lint",
110 | "test": "node ./out/test/runTest.js",
111 | "build": "webpack --config webpack.config.js",
112 | "package": "vsce package --no-dependencies",
113 | "publish": "vsce publish --no-dependencies",
114 | "package-pre-release": "vsce package --pre-release --no-dependencies",
115 | "publish-pre-release": "vsce publish --pre-release --no-dependencies"
116 | },
117 | "devDependencies": {
118 | "@electron/rebuild": "^3.6.0",
119 | "@types/glob": "^8.1.0",
120 | "@types/mocha": "^10.0.7",
121 | "@types/node": "^22.4.1",
122 | "@types/which": "^3.0.4",
123 | "@types/vscode": "1.85.0",
124 | "@typescript-eslint/eslint-plugin": "^8.2.0",
125 | "@typescript-eslint/parser": "^8.2.0",
126 | "@vscode/test-electron": "^2.4.1",
127 | "cross-env": "^7.0.2",
128 | "eslint": "^9.7.0",
129 | "glob": "^11.0.0",
130 | "mocha": "^10.6.0",
131 | "os-browserify": "^0.3.0",
132 | "path-browserify": "^1.0.1",
133 | "process": "^0.11.10",
134 | "ts-loader": "^9.5.1",
135 | "typescript": "5.4.5",
136 | "vscode-uri": "^3.0.2",
137 | "webpack": "^5.82.1",
138 | "webpack-cli": "^5.1.4"
139 | },
140 | "dependencies": {
141 | "dedent-js": "1.0.1",
142 | "fp-ts": "^2.16.9",
143 | "ts-pattern": "^5.3.1",
144 | "vscode-languageclient": "10.0.0-next.7",
145 | "which": "^4.0.0"
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/editors/code/src/binaries.ts:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------------------------------------
2 | * Copyright (c) Higher Order Company (2024)
3 | * Licensed under the MIT License. See LICENSE in the project root for license information.
4 | *
5 | * This file is based on the Haskell VSCode extension (https://github.com/haskell/vscode-haskell),
6 | * which is also licensed under the MIT license.
7 | * ------------------------------------------------------------------------------------------ */
8 |
9 | // This module deals with pulling and installing the language server binaries into the system.
10 |
11 | import { ConfigurationTarget, ExtensionContext, ProgressLocation, window, workspace, WorkspaceFolder } from "vscode";
12 | import { Logger } from "vscode-languageclient";
13 | import { match } from 'ts-pattern';
14 | import { execPath } from "process";
15 | import which from "which";
16 | import dedent from "dedent-js";
17 | import path from "path";
18 | import * as child_process from 'child_process';
19 | import * as fs from "fs";
20 | import * as os from "os";
21 | // Note: if we keep using `fp-ts`, we should probably use this
22 | import * as TE from "fp-ts/lib/TaskEither";
23 | import * as E from "fp-ts/lib/Either";
24 | import { pipe } from "fp-ts/lib/function";
25 |
26 | const BEND_LS_EXE = "bend-language-server";
27 | // On Windows, the executable needs to be stored with a .exe extension
28 | const EXT = process.platform === "win32" ? ".exe" : "";
29 |
30 | // The language server can be installed automatically by the extension, be used from the PATH environment
31 | // variable, or configured from a specific location by the user
32 | type ManageLanguageServer = "automatic" | "PATH";
33 | let manageLS = workspace
34 | .getConfiguration("bend")
35 | .get("manageLanguageServer") as ManageLanguageServer;
36 |
37 | type ProcessCallback = (
38 | error: child_process.ExecFileException | null,
39 | stdout: string,
40 | stderr: string,
41 | resolve: (value: string | PromiseLike) => void,
42 | reject: (reason?: Error | string) => void
43 | ) => void;
44 |
45 | export interface IEnvVars {
46 | [key: string]: string;
47 | }
48 |
49 | export async function findLanguageServer(
50 | context: ExtensionContext,
51 | logger: Logger,
52 | folder?: WorkspaceFolder
53 | ): Promise> {
54 | logger.info("Looking for the language server");
55 |
56 | let serverExecutablePath = workspace
57 | .getConfiguration("bend")
58 | .get("serverExecutablePath") as string;
59 |
60 | if (serverExecutablePath) {
61 | // Get the language server from a pre-configured location
62 | return await findConfiguredServerExecutable(logger, folder);
63 | }
64 |
65 | // Create local file storage for the extension
66 | const storagePath = await getStoragePath(context);
67 | if (!fs.existsSync(storagePath)) {
68 | fs.mkdirSync(storagePath);
69 | }
70 |
71 | // First initialization, ask the user how they want to manage the language server
72 | if (!context.globalState.get("pluginInitialized") as boolean | null) {
73 | const message =
74 | "How would you like to manage the Bend Language Server executable?";
75 |
76 | const popup = window.showInformationMessage(
77 | message,
78 | { modal: true },
79 | "Automatically (compile via Cargo)",
80 | "Manually via PATH environment variable"
81 | );
82 |
83 | const decision = (await popup) || null;
84 | match(decision)
85 | .with("Automatically (compile via Cargo)", () => manageLS = "automatic")
86 | .with("Manually via PATH environment variable", () => manageLS = "PATH")
87 | .otherwise(() => {
88 | window.showWarningMessage("Choosing to install the Bend Language Server automatically via Cargo")
89 | manageLS = "automatic";
90 | });
91 |
92 | workspace.getConfiguration("bend").update("manageLanguageServer", manageLS, ConfigurationTarget.Global);
93 | context.globalState.update("pluginInitialized", true);
94 | }
95 |
96 | // User wants us to grab `bend-language-server` from the PATH environment variable
97 | if (manageLS == "PATH") {
98 | return pipe(
99 | await findExecutableInPATH(BEND_LS_EXE, context, logger),
100 | E.mapLeft(
101 | error => {
102 | // If PATH management failed, ask management type again on next startup to mitigate misclicks.
103 | // This might be annoying. It could be worthwhile to revisit this decision in the future.
104 | context.globalState.update("pluginInitialized", null);
105 | return error;
106 | }
107 | )
108 | );
109 | }
110 |
111 | // Automatically install the latest `bend-language-server` binary.
112 | if (manageLS == "automatic") {
113 | // -> check if installed locally
114 | // -> yes: check if latest version
115 | // -> yes: just run
116 | // -> no: ask to update
117 | // -> yes: update and run
118 | // -> no: just run
119 | // -> no: install and run
120 |
121 | let languageServer = await managedServerExecutableLocation(context);
122 | if (executableExists(languageServer)) {
123 | // Check if latest version is installed.
124 | const newVersion = await getLanguageServerLatestVersion(context, logger);
125 |
126 | const currentVersion = pipe(
127 | await callAsync(
128 | languageServer,
129 | ["--version"],
130 | logger,
131 | undefined,
132 | "Checking current installed version...",
133 | true
134 | ),
135 | E.map(output => output.trim()),
136 | E.getOrElse((_output) => "" /* empty version */)
137 | )
138 |
139 | return await pipe(
140 | newVersion,
141 | E.match(
142 | async _error => {
143 | // We couldn't find the newest version for some reason...
144 | // Keep this one.
145 | return E.right(languageServer);
146 | },
147 | async version => {
148 | if (version !== currentVersion) {
149 | return await askToUpdateLanguageServer(languageServer, version, context, logger);
150 | } else {
151 | return E.right(languageServer);
152 | }
153 | }
154 | )
155 | );
156 | } else {
157 | // Install the `bend-language-server` binary.
158 | return await pipe(
159 | await getLanguageServerLatestVersion(context, logger),
160 | E.match(
161 | async error => E.left(error),
162 | async version => await installLanguageServer(version, context, logger)
163 | )
164 | )
165 | }
166 | }
167 |
168 | return E.left("Invalid configuration for managing the Bend Language Server");
169 | }
170 |
171 | async function managedServerExecutableLocation(context: ExtensionContext): Promise {
172 | return path.join(await getStoragePath(context), "bin", `${BEND_LS_EXE}${EXT}`);
173 | }
174 |
175 | async function getLanguageServerLatestVersion(context: ExtensionContext, logger: Logger): Promise> {
176 | return pipe(
177 | await callCargo(
178 | ["search", BEND_LS_EXE],
179 | context,
180 | logger,
181 | "Searching for bend-language-server...",
182 | true
183 | ),
184 | E.map(
185 | output => {
186 | let version = /".*"/.exec(output)[0];
187 | version = version.substring(1, version.length - 1); // quotes
188 | return version;
189 | }
190 | ));
191 | }
192 |
193 | async function askToUpdateLanguageServer(languageServer: string, version: string, context: ExtensionContext, logger: Logger): Promise> {
194 | const decision = await window.showInformationMessage(
195 | `There is a new version of the Bend Language Server (${version}). Would you like to install it?`,
196 | "Yes",
197 | "No"
198 | );
199 |
200 | if (decision == "Yes") {
201 | // Update the language server
202 | logger.info(`Installing bend-language-server ${version}`);
203 | return await installLanguageServer(version, context, logger);
204 | }
205 |
206 | // Assume the decision was "No"
207 | return E.right(languageServer);
208 | }
209 |
210 | async function installLanguageServer(version: string, context: ExtensionContext, logger: Logger): Promise> {
211 | return await pipe(
212 | await callCargo(
213 | ["install", BEND_LS_EXE, "--version", version, "--root", await getStoragePath(context)],
214 | context,
215 | logger,
216 | "Installing the Bend Language Server... This might take a while.",
217 | true
218 | ),
219 | E.match(
220 | async error => E.left(error),
221 | async _output => E.right(await managedServerExecutableLocation(context))
222 | )
223 | );
224 | }
225 |
226 | async function findConfiguredServerExecutable(logger: Logger, folder?: WorkspaceFolder): Promise> {
227 | let executablePath = workspace.getConfiguration("bend").get("serverExecutablePath") as string;
228 | logger.info(`Trying to find the Bend Language Server binary at ${executablePath}`);
229 |
230 | executablePath = resolvePlaceHolders(executablePath, folder);
231 | logger.info(`Resolved path variables: ${executablePath}`);
232 |
233 | if (executableExists(executablePath)) {
234 | return E.right(executablePath);
235 | } else {
236 | return E.left(dedent`
237 | Could not find the Bend Language Server at ${execPath}.
238 | Consider changing settings for "bend.serverExecutablePath" and "bend.manageLanguageServer".
239 | `);
240 | }
241 | }
242 |
243 | async function findExecutableInPATH(executable: string, _context: ExtensionContext, logger: Logger): Promise> {
244 | if (executableExists(executable)) {
245 | return E.right(executable);
246 | } else {
247 | return E.left(`Could not find ${executable} in PATH`)
248 | }
249 | }
250 |
251 | export function resolvePlaceHolders(path: string, folder?: WorkspaceFolder) {
252 | path = path
253 | .replace('${HOME}', os.homedir)
254 | .replace('${home}', os.homedir)
255 | .replace(/^~/, os.homedir)
256 | .replace('$PATH', process.env.PATH ?? '$PATH')
257 | .replace('${PATH}', process.env.PATH ?? '${PATH}')
258 | .replace('${CARGO_HOME}', process.env.CARGO_HOME);
259 |
260 | if (folder) {
261 | path = path
262 | .replace('${workspaceFolder}', folder.uri.path)
263 | .replace('${workspaceRoot}', folder.uri.path);
264 | }
265 | return path;
266 | }
267 |
268 | export function executableExists(exe: string): boolean {
269 | const isWindows = process.platform === 'win32';
270 | let newEnv: IEnvVars = resolveServerEnvironmentPATH(
271 | workspace.getConfiguration('bend').get('serverEnvironment') || {}
272 | );
273 | newEnv = { ...(process.env as IEnvVars), ...newEnv };
274 | const cmd: string = isWindows ? 'where' : 'which';
275 | const out = child_process.spawnSync(cmd, [exe], { env: newEnv });
276 | return out.status === 0 || (which.sync(exe, { nothrow: true, path: newEnv.PATH }) ?? '') !== '';
277 | }
278 |
279 | export function resolveServerEnvironmentPATH(serverEnv: IEnvVars): IEnvVars {
280 | const pathSep = process.platform === 'win32' ? ';' : ':';
281 | const path: string[] | null = serverEnv.PATH
282 | ? serverEnv.PATH.split(pathSep).map((p) => resolvePlaceHolders(p))
283 | : null;
284 | return {
285 | ...serverEnv,
286 | ...(path ? { PATH: path.join(pathSep) } : {}),
287 | };
288 | }
289 |
290 | async function getStoragePath(
291 | context: ExtensionContext
292 | ): Promise {
293 | return context.globalStorageUri.fsPath;
294 | }
295 |
296 | async function findCargo(_context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): Promise> {
297 | logger.info("Looking for Cargo...");
298 |
299 | let cargoExecutable = workspace.getConfiguration("bend").get("cargoExecutablePath") as string;
300 | if (cargoExecutable) {
301 | logger.info(`Looking for Cargo in ${cargoExecutable}...`);
302 | cargoExecutable = resolvePlaceHolders(cargoExecutable, folder);
303 | logger.info(`Translated the path to ${cargoExecutable}`);
304 | if (executableExists(cargoExecutable)) {
305 | return E.right(cargoExecutable);
306 | } else {
307 | return E.left(`Could not find Cargo at ${cargoExecutable}`);
308 | }
309 | }
310 |
311 | if (executableExists(`cargo${EXT}`)) {
312 | // Found it in PATH
313 | return E.right(`cargo${EXT}`);
314 | }
315 |
316 | // We'll try to find the cargo binary by looking around
317 | logger.info("Probing for Cargo...");
318 |
319 | cargoExecutable = path.join(process.env.CARGO_HOME, "bin", `cargo${EXT}`);
320 | if (executableExists(cargoExecutable)) return E.right(cargoExecutable);
321 |
322 | cargoExecutable = path.join(os.homedir(), ".cargo", "bin", `cargo${EXT}`);
323 | if (executableExists(cargoExecutable)) return E.right(cargoExecutable);
324 |
325 | return E.left(dedent`
326 | Could not find the Cargo toolchain.
327 | Try installing it through rustup (https://rustup.rs) or setting its path through the "bend.cargoExecutablePath" configuration
328 | `);
329 | }
330 |
331 | async function callCargo(
332 | args: string[],
333 | context: ExtensionContext,
334 | logger: Logger,
335 | title?: string,
336 | cancellable?: boolean,
337 | callback?: ProcessCallback
338 | ): Promise> {
339 | if (manageLS !== "automatic") {
340 | return E.left("Tried to call Cargo when bend.manageLanguageServer is not set to automatic");
341 | }
342 |
343 | return await pipe(
344 | await findCargo(context, logger),
345 | E.match(
346 | async (error) => E.left(error),
347 | async (cargo) =>
348 | await callAsync(
349 | cargo,
350 | args,
351 | logger,
352 | undefined,
353 | title,
354 | cancellable,
355 | {},
356 | callback
357 | )
358 | )
359 | )
360 | }
361 |
362 | async function callAsync(
363 | executablePath: string,
364 | args: string[],
365 | logger: Logger,
366 | dir?: string,
367 | title?: string,
368 | cancellable?: boolean,
369 | envAdd?: IEnvVars,
370 | callback?: ProcessCallback
371 | ): Promise> {
372 | try {
373 | return E.right(await callAsyncThrowable(executablePath, args, logger, dir, title, cancellable, envAdd, callback));
374 | } catch (e) {
375 | return E.left(e);
376 | }
377 | }
378 |
379 | async function callAsyncThrowable(
380 | executablePath: string,
381 | args: string[],
382 | logger: Logger,
383 | dir?: string,
384 | title?: string,
385 | cancellable?: boolean,
386 | envAdd?: IEnvVars,
387 | callback?: ProcessCallback
388 | ): Promise {
389 | let newEnv: IEnvVars = resolveServerEnvironmentPATH(
390 | workspace.getConfiguration('bend').get('serverEnvironment') || {}
391 | );
392 | newEnv = { ...(process.env as IEnvVars), ...newEnv, ...(envAdd || {}) };
393 | return window.withProgress(
394 | {
395 | location: ProgressLocation.Notification,
396 | title,
397 | cancellable,
398 | },
399 | async (_, token) => {
400 | return new Promise((resolve, reject) => {
401 | const command: string = executablePath + ' ' + args.join(' ');
402 | logger.info(`Executing '${command}' in cwd '${dir ? dir : process.cwd()}'`);
403 | token.onCancellationRequested(() => {
404 | logger.warn(`User canceled the execution of '${command}'`);
405 | });
406 | // Need to set the encoding to 'utf8' in order to get back a string
407 | // We execute the command in a shell for windows, to allow use .cmd or .bat scripts
408 | const childProcess = child_process
409 | .execFile(
410 | process.platform === 'win32' ? `"${executablePath}"` : executablePath,
411 | args,
412 | { encoding: 'utf8', cwd: dir, shell: process.platform === 'win32', env: newEnv },
413 | (err, stdout, stderr) => {
414 | if (err) {
415 | logger.error(`Error executing '${command}' with error code ${err.code}`);
416 | logger.error(`stderr: ${stderr}`);
417 | if (stdout) {
418 | logger.error(`stdout: ${stdout}`);
419 | }
420 | }
421 | if (callback) {
422 | callback(err, stdout, stderr, resolve, reject);
423 | } else {
424 | if (err) {
425 | reject(
426 | Error(`\`${command}\` exited with exit code ${err.code}`)
427 | );
428 | } else {
429 | resolve(stdout?.trim());
430 | }
431 | }
432 | }
433 | )
434 | .on('exit', (code, signal) => {
435 | const msg =
436 | `Execution of '${command}' terminated with code ${code}` + (signal ? `and signal ${signal}` : '');
437 | logger.log(msg);
438 | })
439 | .on('error', (err) => {
440 | if (err) {
441 | logger.error(`Error executing '${command}': name = ${err.name}, message = ${err.message}`);
442 | reject(err);
443 | }
444 | });
445 | token.onCancellationRequested(() => childProcess.kill());
446 | });
447 | }
448 | );
449 | }
450 |
--------------------------------------------------------------------------------
/editors/code/src/extension.ts:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------------------------------------
2 | * Copyright (c) Higher Order Company (2024)
3 | * Based on original code by Microsoft Corporation under the MIT License.
4 | * Licensed under the MIT License. See LICENSE in the project root for information.
5 | * ------------------------------------------------------------------------------------------ */
6 |
7 | import {
8 | languages,
9 | workspace,
10 | commands,
11 | EventEmitter,
12 | ExtensionContext,
13 | window,
14 | InlayHintsProvider,
15 | TextDocument,
16 | CancellationToken,
17 | Range,
18 | InlayHint,
19 | TextDocumentChangeEvent,
20 | ProviderResult,
21 | WorkspaceEdit,
22 | TextEdit,
23 | Selection,
24 | Uri,
25 | } from "vscode";
26 |
27 | import {
28 | Disposable,
29 | Executable,
30 | LanguageClient,
31 | LanguageClientOptions,
32 | Logger,
33 | NullLogger,
34 | ServerOptions,
35 | } from "vscode-languageclient/node";
36 |
37 | import { pipe } from "fp-ts/lib/function";
38 | import * as E from "fp-ts/Either";
39 |
40 | import { findLanguageServer } from "./binaries";
41 |
42 | let client: LanguageClient;
43 |
44 | // ===================
45 | // Main extension code
46 |
47 | export async function activate(context: ExtensionContext) {
48 | const logger: Logger = NullLogger; // FIXME
49 | const traceOutputChannel = window.createOutputChannel("Bend Language Server trace");
50 | traceOutputChannel.appendLine(`Local file storage: ${context.globalStorageUri.fsPath}`);
51 |
52 | // Register editor commands
53 | const restartCommand = commands.registerCommand("bend.commands.restartServer", async () => {
54 | if (client.isRunning()) {
55 | client.info("Stopping the language server.");
56 | await client.stop();
57 | }
58 | client.info("Starting the language server.");
59 | await client.start();
60 | });
61 | context.subscriptions.push(restartCommand);
62 |
63 | const stopCommand = commands.registerCommand("bend.commands.stopServer", async () => {
64 | client.info("Stopping the language server.");
65 | await client.stop();
66 | });
67 | context.subscriptions.push(stopCommand);
68 |
69 | // We have to check if `bend-language-server` is installed, and if it's not, try to install it.
70 | const command = process.env.BEND_LS_PATH || pipe(
71 | await findLanguageServer(context, logger),
72 | E.match(
73 | error => { throw new Error(error); },
74 | languageServer => languageServer
75 | )
76 | );
77 |
78 | const run: Executable = {
79 | command,
80 | options: { env: { ...process.env, RUST_LOG: "info" } }
81 | };
82 |
83 | const debug: Executable = {
84 | command,
85 | options: { env: { ...process.env, RUST_LOG: "debug" } }
86 | };
87 |
88 | const serverOptions: ServerOptions = { run, debug, };
89 |
90 | let clientOptions: LanguageClientOptions = {
91 | // Register the server for plain text documents
92 | documentSelector: [{ scheme: "file", language: "bend" }],
93 | synchronize: {
94 | // Notify the server about file changes to '.clientrc files contained in the workspace
95 | // (we don't care about this for now, it was part of the boilerplate)
96 | fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
97 | },
98 | traceOutputChannel,
99 | };
100 |
101 | // Create the language client and start the client.
102 | client = new LanguageClient("bend-language-server", "Bend Language Server", serverOptions, clientOptions);
103 | // activateInlayHints(context);
104 | client.start();
105 | }
106 |
107 | export function deactivate(): Thenable | undefined {
108 | if (!client) {
109 | return undefined;
110 | }
111 | return client.stop();
112 | }
113 |
114 | // ================
115 | // Future additions
116 |
117 | export function activateInlayHints(ctx: ExtensionContext) {
118 | const maybeUpdater = {
119 | hintsProvider: null as Disposable | null,
120 | updateHintsEventEmitter: new EventEmitter(),
121 |
122 | async onConfigChange() {
123 | this.dispose();
124 |
125 | const event = this.updateHintsEventEmitter.event;
126 | this.hintsProvider = languages.registerInlayHintsProvider(
127 | { scheme: "file", language: "bend" },
128 | new (class implements InlayHintsProvider {
129 | onDidChangeInlayHints = event;
130 | resolveInlayHint(hint: InlayHint, token: CancellationToken): ProviderResult {
131 | const ret = {
132 | label: hint.label,
133 | ...hint,
134 | };
135 | return ret;
136 | }
137 | async provideInlayHints(
138 | document: TextDocument,
139 | range: Range,
140 | token: CancellationToken
141 | ): Promise {
142 | const hints = (await client
143 | .sendRequest("custom/inlay_hint", { path: document.uri.toString() })
144 | .catch(err => null)) as [number, number, string][];
145 | if (hints == null) {
146 | return [];
147 | } else {
148 | return hints.map(item => {
149 | const [start, end, label] = item;
150 | let startPosition = document.positionAt(start);
151 | let endPosition = document.positionAt(end);
152 | return {
153 | position: endPosition,
154 | paddingLeft: true,
155 | label: [
156 | {
157 | value: `${label}`,
158 | location: {
159 | uri: document.uri,
160 | range: new Range(1, 0, 1, 0)
161 | },
162 | command: {
163 | title: "hello world",
164 | command: "helloworld.helloWorld",
165 | arguments: [document.uri],
166 | },
167 | },
168 | ],
169 | };
170 | });
171 | }
172 | }
173 | })()
174 | );
175 | },
176 |
177 | onDidChangeTextDocument({ contentChanges, document }: TextDocumentChangeEvent) {
178 | // debugger
179 | // this.updateHintsEventEmitter.fire();
180 | },
181 |
182 | dispose() {
183 | this.hintsProvider?.dispose();
184 | this.hintsProvider = null;
185 | this.updateHintsEventEmitter.dispose();
186 | },
187 | };
188 |
189 | workspace.onDidChangeConfiguration(maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions);
190 | workspace.onDidChangeTextDocument(maybeUpdater.onDidChangeTextDocument, maybeUpdater, ctx.subscriptions);
191 |
192 | maybeUpdater.onConfigChange().catch(console.error);
193 | }
194 |
--------------------------------------------------------------------------------
/editors/code/syntaxes/bend.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
3 | "name": "bend",
4 | "fileTypes": [
5 | "bend"
6 | ],
7 | "patterns": [
8 | {
9 | "name": "comment.block.bend",
10 | "match": "#{([^#]|#[^}])*#}"
11 | },
12 | {
13 | "name": "comment.line.bend",
14 | "match": "#(\\+(.|\r?\n)|[^\\\n])*"
15 | },
16 | {
17 | "include": "#operators"
18 | },
19 | {
20 | "include": "#strings"
21 | },
22 | {
23 | "include": "#numbers"
24 | },
25 | {
26 | "include": "#reserved-words"
27 | },
28 | {
29 | "include": "#operator-dedicated-keywords-statements"
30 | },
31 | {
32 | "include": "#ctrl-statements"
33 | },
34 | {
35 | "include": "#special-keywords"
36 | },
37 | {
38 | "include": "#true-false"
39 | },
40 | {
41 | "include": "#functions"
42 | },
43 | {
44 | "name": "string.quoted.double.carbon",
45 | "begin": "\"",
46 | "end": "\"",
47 | "patterns": [
48 | {
49 | "include": "#string_escapes"
50 | }
51 | ]
52 | },
53 | {
54 | "include": "#customs"
55 | }
56 | ],
57 | "repository": {
58 | "operators": {
59 | "patterns": [
60 | {
61 | "name": "keyword.operator.new.bend",
62 | "match": "\\b(\\+|-|\\*|/|!)\\b"
63 | }
64 | ]
65 | },
66 | "string_escapes": {
67 | "patterns": [
68 | {
69 | "name": "constant.character.escape.bend",
70 | "match": "\\\\([tnr'\"0\\0]|x[0-9A-F]{2}|u\\{[0-9A-F]{4,}\\})"
71 | }
72 | ]
73 | },
74 | "special-keywords": {
75 | "patterns": [
76 | {
77 | "name": "keyword.control.less.bend",
78 | "match": "\\b(def|switch|case|return|if|else|when|match|λ|Some|data|let|use|object|fold|open|do|bind|Name|identity|Bool|ask|with)\\b"
79 | }
80 | ]
81 | },
82 | "operator-dedicated-keywords-statements": {
83 | "patterns": [
84 | {
85 | "name": "keyword.bend",
86 | "match": "\\b(bend|None|Cons|Nil|Result|type|lambda)\\b"
87 | }
88 | ]
89 | },
90 | "reserved-words": {
91 | "patterns": [
92 | {
93 | "name": "support.type.bend",
94 | "match": "\\b(Node|Leaf|Tree)\\b"
95 | }
96 | ]
97 | },
98 | "true-false": {
99 | "patterns": [
100 | {
101 | "name": "keyword.operator.new.bend",
102 | "match": "\\b(True|False|true|false)\\b"
103 | }
104 | ]
105 | },
106 | "functions": {
107 | "patterns": [
108 | {
109 | "name": "support.function.bend",
110 | "match": "\\b[a-zA-Z_0-9]+\\s*\\("
111 | },
112 | {
113 | "name": "support.function.bend",
114 | "match": "\\b(print)\\b"
115 | }
116 | ]
117 | },
118 | "customs": {
119 | "patterns": [
120 | {
121 | "name": "support.class.bend",
122 | "match": "(?<=\\bdata\\s)\\w+"
123 | },
124 | {
125 | "name": "support.class.bend",
126 | "match": "(?<=\\blet\\s)\\w+"
127 | },
128 | {
129 | "name": "support.class.bend",
130 | "match": "(?<=\\bSome\\s)\\w+"
131 | },
132 | {
133 | "name": "support.variable.bend",
134 | "match": "(?<=\\s*\\.)\\w+"
135 | },
136 | {
137 | "name": "support.variable.bend",
138 | "match": "\\w+(?=\\s*=)"
139 | },
140 | {
141 | "name": "support.variable.bend",
142 | "match": "\\w+(?=\\s*<\\-)"
143 | },
144 | {
145 | "name": "support.variable.bend",
146 | "match": "(?<=\\bbend\\s)\\w+"
147 | },
148 | {
149 | "name": "support.variable.bend",
150 | "match": "(?<=<\\-\\s)\\w+"
151 | },
152 | {
153 | "name": "support.function.bend",
154 | "match": "(?<=\\bdef\\s)\\w+"
155 | },
156 | {
157 | "name": "support.function.bend",
158 | "match": "(?<=\\bmatch\\s)\\w+"
159 | },
160 | {
161 | "name": "support.variable.bend",
162 | "match": "(?<=\\=\\s)\\w+"
163 | },
164 | {
165 | "name": "support.variable.bend",
166 | "match": "(?<=\\bcase\\s)\\w+"
167 | },
168 | {
169 | "name": "support.other.bend",
170 | "match": "(?<=\\bobject\\s)\\w+"
171 | },
172 | {
173 | "name": "support.type.property-name.bend",
174 | "match": "(?<=\\bfold\\s)\\w+"
175 | },
176 | {
177 | "name": "support.class.bend",
178 | "match": "(?<=\\bopen\\s)\\w+"
179 | },
180 | {
181 | "name": "support.class.bend",
182 | "match": "(?<=\\bdo\\s)\\w+"
183 | },
184 | {
185 | "name": "support.class.bend",
186 | "match": "(?<=\\bidentity\\s)\\w+"
187 | },
188 | {
189 | "name": "support.class.bend",
190 | "match": "(?<=\\blambda\\s)\\w+"
191 | }
192 | ]
193 | },
194 | "numbers": {
195 | "patterns": [
196 | {
197 | "name": "constant.numeric.bend",
198 | "match": "\\b\\d+\\b"
199 | },
200 | {
201 | "name": "constant.numeric.bend",
202 | "match": "\\b\\d+\\.\\d+\\b"
203 | },
204 | {
205 | "name": "constant.numeric.bend",
206 | "match": "\\b\\d{1,3}(?:_\\d{3})*\\b"
207 | }
208 | ]
209 | }
210 | },
211 | "scopeName": "source.bend"
212 | }
213 |
--------------------------------------------------------------------------------
/editors/code/syntaxes/language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | "lineComment": "#"
4 | },
5 | "brackets": [
6 | ["{", "}"],
7 | ["[", "]"],
8 | ["(", ")"]
9 | ],
10 | "autoClosingPairs": [
11 | ["{", "}"],
12 | ["[", "]"],
13 | ["(", ")"],
14 | ["\"", "\""],
15 | ["'", "'"]
16 | ],
17 | "surroundingPairs": [
18 | ["{", "}"],
19 | ["[", "]"],
20 | ["(", ")"],
21 | ["\"", "\""],
22 | ["'", "'"]
23 | ],
24 | "onEnterRules": [
25 | {
26 | "beforeText": "^\\s*(?:def|class|for|if|elif|else|try|with|finally|except|async).*?:\\s*$",
27 | "action": {
28 | "indent": "none",
29 | "appendText": " "
30 | }
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/editors/code/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "nodenext",
4 | "target": "es2019",
5 | "lib": ["ES2019"],
6 | "outDir": "./dist",
7 | "rootDir": "src",
8 | "sourceMap": true,
9 | "moduleResolution": "nodenext"
10 | },
11 | "include": ["src"],
12 | "exclude": ["node_modules", ".vscode-test"]
13 | }
14 |
--------------------------------------------------------------------------------
/editors/code/webpack.config.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | "use strict";
4 |
5 | const path = require("path");
6 |
7 | /**@type {import('webpack').Configuration}*/
8 | const config = {
9 | target: "node", // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target
10 |
11 | entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
12 | output: {
13 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
14 | path: path.resolve(__dirname, "dist"),
15 | filename: "extension.js",
16 | libraryTarget: "commonjs2",
17 | devtoolModuleFilenameTemplate: "../[resource-path]",
18 | },
19 | devtool: "hidden-source-map",
20 | externals: {
21 | vscode:
22 | "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
23 | },
24 | resolve: {
25 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
26 | mainFields: ["browser", "module", "main"], // look for `browser` entry point in imported node modules
27 | extensions: [".ts", ".js"],
28 | },
29 | module: {
30 | rules: [
31 | {
32 | test: /\.ts$/,
33 | exclude: /node_modules/,
34 | use: [
35 | {
36 | loader: "ts-loader",
37 | },
38 | ],
39 | },
40 | ],
41 | }
42 | };
43 | module.exports = config;
44 |
--------------------------------------------------------------------------------
/src/bin/bend-language-server.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 |
3 | use tokio::io::AsyncWriteExt;
4 | use tower_lsp::{LspService, Server};
5 |
6 | use bend_language_server::server::*;
7 |
8 | #[tokio::main]
9 | async fn main() {
10 | let stdin = tokio::io::stdin();
11 | let mut stdout = tokio::io::stdout();
12 |
13 | if env::args().any(|arg| arg == "--version") {
14 | let version = format!("{}\n", env!("CARGO_PKG_VERSION"));
15 | stdout
16 | .write_all(version.as_bytes())
17 | .await
18 | .expect("Couldn't write to stdout.");
19 | return;
20 | }
21 |
22 | let (service, socket) = LspService::new(Backend::new);
23 | Server::new(stdin, stdout, socket).serve(service).await;
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/diagnostics.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | pub use bend::diagnostics::*;
4 | use bend::{check_book, imports::DefaultLoader, CompileOpts};
5 | use tower_lsp::lsp_types::{self as lsp, Position};
6 | use tree_sitter as ts;
7 |
8 | use super::document::Document;
9 | use crate::utils::color_wrapper::treat_colors;
10 |
11 | /// Checks a Bend file and return its diagnostics.
12 | pub fn check(doc: &Document) -> Diagnostics {
13 | let path = Path::new(doc.url.path());
14 | let diagnostics_config = DiagnosticsConfig::new(Severity::Warning, true);
15 | let compile_opts = CompileOpts::default();
16 |
17 | let package_loader = DefaultLoader::new(path);
18 |
19 | let diagnostics = bend::load_file_to_book(path, package_loader, diagnostics_config)
20 | .and_then(|mut book| check_book(&mut book, diagnostics_config, compile_opts));
21 |
22 | match diagnostics {
23 | Ok(d) | Err(d) => d,
24 | }
25 | }
26 |
27 | pub fn lsp_diagnostics(doc: &Document, diagnostics: &Diagnostics) -> Vec {
28 | diagnostics
29 | .diagnostics
30 | // Iter<(DiagnosticOrigin, Vec)>
31 | .iter()
32 | // -> Iter<(DiagnosticOrigin, Diagnostic)>
33 | .flat_map(|(key, vals)| vals.iter().map(move |val| (key, val)))
34 | // Ignore unwanted diagnostics
35 | .filter_map(|(origin, diagnostic)| treat_diagnostic(doc, origin, diagnostic))
36 | .collect()
37 | }
38 |
39 | fn treat_diagnostic(
40 | doc: &Document,
41 | origin: &DiagnosticOrigin,
42 | diag: &Diagnostic,
43 | ) -> Option {
44 | Some(lsp::Diagnostic {
45 | message: treat_colors(&diag.display_with_origin(origin).to_string()),
46 | severity: match diag.severity {
47 | Severity::Allow => Some(lsp::DiagnosticSeverity::HINT),
48 | Severity::Warning => Some(lsp::DiagnosticSeverity::WARNING),
49 | Severity::Error => Some(lsp::DiagnosticSeverity::ERROR),
50 | },
51 | range: match origin {
52 | DiagnosticOrigin::Function(name) => find_def(doc, name.as_ref())?,
53 | DiagnosticOrigin::Inet(name) => find_def(doc, name.as_ref())?,
54 | _ => span_to_range(&diag.source.span),
55 | },
56 | code: None,
57 | code_description: None,
58 | source: Some("bend".into()),
59 | related_information: None,
60 | tags: None,
61 | data: None,
62 | })
63 | }
64 |
65 | /// Diagnostics with `Rule`, `Function` or `Inet` origins may have their
66 | /// spans including entire definitions, while we would only like to
67 | /// highlight their names.
68 | fn find_def(doc: &Document, name: &str) -> Option {
69 | let query = format!(
70 | r#"
71 | (fun_function_definition
72 | name: (identifier) @name
73 | (#eq? @name "{name}"))
74 | (imp_function_definition
75 | name: (identifier) @name
76 | (#eq? @name "{name}"))
77 | "#
78 | );
79 |
80 | doc.find_one(&query)
81 | .map(|node| ts_range_to_lsp(node.range()))
82 | }
83 |
84 | fn span_to_range(span: &Option) -> lsp::Range {
85 | span.as_ref()
86 | .map(|span| lsp::Range {
87 | start: Position {
88 | line: span.start.line as u32,
89 | character: span.start.char as u32,
90 | },
91 | end: Position {
92 | line: span.end.line as u32,
93 | character: span.end.char as u32,
94 | },
95 | })
96 | .unwrap_or_default()
97 | }
98 |
99 | pub fn ts_range_to_lsp(range: ts::Range) -> lsp::Range {
100 | lsp::Range {
101 | start: lsp::Position {
102 | line: range.start_point.row as u32,
103 | character: range.start_point.column as u32,
104 | },
105 | end: lsp::Position {
106 | line: range.end_point.row as u32,
107 | character: range.end_point.column as u32,
108 | },
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/core/document.rs:
--------------------------------------------------------------------------------
1 | use ropey::Rope;
2 | use tower_lsp::lsp_types as lsp;
3 | use tree_sitter as ts;
4 | use tree_sitter_highlight::{self as hg, Highlighter};
5 |
6 | use crate::language::{bend, bend_parser};
7 | use crate::utils::rope::TextProviderRope;
8 |
9 | /// Represents a text document open in the client's text editor.
10 | pub struct Document {
11 | pub url: lsp::Url,
12 | pub text: Rope,
13 | pub tree: Option,
14 | pub parser: ts::Parser,
15 | pub highlighter: hg::Highlighter,
16 | // pub components: HashMap
17 | }
18 |
19 | impl Document {
20 | /// Create an empty document for `url`.
21 | pub fn new(url: lsp::Url) -> Self {
22 | Self {
23 | url,
24 | text: Rope::new(),
25 | tree: None,
26 | parser: bend_parser().unwrap(),
27 | highlighter: Highlighter::new(),
28 | }
29 | }
30 |
31 | /// Create a new document with text for `url`.
32 | pub fn new_with_text(url: lsp::Url, text: &str) -> Self {
33 | let mut doc = Self::new(url);
34 | doc.update_whole_text(text);
35 | doc
36 | }
37 |
38 | /// Update the document with entirely new text.
39 | pub fn update_whole_text(&mut self, text: &str) {
40 | self.text = Rope::from_str(text);
41 | self.tree = self.parser.parse(text, None);
42 | }
43 |
44 | pub fn get_tree(&mut self) -> &ts::Tree {
45 | if self.tree.is_none() {
46 | self.do_parse();
47 | }
48 | self.tree.as_ref().expect("tried to get empty tree")
49 | }
50 |
51 | /// Find up to one node based on a tree-sitter query.
52 | pub fn find_one(&self, query: &str) -> Option {
53 | let mut cursor = ts::QueryCursor::new();
54 | let query = ts::Query::new(&bend(), query).unwrap();
55 | let root = self.tree.as_ref()?.root_node();
56 |
57 | cursor
58 | .captures(&query, root, &TextProviderRope(&self.text))
59 | .flat_map(|(m, _)| m.captures)
60 | .next()
61 | .map(|capture| capture.node)
62 | }
63 |
64 | fn do_parse(&mut self) -> Option {
65 | self.parser.parse_with(
66 | &mut |start_byte, _| {
67 | self.text
68 | .byte_slice(start_byte..)
69 | .chunks()
70 | .next()
71 | .unwrap_or("")
72 | },
73 | self.tree.as_ref(),
74 | )
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod diagnostics;
2 | pub mod document;
3 | pub mod semantic_token;
4 |
--------------------------------------------------------------------------------
/src/core/semantic_token.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use itertools::Itertools;
4 | use ropey::Rope;
5 | use tower_lsp::lsp_types::{Range, SemanticToken, SemanticTokenType};
6 | use tree_sitter_bend::HIGHLIGHTS_QUERY;
7 | use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent};
8 |
9 | use super::document::Document;
10 | use crate::language::bend;
11 |
12 | lazy_static::lazy_static! {
13 | /// Tree sitter capture names into LSP semantic token types.
14 | /// Changes to this table don't need to be added anywhere else due to the structures below.
15 | pub static ref NAME_TO_TOKEN_TYPE: HashMap<&'static str, SemanticTokenType> = {
16 | HashMap::from([
17 | ("variable", SemanticTokenType::VARIABLE),
18 | ("variable.parameter", SemanticTokenType::PARAMETER),
19 | ("variable.member", SemanticTokenType::ENUM_MEMBER),
20 | ("property", SemanticTokenType::TYPE),
21 | ("keyword", SemanticTokenType::KEYWORD),
22 | ("keyword.conditional", SemanticTokenType::KEYWORD),
23 | ("keyword.function", SemanticTokenType::KEYWORD),
24 | ("keyword.return", SemanticTokenType::KEYWORD),
25 | ("keyword.repeat", SemanticTokenType::KEYWORD),
26 | ("keyword.type", SemanticTokenType::KEYWORD),
27 | ("string", SemanticTokenType::STRING),
28 | ("function", SemanticTokenType::FUNCTION),
29 | ("function.call", SemanticTokenType::FUNCTION),
30 | ("type", SemanticTokenType::TYPE),
31 | // ("constructor", SemanticTokenType::?),
32 | ("character", SemanticTokenType::STRING),
33 | ("character.special", SemanticTokenType::STRING),
34 | ("number", SemanticTokenType::NUMBER),
35 | ("number.float", SemanticTokenType::NUMBER),
36 | ("comment", SemanticTokenType::COMMENT),
37 | // ("punctuation", SemanticTokenType::new("operator")),
38 | // ("punctuation.delimiter", SemanticTokenType::new("operator")),
39 | // ("punctuation.bracket", SemanticTokenType::new("operator")),
40 | ("operator", SemanticTokenType::OPERATOR),
41 | ])
42 | };
43 |
44 | /// Legend for token types.
45 | /// This is sent to the LSP client with the semantic tokens capabilities.
46 | pub static ref LEGEND_TOKEN_TYPE: Vec =
47 | NAME_TO_TOKEN_TYPE.values().cloned().unique().collect();
48 |
49 | /// Tree sitter highlighting names.
50 | /// This is used to perform syntax highlighting with tree sitter.
51 | pub static ref HIGHLIGHT_NAMES: Vec<&'static str> =
52 | NAME_TO_TOKEN_TYPE.keys().copied().collect();
53 |
54 | /// Translate indices from `HIGHLIGHT_NAMES` to indices from `LEGEND_TOKEN_TYPE`.
55 | pub static ref HIGHLIGHT_INDEX_TO_LSP_INDEX: HashMap = {
56 | let token_type_index: HashMap = LEGEND_TOKEN_TYPE.iter().enumerate().map(|(i, v)| (v.clone(), i)).collect();
57 | let highlight_index: HashMap<&&str, usize> = HIGHLIGHT_NAMES.iter().enumerate().map(|(i, v)| (v, i)).collect();
58 | NAME_TO_TOKEN_TYPE.iter().map(|(key, val)| (highlight_index[key], token_type_index[val])).collect()
59 | };
60 |
61 | /// Global configuration for syntax highlighting.
62 | pub static ref HIGHLIGHTER_CONFIG: HighlightConfiguration = {
63 | let mut config = HighlightConfiguration::new(bend(), "bend", HIGHLIGHTS_QUERY, "", "").unwrap();
64 | config.configure(&HIGHLIGHT_NAMES);
65 | config
66 | };
67 | }
68 |
69 | /// Generate the semantic tokens of a document for syntax highlighting.
70 | pub fn semantic_tokens(doc: &mut Document, range: Option) -> Vec {
71 | let code = doc.text.to_string(); // TODO: this is bad
72 | let highlights = doc
73 | .highlighter
74 | .highlight(&HIGHLIGHTER_CONFIG, code.as_bytes(), None, |_| None)
75 | .unwrap();
76 |
77 | // Get byte indices of line positions in the range, if it exists
78 | let range = range.map(|r| {
79 | let rstart = doc.text.line_to_byte(r.start.line as usize);
80 | let rend = doc.text.line_to_byte(r.end.line as usize + 1);
81 | rstart..rend
82 | });
83 |
84 | let mut tokens = vec![]; // result vector
85 | let mut types = vec![]; // token type stack
86 | let mut pre_line = 0; // calculate line deltas between tokens
87 | let mut pre_start = 0; // calculate index deltas between tokens
88 | for event in highlights {
89 | match event {
90 | Result::Ok(HighlightEvent::HighlightStart(h)) => types.push(h.0),
91 | Result::Ok(HighlightEvent::HighlightEnd) => drop(types.pop()),
92 | Result::Ok(HighlightEvent::Source { mut start, end }) => {
93 | // Ranged or full semantic tokens call
94 | if let Some(range) = &range {
95 | // If we still haven't gotten to the start of the range, continue.
96 | if end < range.start {
97 | continue;
98 | }
99 | // If we got past the end of the range, stop.
100 | if range.end < start {
101 | break;
102 | }
103 | }
104 |
105 | let token = types
106 | .last()
107 | .and_then(|curr| HIGHLIGHT_INDEX_TO_LSP_INDEX.get(curr))
108 | .and_then(|type_index| {
109 | // Prevents tokens from starting with new lines or other white space.
110 | // New lines at the start of tokens may break the `make_semantic_token` function.
111 | while start < end && char::from(doc.text.byte(start)).is_whitespace() {
112 | start += 1;
113 | }
114 |
115 | // Translates the token ranges into the expected struct from LSP.
116 | make_semantic_token(
117 | &doc.text,
118 | start..end,
119 | *type_index as u32,
120 | &mut pre_line,
121 | &mut pre_start,
122 | )
123 | });
124 |
125 | if let Some(token) = token {
126 | tokens.push(token);
127 | }
128 | }
129 | Err(_) => { /* log error? */ }
130 | }
131 | }
132 |
133 | tokens
134 | }
135 |
136 | /// Generates a specific semantic token within the guidelines of the LSP.
137 | fn make_semantic_token(
138 | code: &Rope,
139 | range: std::ops::Range,
140 | token_type: u32,
141 | pre_line: &mut u32,
142 | pre_start: &mut u32,
143 | ) -> Option {
144 | let line = code.try_byte_to_line(range.start).ok()? as u32;
145 | let first = code.try_line_to_char(line as usize).ok()? as u32;
146 | let start = (code.try_byte_to_char(range.start).ok()? as u32).checked_sub(first)?;
147 |
148 | let delta_line = line.checked_sub(*pre_line)?;
149 | let delta_start = if delta_line == 0 {
150 | start.checked_sub(*pre_start)?
151 | } else {
152 | start
153 | };
154 |
155 | *pre_line = line;
156 | *pre_start = start;
157 |
158 | Some(SemanticToken {
159 | delta_line,
160 | delta_start,
161 | // Multi-byte chars like `λ` need to be treated as a single char
162 | length: code.byte_slice(range).chars().count() as u32,
163 | token_type,
164 | token_modifiers_bitset: 0,
165 | })
166 | }
167 |
168 | /// Debugging test - tests steps from the semantic token generation algorithm.
169 | #[test]
170 | fn token_capture_test() {
171 | let code: Rope = r#"
172 | def main():
173 | return "Hi!"
174 | "#
175 | .into();
176 | let mut highlighter = tree_sitter_highlight::Highlighter::new();
177 | let config = &HIGHLIGHTER_CONFIG;
178 |
179 | // TODO: use TextProviderRope when the highlighting crate allows it
180 | let text = code.to_string();
181 | let highlights = highlighter
182 | .highlight(&config, text.as_bytes(), None, |_| None)
183 | .unwrap();
184 |
185 | let mut stack = vec![];
186 | for event in highlights {
187 | match event.unwrap() {
188 | HighlightEvent::HighlightStart(k) => {
189 | let name = HIGHLIGHT_NAMES[k.0];
190 | stack.push(name);
191 | println!("> start {}", name);
192 | }
193 | HighlightEvent::Source { start, end } => {
194 | println!("> {start}-{end}: {:?}", &text[start..end])
195 | }
196 | HighlightEvent::HighlightEnd => {
197 | println!("> end {}", stack.pop().unwrap());
198 | }
199 | }
200 | }
201 | println!();
202 |
203 | let highlights = highlighter
204 | .highlight(&config, text.as_bytes(), None, |_| None)
205 | .unwrap();
206 |
207 | let mut tokens = vec![];
208 | let mut stack = vec![];
209 | let mut pre_line = 0;
210 | let mut pre_start = 0;
211 | for event in highlights {
212 | match event {
213 | // if the highlight is nested, only save inner range
214 | Result::Ok(HighlightEvent::HighlightStart(h)) => stack.push(h.0),
215 | Result::Ok(HighlightEvent::HighlightEnd) => drop(stack.pop()),
216 | Result::Ok(HighlightEvent::Source { mut start, end }) => {
217 | stack
218 | .last()
219 | .and_then(|curr| HIGHLIGHT_INDEX_TO_LSP_INDEX.get(curr))
220 | .and_then(|type_index| {
221 | while start < end && char::from(code.byte(start)).is_whitespace() {
222 | start += 1;
223 | }
224 |
225 | println!(
226 | "{}-{} {:?}: {}",
227 | start,
228 | end,
229 | &text[start..end],
230 | LEGEND_TOKEN_TYPE[*type_index as usize].as_str()
231 | );
232 | make_semantic_token(
233 | &code,
234 | start..end,
235 | *type_index as u32,
236 | &mut pre_line,
237 | &mut pre_start,
238 | )
239 | })
240 | .map(|token| tokens.push(token));
241 | }
242 | Err(_) => { /* log error? */ }
243 | }
244 | }
245 | println!();
246 |
247 | println!("> got {} tokens", tokens.len());
248 | for token in tokens {
249 | println!("{:?}", token);
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/language/mod.rs:
--------------------------------------------------------------------------------
1 | //! Language module.
2 | //!
3 | //! Deals with processing directly related to the Bend language.
4 | //! Right now it only returns the parser from tree sitter, but in the future we
5 | //! might do additional processing from this module.
6 |
7 | use tree_sitter::{Language, LanguageError, Parser};
8 |
9 | /// Tree sitter representation for the Bend language.
10 | pub fn bend() -> Language {
11 | tree_sitter_bend::language()
12 | }
13 | /// Returns a new tree sitter parser for Bend.
14 | pub fn bend_parser() -> Result {
15 | let mut parser = Parser::new();
16 | parser.set_language(&bend())?;
17 | Ok(parser)
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod core;
2 | pub mod language;
3 | pub mod server;
4 | pub(crate) mod utils;
5 |
--------------------------------------------------------------------------------
/src/server/mod.rs:
--------------------------------------------------------------------------------
1 | use dashmap::DashMap;
2 | use tower_lsp::jsonrpc::Result;
3 | use tower_lsp::lsp_types::{self as lsp, SemanticTokensRangeResult};
4 | use tower_lsp::{Client, LanguageServer};
5 |
6 | use crate::core::diagnostics;
7 | use crate::core::document::{self, Document};
8 | use crate::core::semantic_token;
9 | use crate::utils::lsp_log;
10 |
11 | pub struct Backend {
12 | /// Connection to the client, used to send data
13 | pub client: Client,
14 | /// Currently open documents
15 | pub open_docs: DashMap,
16 | }
17 |
18 | #[tower_lsp::async_trait]
19 | impl LanguageServer for Backend {
20 | // All of these represent messages the server may receive from the client.
21 | // See the automatic documentation generated by `tower_lsp` to understand what each method does.
22 |
23 | async fn initialize(&self, _: lsp::InitializeParams) -> Result {
24 | let capabilities = Self::capabilities();
25 |
26 | Ok(lsp::InitializeResult {
27 | server_info: Some(lsp::ServerInfo {
28 | name: "Bend Language Server".into(),
29 | version: Some(env!("CARGO_PKG_VERSION").into()),
30 | }),
31 | offset_encoding: None,
32 | capabilities,
33 | })
34 | }
35 |
36 | async fn initialized(&self, _: lsp::InitializedParams) {
37 | let values = self
38 | .client
39 | .configuration(vec![lsp::ConfigurationItem {
40 | scope_uri: Some(lsp::Url::parse("file:///libraryPaths").unwrap()),
41 | section: Some("bend-language-server".to_string()),
42 | }])
43 | .await;
44 |
45 | if let Ok(_val) = values {
46 | // TODO: configuration
47 | }
48 |
49 | self.publish_all_diagnostics().await;
50 |
51 | lsp_log::info!(self.client, "bend-language-server initialized");
52 | }
53 |
54 | async fn shutdown(&self) -> Result<()> {
55 | Ok(())
56 | }
57 |
58 | async fn did_open(&self, params: lsp::DidOpenTextDocumentParams) {
59 | // This is called when the client opens a new document.
60 | // We get the entire text of the document; let's store it.
61 | lsp_log::info!(self.client, "opening file at {}", params.text_document.uri);
62 |
63 | self.open_doc(params.text_document.uri.clone(), params.text_document.text);
64 | self.publish_diagnostics(¶ms.text_document.uri).await;
65 | }
66 |
67 | async fn did_change_configuration(&self, _params: lsp::DidChangeConfigurationParams) {
68 | lsp_log::info!(self.client, "changing language server configurations");
69 |
70 | // TODO: configuration
71 |
72 | self.publish_all_diagnostics().await;
73 | }
74 |
75 | async fn did_change(&self, params: lsp::DidChangeTextDocumentParams) {
76 | lsp_log::log!(
77 | self.client,
78 | "getting new text from {}",
79 | params.text_document.uri
80 | );
81 |
82 | self.update_document(¶ms.text_document.uri, |doc| {
83 | for event in ¶ms.content_changes {
84 | doc.update_whole_text(&event.text);
85 | }
86 | });
87 | }
88 |
89 | async fn did_save(&self, params: lsp::DidSaveTextDocumentParams) {
90 | // Called when document is saved.
91 | // Update diagnostics (when we have them!)
92 | lsp_log::log!(
93 | self.client,
94 | "document saved at {}",
95 | params.text_document.uri
96 | );
97 |
98 | let url = ¶ms.text_document.uri;
99 | self.publish_diagnostics(url).await;
100 | }
101 |
102 | async fn semantic_tokens_full(
103 | &self,
104 | params: lsp::SemanticTokensParams,
105 | ) -> Result