├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── config.rs ├── documents.rs ├── documents └── environments.rs ├── language_server.rs ├── language_server ├── capabilities.rs ├── completion.rs ├── definition.rs ├── diagnostics.rs └── hover.rs ├── lib.rs ├── main.rs ├── resource └── completion_items.toml └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anyhow" 25 | version = "1.0.51" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 28 | 29 | [[package]] 30 | name = "async-trait" 31 | version = "0.1.51" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" 34 | dependencies = [ 35 | "proc-macro2", 36 | "quote", 37 | "syn", 38 | ] 39 | 40 | [[package]] 41 | name = "atty" 42 | version = "0.2.14" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 45 | dependencies = [ 46 | "hermit-abi", 47 | "libc", 48 | "winapi", 49 | ] 50 | 51 | [[package]] 52 | name = "auto_impl" 53 | version = "0.5.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" 56 | dependencies = [ 57 | "proc-macro-error", 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "autocfg" 65 | version = "1.0.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "1.3.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 74 | 75 | [[package]] 76 | name = "bytes" 77 | version = "1.1.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 80 | 81 | [[package]] 82 | name = "cfg-if" 83 | version = "1.0.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 86 | 87 | [[package]] 88 | name = "chrono" 89 | version = "0.4.19" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 92 | dependencies = [ 93 | "libc", 94 | "num-integer", 95 | "num-traits", 96 | "time", 97 | "winapi", 98 | ] 99 | 100 | [[package]] 101 | name = "clap" 102 | version = "2.34.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 105 | dependencies = [ 106 | "ansi_term", 107 | "atty", 108 | "bitflags", 109 | "strsim 0.8.0", 110 | "textwrap 0.11.0", 111 | "unicode-width", 112 | "vec_map", 113 | ] 114 | 115 | [[package]] 116 | name = "clap" 117 | version = "3.2.2" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "8e538f9ee5aa3b3963f09a997035f883677966ed50fce0292611927ce6f6d8c6" 120 | dependencies = [ 121 | "atty", 122 | "bitflags", 123 | "clap_derive", 124 | "clap_lex", 125 | "indexmap", 126 | "lazy_static", 127 | "strsim 0.10.0", 128 | "termcolor", 129 | "textwrap 0.15.2", 130 | ] 131 | 132 | [[package]] 133 | name = "clap_derive" 134 | version = "3.2.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a7f98063cac4652f23ccda556b8d04347a7fc4b2cff1f7577cc8c6546e0d8078" 137 | dependencies = [ 138 | "heck 0.4.0", 139 | "proc-macro-error", 140 | "proc-macro2", 141 | "quote", 142 | "syn", 143 | ] 144 | 145 | [[package]] 146 | name = "clap_lex" 147 | version = "0.2.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 150 | dependencies = [ 151 | "os_str_bytes", 152 | ] 153 | 154 | [[package]] 155 | name = "dashmap" 156 | version = "4.0.2" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" 159 | dependencies = [ 160 | "cfg-if", 161 | "num_cpus", 162 | ] 163 | 164 | [[package]] 165 | name = "dirs" 166 | version = "4.0.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 169 | dependencies = [ 170 | "dirs-sys", 171 | ] 172 | 173 | [[package]] 174 | name = "dirs-sys" 175 | version = "0.3.7" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 178 | dependencies = [ 179 | "libc", 180 | "redox_users", 181 | "winapi", 182 | ] 183 | 184 | [[package]] 185 | name = "either" 186 | version = "1.6.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 189 | 190 | [[package]] 191 | name = "env_logger" 192 | version = "0.8.4" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 195 | dependencies = [ 196 | "atty", 197 | "humantime", 198 | "log", 199 | "regex", 200 | "termcolor", 201 | ] 202 | 203 | [[package]] 204 | name = "form_urlencoded" 205 | version = "1.0.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 208 | dependencies = [ 209 | "matches", 210 | "percent-encoding", 211 | ] 212 | 213 | [[package]] 214 | name = "futures" 215 | version = "0.3.18" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" 218 | dependencies = [ 219 | "futures-channel", 220 | "futures-core", 221 | "futures-io", 222 | "futures-sink", 223 | "futures-task", 224 | "futures-util", 225 | ] 226 | 227 | [[package]] 228 | name = "futures-channel" 229 | version = "0.3.18" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" 232 | dependencies = [ 233 | "futures-core", 234 | "futures-sink", 235 | ] 236 | 237 | [[package]] 238 | name = "futures-core" 239 | version = "0.3.18" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" 242 | 243 | [[package]] 244 | name = "futures-io" 245 | version = "0.3.18" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" 248 | 249 | [[package]] 250 | name = "futures-macro" 251 | version = "0.3.18" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" 254 | dependencies = [ 255 | "proc-macro2", 256 | "quote", 257 | "syn", 258 | ] 259 | 260 | [[package]] 261 | name = "futures-sink" 262 | version = "0.3.18" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" 265 | 266 | [[package]] 267 | name = "futures-task" 268 | version = "0.3.18" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" 271 | 272 | [[package]] 273 | name = "futures-util" 274 | version = "0.3.18" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" 277 | dependencies = [ 278 | "futures-channel", 279 | "futures-core", 280 | "futures-io", 281 | "futures-macro", 282 | "futures-sink", 283 | "futures-task", 284 | "memchr", 285 | "pin-project-lite", 286 | "pin-utils", 287 | "slab", 288 | ] 289 | 290 | [[package]] 291 | name = "getrandom" 292 | version = "0.2.8" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 295 | dependencies = [ 296 | "cfg-if", 297 | "libc", 298 | "wasi 0.11.0+wasi-snapshot-preview1", 299 | ] 300 | 301 | [[package]] 302 | name = "glob" 303 | version = "0.3.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 306 | 307 | [[package]] 308 | name = "hashbrown" 309 | version = "0.12.3" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 312 | 313 | [[package]] 314 | name = "heck" 315 | version = "0.3.3" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 318 | dependencies = [ 319 | "unicode-segmentation", 320 | ] 321 | 322 | [[package]] 323 | name = "heck" 324 | version = "0.4.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 327 | 328 | [[package]] 329 | name = "hermit-abi" 330 | version = "0.1.19" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 333 | dependencies = [ 334 | "libc", 335 | ] 336 | 337 | [[package]] 338 | name = "httparse" 339 | version = "1.5.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 342 | 343 | [[package]] 344 | name = "humantime" 345 | version = "2.1.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 348 | 349 | [[package]] 350 | name = "idna" 351 | version = "0.2.3" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 354 | dependencies = [ 355 | "matches", 356 | "unicode-bidi", 357 | "unicode-normalization", 358 | ] 359 | 360 | [[package]] 361 | name = "indexmap" 362 | version = "1.9.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 365 | dependencies = [ 366 | "autocfg", 367 | "hashbrown", 368 | ] 369 | 370 | [[package]] 371 | name = "instant" 372 | version = "0.1.12" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 375 | dependencies = [ 376 | "cfg-if", 377 | ] 378 | 379 | [[package]] 380 | name = "itertools" 381 | version = "0.10.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 384 | dependencies = [ 385 | "either", 386 | ] 387 | 388 | [[package]] 389 | name = "itoa" 390 | version = "0.4.8" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 393 | 394 | [[package]] 395 | name = "lazy_static" 396 | version = "1.4.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 399 | 400 | [[package]] 401 | name = "libc" 402 | version = "0.2.139" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 405 | 406 | [[package]] 407 | name = "lock_api" 408 | version = "0.4.5" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 411 | dependencies = [ 412 | "scopeguard", 413 | ] 414 | 415 | [[package]] 416 | name = "log" 417 | version = "0.4.14" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 420 | dependencies = [ 421 | "cfg-if", 422 | ] 423 | 424 | [[package]] 425 | name = "lsp-types" 426 | version = "0.91.1" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae" 429 | dependencies = [ 430 | "bitflags", 431 | "serde", 432 | "serde_json", 433 | "serde_repr", 434 | "url", 435 | ] 436 | 437 | [[package]] 438 | name = "lspower" 439 | version = "1.4.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "c713fbfa0f03f0b8286a1e250281350aa07dee40e6ef5c0a4d5a2801146d7a54" 442 | dependencies = [ 443 | "anyhow", 444 | "async-trait", 445 | "auto_impl", 446 | "bytes", 447 | "dashmap", 448 | "futures", 449 | "httparse", 450 | "log", 451 | "lsp-types", 452 | "lspower-macros", 453 | "serde", 454 | "serde_json", 455 | "thiserror", 456 | "tokio", 457 | "tokio-util", 458 | "tower-service", 459 | "twoway", 460 | ] 461 | 462 | [[package]] 463 | name = "lspower-macros" 464 | version = "0.2.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "ca1d48da0e4a6100b4afd52fae99f36d47964a209624021280ad9ffdd410e83d" 467 | dependencies = [ 468 | "heck 0.3.3", 469 | "proc-macro2", 470 | "quote", 471 | "syn", 472 | ] 473 | 474 | [[package]] 475 | name = "matches" 476 | version = "0.1.9" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 479 | 480 | [[package]] 481 | name = "memchr" 482 | version = "2.4.1" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 485 | 486 | [[package]] 487 | name = "mio" 488 | version = "0.7.14" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 491 | dependencies = [ 492 | "libc", 493 | "log", 494 | "miow", 495 | "ntapi", 496 | "winapi", 497 | ] 498 | 499 | [[package]] 500 | name = "miow" 501 | version = "0.3.7" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 504 | dependencies = [ 505 | "winapi", 506 | ] 507 | 508 | [[package]] 509 | name = "ntapi" 510 | version = "0.3.6" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 513 | dependencies = [ 514 | "winapi", 515 | ] 516 | 517 | [[package]] 518 | name = "num-integer" 519 | version = "0.1.44" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 522 | dependencies = [ 523 | "autocfg", 524 | "num-traits", 525 | ] 526 | 527 | [[package]] 528 | name = "num-traits" 529 | version = "0.2.14" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 532 | dependencies = [ 533 | "autocfg", 534 | ] 535 | 536 | [[package]] 537 | name = "num_cpus" 538 | version = "1.13.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 541 | dependencies = [ 542 | "hermit-abi", 543 | "libc", 544 | ] 545 | 546 | [[package]] 547 | name = "once_cell" 548 | version = "1.8.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 551 | 552 | [[package]] 553 | name = "os_str_bytes" 554 | version = "6.4.1" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 557 | 558 | [[package]] 559 | name = "parking_lot" 560 | version = "0.11.2" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 563 | dependencies = [ 564 | "instant", 565 | "lock_api", 566 | "parking_lot_core", 567 | ] 568 | 569 | [[package]] 570 | name = "parking_lot_core" 571 | version = "0.8.5" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 574 | dependencies = [ 575 | "cfg-if", 576 | "instant", 577 | "libc", 578 | "redox_syscall", 579 | "smallvec", 580 | "winapi", 581 | ] 582 | 583 | [[package]] 584 | name = "peg" 585 | version = "0.7.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" 588 | dependencies = [ 589 | "peg-macros", 590 | "peg-runtime", 591 | ] 592 | 593 | [[package]] 594 | name = "peg-macros" 595 | version = "0.7.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" 598 | dependencies = [ 599 | "peg-runtime", 600 | "proc-macro2", 601 | "quote", 602 | ] 603 | 604 | [[package]] 605 | name = "peg-runtime" 606 | version = "0.7.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" 609 | 610 | [[package]] 611 | name = "percent-encoding" 612 | version = "2.1.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 615 | 616 | [[package]] 617 | name = "pin-project-lite" 618 | version = "0.2.7" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 621 | 622 | [[package]] 623 | name = "pin-utils" 624 | version = "0.1.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 627 | 628 | [[package]] 629 | name = "proc-macro-error" 630 | version = "1.0.4" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 633 | dependencies = [ 634 | "proc-macro-error-attr", 635 | "proc-macro2", 636 | "quote", 637 | "syn", 638 | "version_check", 639 | ] 640 | 641 | [[package]] 642 | name = "proc-macro-error-attr" 643 | version = "1.0.4" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 646 | dependencies = [ 647 | "proc-macro2", 648 | "quote", 649 | "version_check", 650 | ] 651 | 652 | [[package]] 653 | name = "proc-macro2" 654 | version = "1.0.32" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 657 | dependencies = [ 658 | "unicode-xid", 659 | ] 660 | 661 | [[package]] 662 | name = "quote" 663 | version = "1.0.10" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 666 | dependencies = [ 667 | "proc-macro2", 668 | ] 669 | 670 | [[package]] 671 | name = "redox_syscall" 672 | version = "0.2.16" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 675 | dependencies = [ 676 | "bitflags", 677 | ] 678 | 679 | [[package]] 680 | name = "redox_users" 681 | version = "0.4.3" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 684 | dependencies = [ 685 | "getrandom", 686 | "redox_syscall", 687 | "thiserror", 688 | ] 689 | 690 | [[package]] 691 | name = "regex" 692 | version = "1.5.4" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 695 | dependencies = [ 696 | "aho-corasick", 697 | "memchr", 698 | "regex-syntax", 699 | ] 700 | 701 | [[package]] 702 | name = "regex-syntax" 703 | version = "0.6.25" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 706 | 707 | [[package]] 708 | name = "ryu" 709 | version = "1.0.6" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" 712 | 713 | [[package]] 714 | name = "satysfi-formatter" 715 | version = "0.1.0" 716 | source = "git+https://github.com/usagrada/satysfi-formatter.git?branch=main#cbf2e50d246b905e21037d6df119e4503f04c250" 717 | dependencies = [ 718 | "clap 3.2.2", 719 | "dirs", 720 | "lspower", 721 | "satysfi-parser 0.0.3 (git+https://github.com/usagrada/satysfi-parser.git)", 722 | ] 723 | 724 | [[package]] 725 | name = "satysfi-language-server" 726 | version = "0.0.1" 727 | dependencies = [ 728 | "anyhow", 729 | "env_logger", 730 | "glob", 731 | "itertools", 732 | "log", 733 | "lspower", 734 | "peg", 735 | "regex", 736 | "satysfi-formatter", 737 | "satysfi-parser 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 738 | "serde", 739 | "simplelog", 740 | "structopt", 741 | "thiserror", 742 | "tokio", 743 | "toml", 744 | ] 745 | 746 | [[package]] 747 | name = "satysfi-parser" 748 | version = "0.0.3" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "b39274032a5660839ff938cee9e28358c92a1206e315194490304d7d613109d7" 751 | dependencies = [ 752 | "anyhow", 753 | "glob", 754 | "itertools", 755 | "log", 756 | "peg", 757 | "structopt", 758 | "thiserror", 759 | ] 760 | 761 | [[package]] 762 | name = "satysfi-parser" 763 | version = "0.0.3" 764 | source = "git+https://github.com/usagrada/satysfi-parser.git#d89da3dd653abbdc6ee6c220fbfeef862c963863" 765 | dependencies = [ 766 | "anyhow", 767 | "glob", 768 | "itertools", 769 | "log", 770 | "peg", 771 | "structopt", 772 | "thiserror", 773 | ] 774 | 775 | [[package]] 776 | name = "scopeguard" 777 | version = "1.1.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 780 | 781 | [[package]] 782 | name = "serde" 783 | version = "1.0.130" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 786 | dependencies = [ 787 | "serde_derive", 788 | ] 789 | 790 | [[package]] 791 | name = "serde_derive" 792 | version = "1.0.130" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 795 | dependencies = [ 796 | "proc-macro2", 797 | "quote", 798 | "syn", 799 | ] 800 | 801 | [[package]] 802 | name = "serde_json" 803 | version = "1.0.72" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" 806 | dependencies = [ 807 | "itoa", 808 | "ryu", 809 | "serde", 810 | ] 811 | 812 | [[package]] 813 | name = "serde_repr" 814 | version = "0.1.7" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" 817 | dependencies = [ 818 | "proc-macro2", 819 | "quote", 820 | "syn", 821 | ] 822 | 823 | [[package]] 824 | name = "signal-hook-registry" 825 | version = "1.4.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 828 | dependencies = [ 829 | "libc", 830 | ] 831 | 832 | [[package]] 833 | name = "simplelog" 834 | version = "0.9.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720" 837 | dependencies = [ 838 | "chrono", 839 | "log", 840 | "termcolor", 841 | ] 842 | 843 | [[package]] 844 | name = "slab" 845 | version = "0.4.5" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 848 | 849 | [[package]] 850 | name = "smallvec" 851 | version = "1.7.0" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 854 | 855 | [[package]] 856 | name = "strsim" 857 | version = "0.8.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 860 | 861 | [[package]] 862 | name = "strsim" 863 | version = "0.10.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 866 | 867 | [[package]] 868 | name = "structopt" 869 | version = "0.3.25" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 872 | dependencies = [ 873 | "clap 2.34.0", 874 | "lazy_static", 875 | "structopt-derive", 876 | ] 877 | 878 | [[package]] 879 | name = "structopt-derive" 880 | version = "0.4.18" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 883 | dependencies = [ 884 | "heck 0.3.3", 885 | "proc-macro-error", 886 | "proc-macro2", 887 | "quote", 888 | "syn", 889 | ] 890 | 891 | [[package]] 892 | name = "syn" 893 | version = "1.0.82" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 896 | dependencies = [ 897 | "proc-macro2", 898 | "quote", 899 | "unicode-xid", 900 | ] 901 | 902 | [[package]] 903 | name = "termcolor" 904 | version = "1.1.2" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 907 | dependencies = [ 908 | "winapi-util", 909 | ] 910 | 911 | [[package]] 912 | name = "textwrap" 913 | version = "0.11.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 916 | dependencies = [ 917 | "unicode-width", 918 | ] 919 | 920 | [[package]] 921 | name = "textwrap" 922 | version = "0.15.2" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" 925 | 926 | [[package]] 927 | name = "thiserror" 928 | version = "1.0.30" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 931 | dependencies = [ 932 | "thiserror-impl", 933 | ] 934 | 935 | [[package]] 936 | name = "thiserror-impl" 937 | version = "1.0.30" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 940 | dependencies = [ 941 | "proc-macro2", 942 | "quote", 943 | "syn", 944 | ] 945 | 946 | [[package]] 947 | name = "time" 948 | version = "0.1.44" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 951 | dependencies = [ 952 | "libc", 953 | "wasi 0.10.0+wasi-snapshot-preview1", 954 | "winapi", 955 | ] 956 | 957 | [[package]] 958 | name = "tinyvec" 959 | version = "1.5.1" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 962 | dependencies = [ 963 | "tinyvec_macros", 964 | ] 965 | 966 | [[package]] 967 | name = "tinyvec_macros" 968 | version = "0.1.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 971 | 972 | [[package]] 973 | name = "tokio" 974 | version = "1.14.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" 977 | dependencies = [ 978 | "autocfg", 979 | "bytes", 980 | "libc", 981 | "memchr", 982 | "mio", 983 | "num_cpus", 984 | "once_cell", 985 | "parking_lot", 986 | "pin-project-lite", 987 | "signal-hook-registry", 988 | "tokio-macros", 989 | "winapi", 990 | ] 991 | 992 | [[package]] 993 | name = "tokio-macros" 994 | version = "1.6.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" 997 | dependencies = [ 998 | "proc-macro2", 999 | "quote", 1000 | "syn", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "tokio-util" 1005 | version = "0.6.9" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 1008 | dependencies = [ 1009 | "bytes", 1010 | "futures-core", 1011 | "futures-sink", 1012 | "log", 1013 | "pin-project-lite", 1014 | "tokio", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "toml" 1019 | version = "0.5.8" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1022 | dependencies = [ 1023 | "serde", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "tower-service" 1028 | version = "0.3.1" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1031 | 1032 | [[package]] 1033 | name = "twoway" 1034 | version = "0.2.2" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" 1037 | dependencies = [ 1038 | "memchr", 1039 | "unchecked-index", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "unchecked-index" 1044 | version = "0.2.2" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" 1047 | 1048 | [[package]] 1049 | name = "unicode-bidi" 1050 | version = "0.3.7" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 1053 | 1054 | [[package]] 1055 | name = "unicode-normalization" 1056 | version = "0.1.19" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1059 | dependencies = [ 1060 | "tinyvec", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "unicode-segmentation" 1065 | version = "1.8.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 1068 | 1069 | [[package]] 1070 | name = "unicode-width" 1071 | version = "0.1.9" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1074 | 1075 | [[package]] 1076 | name = "unicode-xid" 1077 | version = "0.2.2" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1080 | 1081 | [[package]] 1082 | name = "url" 1083 | version = "2.2.2" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1086 | dependencies = [ 1087 | "form_urlencoded", 1088 | "idna", 1089 | "matches", 1090 | "percent-encoding", 1091 | "serde", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "vec_map" 1096 | version = "0.8.2" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1099 | 1100 | [[package]] 1101 | name = "version_check" 1102 | version = "0.9.3" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1105 | 1106 | [[package]] 1107 | name = "wasi" 1108 | version = "0.10.0+wasi-snapshot-preview1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1111 | 1112 | [[package]] 1113 | name = "wasi" 1114 | version = "0.11.0+wasi-snapshot-preview1" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1117 | 1118 | [[package]] 1119 | name = "winapi" 1120 | version = "0.3.9" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1123 | dependencies = [ 1124 | "winapi-i686-pc-windows-gnu", 1125 | "winapi-x86_64-pc-windows-gnu", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "winapi-i686-pc-windows-gnu" 1130 | version = "0.4.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1133 | 1134 | [[package]] 1135 | name = "winapi-util" 1136 | version = "0.1.5" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1139 | dependencies = [ 1140 | "winapi", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "winapi-x86_64-pc-windows-gnu" 1145 | version = "0.4.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1148 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "satysfi-language-server" 3 | version = "0.0.1" 4 | authors = ["monaqa "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.38" 11 | env_logger = "0.8.3" 12 | itertools = "0.10.0" 13 | log = "0.4.14" 14 | lspower = "1.4.0" 15 | serde = { version = "1.0.124", features = ["derive"] } 16 | simplelog = "0.9.0" 17 | structopt = "0.3.21" 18 | thiserror = "1.0.24" 19 | tokio = { version = "1.2.0", features = ["full"] } 20 | toml = "0.5.8" 21 | satysfi-parser = "0.0.3" 22 | # satysfi-parser = { path = "../satysfi-parser" } 23 | peg = "0.7.0" 24 | glob = "0.3.0" 25 | regex = "1.5.4" 26 | satysfi-formatter = { git = "https://github.com/usagrada/satysfi-formatter.git", branch = "main" } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mogami Shinichi 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 | # [WIP] SATySFi Language Server 2 | 3 | This repository is work-in-progress yet. 4 | 5 | ## Features 6 | 7 | |Kind |Function |Done| 8 | |:----------------|:----------------------------------------------------------|:--:| 9 | |`codeAction` |Add the definition of an undefined command under the cursor| | 10 | |`completion` |Complete a command name |✅ | 11 | |`completion` |Complete a field name in a record | | 12 | |`completion` |Complete a local function/variable name |✅ | 13 | |`completion` |Complete a primitive |✅ | 14 | |`completion` |Complete a public function in a module |✅ | 15 | |`diagnostics` |Linter (warning) | | 16 | |`diagnostics` |Syntax error (Recoverable) |✅ | 17 | |`diagnostics` |Syntax error (Unrecoverable) |✅ | 18 | |`diagnostics` |Type error | | 19 | |`format` |Code formatting |✅ | 20 | |`gotoDeclaration`|Go to the type declaration of a command in a module | | 21 | |`gotoDeclaration`|Go to the type declaration of a public function in a module| | 22 | |`gotoDefinition` |Go to the definiton of a command |✅ | 23 | |`gotoDefinition` |Go to the definiton of a local function/variable |✅ | 24 | |`gotoDefinition` |Go to the definiton of a public function in a module |✅ | 25 | |`hover` |Hover on a command in a module |✅ | 26 | |`hover` |Hover on a primitive | | 27 | |`hover` |Hover on a public function in a module |✅ | 28 | |`rename` |Rename a variable name | | 29 | |`typeHint` |Type hints after a command | | 30 | 31 | ## How to setup 32 | 33 | At the moment, we are only using 34 | [coc.nvim](https://github.com/neoclide/coc.nvim) on [Neovim](https://github.com/neovim/neovim) 35 | to check the operation. 36 | 37 | ### Usage 38 | 39 | In `coc-settings.json`: 40 | 41 | ```json 42 | { 43 | "languageserver": { 44 | "satysfi-ls": { 45 | "command": "/path/to/satysfi-language-server/target/debug/satysfi-language-server", 46 | "args": [], 47 | "filetypes": ["satysfi"], 48 | "trace.server": "verbose" 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | #### Debug Mode 55 | 56 | ``` 57 | /path/to/satysfi-language-server/target/debug/satysfi-language-server --tcp 58 | ``` 59 | 60 | In `coc-settings.json`: 61 | 62 | ```json 63 | { 64 | "languageserver": { 65 | "socketserver": { 66 | "host":"127.0.0.1", 67 | "port": 9527, 68 | "filetypes": ["satysfi"] 69 | } 70 | } 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct Config; 3 | -------------------------------------------------------------------------------- /src/documents.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | use anyhow::{anyhow, Result}; 7 | use itertools::Itertools; 8 | use log::info; 9 | use lspower::lsp::{Position, Url}; 10 | use satysfi_parser::{ 11 | grammar::{type_block_cmd, type_inline_cmd, type_math_cmd}, 12 | structure::{Header, LetRecInner, Program, ProgramText, Signature, Statement, TypeInner}, 13 | Cst, CstText, LineCol, Rule, Span, 14 | }; 15 | 16 | use crate::util::{ConvertPosition, UrlPos}; 17 | 18 | /// オンメモリで取り扱うデータをまとめたデータ構造。 19 | #[derive(Debug, Default)] 20 | pub struct DocumentCache(pub HashMap); 21 | 22 | impl DocumentCache { 23 | pub fn get(&self, url: &Url) -> Option<&DocumentData> { 24 | self.0.get(url) 25 | } 26 | 27 | pub fn get_doc_info(&self, url: &Url) -> Option<(&ProgramText, &Environment)> { 28 | if let Some(DocumentData::Parsed { 29 | program_text, 30 | environment, 31 | }) = self.get(url) 32 | { 33 | Some((program_text, environment)) 34 | } else { 35 | None 36 | } 37 | } 38 | 39 | pub fn get_text_from_span<'a>(&'a self, url: &Url, span: Span) -> Option<&'a str> { 40 | let doc = self.0.get(url)?; 41 | if let DocumentData::Parsed { program_text, .. } = doc { 42 | Some(program_text.get_text_from_span(span)) 43 | } else { 44 | None 45 | } 46 | } 47 | 48 | /// カーソルを含む行内容を抽出する。 49 | pub fn get_line<'a>(&'a self, urlpos: &UrlPos) -> Option<&'a str> { 50 | let UrlPos { url, pos } = &urlpos; 51 | match self.0.get(url)? { 52 | DocumentData::Parsed { program_text, .. } => { 53 | let pos_usize = program_text.from_position(&pos)?; 54 | let LineCol { line, .. } = program_text.get_line_col(pos_usize)?; 55 | let start = *program_text.lines.get(line)?; 56 | let end = *program_text 57 | .lines 58 | .get(line + 1) 59 | .unwrap_or(&program_text.text.len()); 60 | Some(&program_text.text.as_str()[start..end]) 61 | } 62 | DocumentData::NotParsed { text, .. } => { 63 | let line = pos.line as usize; 64 | text.split('\n').nth(line) 65 | } 66 | } 67 | } 68 | 69 | /// dependencies の中のパッケージについてパースし、 Environment 情報の登録を行う。 70 | /// この操作は再帰的に行う。 71 | pub fn register_dependencies(&mut self, deps: &[Dependency]) { 72 | for dep in deps { 73 | if let Some(url) = &dep.url { 74 | // 既に登録されている url は一度読んでいるので skip 75 | if self.0.get(url).is_none() { 76 | if let Ok(doc_data) = DocumentData::new_from_file(url) { 77 | self.0.insert(url.clone(), doc_data); 78 | // 上で格納したファイルの中に dependencies 情報があればクローンして取り出す 79 | let dependencies = self.0.get(url).and_then(|doc| { 80 | if let DocumentData::Parsed { environment, .. } = doc { 81 | Some(environment.dependencies.clone()) 82 | } else { 83 | None 84 | } 85 | }); 86 | if let Some(dependencies) = dependencies { 87 | self.register_dependencies(&dependencies); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | pub fn get_dependencies_recursive<'a>(&'a self, deps: &'a [Dependency]) -> Vec<&'a Dependency> { 96 | deps.iter() 97 | .map(|dep| self.get_dependency_recursive(dep).into_iter().collect_vec()) 98 | .concat() 99 | .into_iter() 100 | .collect::>() // 重複した URL を排除 101 | .into_iter() 102 | .map(|(_, dep)| dep) 103 | .collect_vec() 104 | } 105 | 106 | /// その dependency 先のファイルを読み、そのファイルが依存しているものを再帰的に取り出す。 107 | fn get_dependency_recursive<'a>(&'a self, dep: &'a Dependency) -> HashMap { 108 | let mut hm = HashMap::new(); 109 | if let Some(url) = &dep.url { 110 | if !hm.contains_key(url) { 111 | hm.insert(url.clone(), dep); 112 | } 113 | } 114 | 115 | if let Some(DocumentData::Parsed { 116 | program_text, 117 | environment, 118 | }) = dep.url.as_ref().and_then(|url| self.0.get(url)) 119 | { 120 | for dep in &environment.dependencies { 121 | if let Some(url) = &dep.url { 122 | if !hm.contains_key(url) { 123 | hm.insert(url.clone(), dep); 124 | let child_hm = self.get_dependency_recursive(dep); 125 | hm = hm.into_iter().chain(child_hm).collect(); 126 | } 127 | } 128 | } 129 | } 130 | hm 131 | } 132 | } 133 | 134 | /// 一つのファイルに関するデータを纏めたデータ構造。 135 | #[derive(Debug)] 136 | pub enum DocumentData { 137 | /// パーサによって正常にパースできたデータ。 138 | Parsed { 139 | /// パース結果の具象構文木 + テキスト本体。 140 | program_text: ProgramText, 141 | /// このファイルで定義されている変数やコマンドに関する情報。 142 | environment: Environment, 143 | }, 144 | 145 | /// パーサによってパースできなかったデータ。 146 | NotParsed { 147 | /// テキスト本体。 148 | text: String, 149 | /// エラー箇所。 150 | linecol: LineCol, 151 | /// エラー箇所にて期待するパターン(終端記号)列。 152 | expect: Vec<&'static str>, 153 | }, 154 | } 155 | 156 | impl DocumentData { 157 | /// テキストから新たな DocumentData を作成する。 158 | pub fn new(text: &str, url: &Url) -> DocumentData { 159 | match ProgramText::parse(text) { 160 | Ok(program_text) => { 161 | let environment = Environment::from_program(&program_text, &url); 162 | DocumentData::Parsed { 163 | program_text, 164 | environment, 165 | } 166 | } 167 | Err((linecol, expect)) => { 168 | let text = text.to_owned(); 169 | DocumentData::NotParsed { 170 | text, 171 | linecol, 172 | expect, 173 | } 174 | } 175 | } 176 | } 177 | 178 | pub fn new_from_file(url: &Url) -> Result { 179 | if let Ok(fpath) = url.to_file_path() { 180 | let text = std::fs::read_to_string(&fpath)?; 181 | Ok(DocumentData::new(&text, url)) 182 | } else { 183 | Err(anyhow!("Failed to convert url to file path.")) 184 | } 185 | } 186 | 187 | pub fn show_envs_debug(&self) { 188 | match self { 189 | DocumentData::Parsed { 190 | environment, 191 | program_text, 192 | } => { 193 | environment.show_debug(); 194 | let cst_text = CstText { 195 | text: program_text.text.clone(), 196 | lines: program_text.lines.clone(), 197 | cst: program_text.cst.clone(), 198 | }; 199 | info!("{cst_text}"); 200 | } 201 | DocumentData::NotParsed { .. } => {} 202 | } 203 | } 204 | 205 | /// その position において、 Open している module 名の一覧を表示する。 206 | pub fn get_open_modules(&self, pos: usize) -> Vec { 207 | match self { 208 | DocumentData::Parsed { 209 | program_text, 210 | environment, 211 | } => { 212 | let open_stmts = environment 213 | .open_modules 214 | .iter() 215 | .filter(|opmod| opmod.scope.includes(pos)) 216 | .map(|opmod| opmod.name.clone()); 217 | let csts = program_text.cst.dig(pos); 218 | let binded_open_stmts = csts 219 | .iter() 220 | .filter(|&cst| { 221 | cst.rule == Rule::bind_stmt 222 | && cst.inner.get(0).unwrap().rule == Rule::open_stmt 223 | }) 224 | .map(|cst| program_text.get_text(cst).to_owned()); 225 | binded_open_stmts.chain(open_stmts).collect() 226 | } 227 | DocumentData::NotParsed { .. } => vec![], 228 | } 229 | } 230 | 231 | /// その position において、 Module.( | ) のようになっている module 名の一覧を表示する。 232 | pub fn get_localized_modules(&self, pos: usize) -> Vec { 233 | match self { 234 | DocumentData::Parsed { program_text, .. } => program_text 235 | .cst 236 | .dig(pos) 237 | .into_iter() 238 | .filter(|cst| cst.rule == Rule::expr_with_mod) 239 | .flat_map(|cst| { 240 | cst.inner.iter().find_map(|inner| { 241 | if inner.rule == Rule::module_name { 242 | Some(inner.span) 243 | } else { 244 | None 245 | } 246 | }) 247 | }) 248 | .map(|span| program_text.get_text_from_span(span).to_owned()) 249 | .collect_vec(), 250 | DocumentData::NotParsed { .. } => vec![], 251 | } 252 | } 253 | } 254 | 255 | /// 変数やコマンドに関する情報。 256 | #[derive(Debug, Default)] 257 | pub struct Environment { 258 | dependencies: Vec, 259 | components: Vec, 260 | open_modules: Vec, 261 | } 262 | 263 | impl Environment { 264 | pub fn from_program(program_text: &ProgramText, url: &Url) -> Self { 265 | match &program_text.structure { 266 | Ok(structure) => { 267 | let (header, preamble) = match structure { 268 | Program::Saty { 269 | header, preamble, .. 270 | } => (header, preamble), 271 | Program::Satyh { 272 | header, preamble, .. 273 | } => (header, preamble), 274 | }; 275 | let header = header.iter().collect_vec(); 276 | let preamble = preamble.iter().collect_vec(); 277 | let dependencies = Dependency::from_header(&header, program_text, url); 278 | let components = Component::from_preamble(&preamble, program_text, url); 279 | let open_modules = OpenModule::from_preamble(&preamble, program_text, url); 280 | Environment { 281 | dependencies, 282 | components, 283 | open_modules, 284 | } 285 | } 286 | Err(_) => Environment::default(), 287 | } 288 | } 289 | 290 | /// Get a reference to the environment's dependencies. 291 | pub fn dependencies(&self) -> &[Dependency] { 292 | self.dependencies.as_slice() 293 | } 294 | 295 | pub fn modules(&self) -> Vec<&Component> { 296 | self.components 297 | .iter() 298 | .filter(|c| matches!(c.body, ComponentBody::Module { .. })) 299 | .collect_vec() 300 | } 301 | 302 | pub fn variables(&self) -> Vec<&Component> { 303 | self.components 304 | .iter() 305 | .filter(|c| matches!(c.body, ComponentBody::Variable { .. })) 306 | .collect_vec() 307 | } 308 | 309 | pub fn types(&self) -> Vec<&Component> { 310 | self.components 311 | .iter() 312 | .filter(|c| matches!(c.body, ComponentBody::Type { .. })) 313 | .collect_vec() 314 | } 315 | 316 | pub fn variants(&self) -> Vec<&Component> { 317 | self.components 318 | .iter() 319 | .filter(|c| matches!(c.body, ComponentBody::Variant { .. })) 320 | .collect_vec() 321 | } 322 | 323 | pub fn inline_cmds(&self) -> Vec<&Component> { 324 | self.components 325 | .iter() 326 | .filter(|c| matches!(c.body, ComponentBody::InlineCmd { .. })) 327 | .collect_vec() 328 | } 329 | 330 | pub fn block_cmds(&self) -> Vec<&Component> { 331 | self.components 332 | .iter() 333 | .filter(|c| matches!(c.body, ComponentBody::BlockCmd { .. })) 334 | .collect_vec() 335 | } 336 | 337 | pub fn math_cmds(&self) -> Vec<&Component> { 338 | self.components 339 | .iter() 340 | .filter(|c| matches!(c.body, ComponentBody::MathCmd { .. })) 341 | .collect_vec() 342 | } 343 | 344 | pub fn variables_external(&self, open_modules: &[String]) -> Vec<&Component> { 345 | let local = self.variables(); 346 | let in_mods = self 347 | .modules() 348 | .iter() 349 | .filter(|&module| open_modules.contains(&module.name)) 350 | .map(|module| match &module.body { 351 | ComponentBody::Module { components } => components 352 | .iter() 353 | .filter(|c| matches!(c.visibility, Visibility::Public)) 354 | .filter(|c| matches!(c.body, ComponentBody::Variable { .. })) 355 | .collect_vec(), 356 | _ => unreachable!(), 357 | }) 358 | .concat(); 359 | [local, in_mods].concat() 360 | } 361 | 362 | pub fn types_external(&self, open_modules: &[String]) -> Vec<&Component> { 363 | let local = self.types(); 364 | let in_mods = self 365 | .modules() 366 | .iter() 367 | .filter(|&module| open_modules.contains(&module.name)) 368 | .map(|module| match &module.body { 369 | ComponentBody::Module { components } => components 370 | .iter() 371 | .filter(|c| matches!(c.visibility, Visibility::Public)) 372 | .filter(|c| matches!(c.body, ComponentBody::Type { .. })) 373 | .collect_vec(), 374 | _ => unreachable!(), 375 | }) 376 | .concat(); 377 | [local, in_mods].concat() 378 | } 379 | 380 | pub fn variants_external(&self, open_modules: &[String]) -> Vec<&Component> { 381 | let local = self.variants(); 382 | let in_mods = self 383 | .modules() 384 | .iter() 385 | .map(|module| match &module.body { 386 | ComponentBody::Module { components } => components 387 | .iter() 388 | .filter(|c| matches!(c.visibility, Visibility::Public)) 389 | .filter(|c| matches!(c.body, ComponentBody::Variant { .. })) 390 | .collect_vec(), 391 | _ => unreachable!(), 392 | }) 393 | .concat(); 394 | [local, in_mods].concat() 395 | } 396 | 397 | pub fn inline_cmds_external(&self, open_modules: &[String]) -> Vec<&Component> { 398 | let local = self.inline_cmds(); 399 | 400 | let in_mods = self 401 | .modules() 402 | .iter() 403 | .filter(|&module| open_modules.contains(&module.name)) 404 | .map(|module| match &module.body { 405 | ComponentBody::Module { components } => components 406 | .iter() 407 | .filter(|c| matches!(c.visibility, Visibility::Public)) 408 | .filter(|c| matches!(c.body, ComponentBody::InlineCmd { .. })) 409 | .collect_vec(), 410 | _ => unreachable!(), 411 | }) 412 | .concat(); 413 | 414 | let in_mods_direct = self 415 | .modules() 416 | .iter() 417 | .map(|module| match &module.body { 418 | ComponentBody::Module { components } => components 419 | .iter() 420 | .filter(|c| matches!(c.visibility, Visibility::Direct)) 421 | .filter(|c| matches!(c.body, ComponentBody::InlineCmd { .. })) 422 | .collect_vec(), 423 | _ => unreachable!(), 424 | }) 425 | .concat(); 426 | 427 | [local, in_mods, in_mods_direct].concat() 428 | } 429 | 430 | pub fn block_cmds_external(&self, open_modules: &[String]) -> Vec<&Component> { 431 | let local = self.block_cmds(); 432 | 433 | let in_mods = self 434 | .modules() 435 | .iter() 436 | .filter(|&module| open_modules.contains(&module.name)) 437 | .map(|module| match &module.body { 438 | ComponentBody::Module { components } => components 439 | .iter() 440 | .filter(|c| matches!(c.visibility, Visibility::Public)) 441 | .filter(|c| matches!(c.body, ComponentBody::BlockCmd { .. })) 442 | .collect_vec(), 443 | _ => unreachable!(), 444 | }) 445 | .concat(); 446 | 447 | let in_mods_direct = self 448 | .modules() 449 | .iter() 450 | .map(|module| match &module.body { 451 | ComponentBody::Module { components } => components 452 | .iter() 453 | .filter(|c| matches!(c.visibility, Visibility::Direct)) 454 | .filter(|c| matches!(c.body, ComponentBody::BlockCmd { .. })) 455 | .collect_vec(), 456 | _ => unreachable!(), 457 | }) 458 | .concat(); 459 | 460 | [local, in_mods, in_mods_direct].concat() 461 | } 462 | 463 | pub fn math_cmds_external(&self, open_modules: &[String]) -> Vec<&Component> { 464 | let local = self.math_cmds(); 465 | 466 | let in_mods = self 467 | .modules() 468 | .iter() 469 | .filter(|&module| open_modules.contains(&module.name)) 470 | .map(|module| match &module.body { 471 | ComponentBody::Module { components } => components 472 | .iter() 473 | .filter(|c| matches!(c.visibility, Visibility::Public)) 474 | .filter(|c| matches!(c.body, ComponentBody::MathCmd { .. })) 475 | .collect_vec(), 476 | _ => unreachable!(), 477 | }) 478 | .concat(); 479 | 480 | let in_mods_direct = self 481 | .modules() 482 | .iter() 483 | .map(|module| match &module.body { 484 | ComponentBody::Module { components } => components 485 | .iter() 486 | .filter(|c| matches!(c.visibility, Visibility::Direct)) 487 | .filter(|c| matches!(c.body, ComponentBody::MathCmd { .. })) 488 | .collect_vec(), 489 | _ => unreachable!(), 490 | }) 491 | .concat(); 492 | 493 | [local, in_mods, in_mods_direct].concat() 494 | } 495 | 496 | pub fn show_debug(&self) { 497 | for dep in &self.dependencies { 498 | info!("Dependency: {:?}", dep.name); 499 | } 500 | for module in self.modules() { 501 | info!("Module: {:?}", module.name); 502 | } 503 | for var in self.variables() { 504 | info!("Varable: {:?}", var.name); 505 | } 506 | for cmd in self.inline_cmds() { 507 | info!("InlineCmd: {:?}", cmd.name); 508 | } 509 | for cmd in self.block_cmds() { 510 | info!("BlockCmd: {:?}", cmd.name); 511 | } 512 | for cmd in self.math_cmds() { 513 | info!("BlockCmd: {:?}", cmd.name); 514 | } 515 | } 516 | } 517 | 518 | #[derive(Debug, Clone)] 519 | pub struct Dependency { 520 | /// パッケージ名。 521 | pub name: String, 522 | /// require か import か。 523 | pub kind: DependencyKind, 524 | /// `@require:` や `@import` が呼ばれている場所。 525 | pub definition: Span, 526 | /// 実際のファイルパス。パスを解決できなかったら None を返す。 527 | pub url: Option, 528 | } 529 | 530 | impl Dependency { 531 | fn from_header(headers: &[&Header], program_text: &ProgramText, url: &Url) -> Vec { 532 | let require_packages = headers.iter().map(|header| &header.name); 533 | let import_packages = headers.iter().map(|header| &header.name); 534 | 535 | let mut deps = vec![]; 536 | let home_path = std::env::var("HOME").map(PathBuf::from).ok(); 537 | let file_path = url.to_file_path().ok(); 538 | let parent_path = file_path.as_ref().map(|p| p.parent().unwrap().to_owned()); 539 | 540 | // require 系のパッケージの依存関係追加 541 | let require_dependencies = require_packages.map(|pkg| { 542 | let pkgname = program_text.get_text(pkg); 543 | // TODO: consider satyg file 544 | for pkgpath in 545 | require_candidate_paths(pkgname, parent_path.as_deref(), home_path.as_deref()) 546 | { 547 | if pkgpath.exists() { 548 | let url = Url::from_file_path(pkgpath).ok(); 549 | return Dependency { 550 | name: pkgname.to_owned(), 551 | kind: DependencyKind::Require, 552 | definition: pkg.span, 553 | url, 554 | }; 555 | } 556 | } 557 | Dependency { 558 | name: pkgname.to_owned(), 559 | kind: DependencyKind::Require, 560 | definition: pkg.span, 561 | url: None, 562 | } 563 | }); 564 | deps.extend(require_dependencies); 565 | 566 | if let Some(file_path) = file_path { 567 | // TODO: add validate 568 | let parent_path = file_path.parent().unwrap(); 569 | 570 | let import_dependencies = import_packages.map(|pkg| { 571 | let pkgname = program_text.get_text(pkg); 572 | // TODO: consider satyg file 573 | let pkg_path = parent_path.join(format!("{}.satyh", pkgname)); 574 | let url = if pkg_path.exists() { 575 | Url::from_file_path(pkg_path).ok() 576 | } else { 577 | None 578 | }; 579 | Dependency { 580 | name: pkgname.to_owned(), 581 | kind: DependencyKind::Import, 582 | definition: pkg.span, 583 | url, 584 | } 585 | }); 586 | 587 | deps.extend(import_dependencies); 588 | } 589 | 590 | deps 591 | } 592 | } 593 | 594 | /// 以下の4箇所から探す。 595 | /// - $PARENT_PATH/.satysfi/{kind}/packages/a.{ext} 596 | /// - $HOME/.satysfi/{kind}/packages/a.{ext} 597 | /// - /usr/local/share/satysfi/{kind}/packages/a.{ext} 598 | /// - /usr/share/satysfi/{kind}/packages/a.{ext} 599 | /// kind: local, dist 600 | /// ext: satyh, satyg 601 | fn require_candidate_paths( 602 | pkgname: &str, 603 | parent: Option<&Path>, 604 | home: Option<&Path>, 605 | ) -> Vec { 606 | let usr_local_share = Some(PathBuf::from("/usr/local/share/satysfi")); 607 | let usr_share = Some(PathBuf::from("/usr/share/satysfi")); 608 | let home = home.map(|p| p.join(".satysfi")); 609 | let parent = parent.map(|p| p.join(".satysfi")); 610 | [parent, home, usr_local_share, usr_share] 611 | .iter() 612 | .filter_map(|x| x.clone()) 613 | .map(|path| { 614 | vec![ 615 | path.join(format!("local/packages/{}.satyh", pkgname)), 616 | path.join(format!("local/packages/{}.satyg", pkgname)), 617 | path.join(format!("dist/packages/{}.satyh", pkgname)), 618 | path.join(format!("dist/packages/{}.satyg", pkgname)), 619 | ] 620 | }) 621 | .concat() 622 | } 623 | 624 | pub fn require_candidate_dirs(parent: Option<&Path>, home: Option<&Path>) -> Vec { 625 | let usr_local_share = Some(PathBuf::from("/usr/local/share/satysfi")); 626 | let usr_share = Some(PathBuf::from("/usr/share/satysfi")); 627 | let home = home.map(|p| p.join(".satysfi")); 628 | let parent = parent.map(|p| p.join(".satysfi")); 629 | [parent, home, usr_local_share, usr_share] 630 | .iter() 631 | .filter_map(|x| x.clone()) 632 | .map(|path| vec![path.join("local/packages"), path.join("dist/packages")]) 633 | .concat() 634 | } 635 | 636 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 637 | pub enum DependencyKind { 638 | Require, 639 | Import, 640 | } 641 | 642 | #[derive(Debug)] 643 | pub struct Component { 644 | /// 名前。 645 | pub name: String, 646 | /// 種類。 647 | pub body: ComponentBody, 648 | /// その変数がそのファイル内で有効なスコープ。 649 | pub scope: Span, 650 | /// 定義がどこにあるか。 651 | pub pos_definition: Span, 652 | /// 可視性。パッケージ内に直接定義されていたら public。 653 | /// module 内のときは signature があるかどうかで変わる。 654 | pub visibility: Visibility, 655 | /// モジュール内のパブリック変数のとき、宣言がどこにあるか。 656 | pub pos_declaration: Option, 657 | /// そのコンポーネントが定義されている URL。 658 | pub url: Url, 659 | } 660 | 661 | /// モジュールについての情報。モジュール内で定義された変数を格納するのに用いる。 662 | struct ModuleInfo<'a> { 663 | module_span: Span, 664 | sigs: &'a [Signature], 665 | } 666 | 667 | impl<'a> ModuleInfo<'a> { 668 | fn map_types<'b>(&self, program_text: &'b ProgramText) -> HashMap<&'b str, &Signature> { 669 | self.sigs 670 | .iter() 671 | .filter_map(|sig| match sig { 672 | Signature::Type { name, .. } => Some((program_text.get_text(name), sig)), 673 | _ => None, 674 | }) 675 | .collect() 676 | } 677 | fn map_val<'b>(&self, program_text: &'b ProgramText) -> HashMap<&'b str, &Signature> { 678 | self.sigs 679 | .iter() 680 | .filter_map(|sig| match sig { 681 | Signature::Val { var, .. } => Some((program_text.get_text(var), sig)), 682 | _ => None, 683 | }) 684 | .collect() 685 | } 686 | fn map_direct<'b>(&self, program_text: &'b ProgramText) -> HashMap<&'b str, &Signature> { 687 | self.sigs 688 | .iter() 689 | .filter_map(|sig| match sig { 690 | Signature::Direct { var, .. } => Some((program_text.get_text(var), sig)), 691 | _ => None, 692 | }) 693 | .collect() 694 | } 695 | } 696 | 697 | impl Component { 698 | fn from_preamble( 699 | preamble: &[&Statement], 700 | program_text: &ProgramText, 701 | url: &Url, 702 | ) -> Vec { 703 | preamble 704 | .iter() 705 | .map(|stmt| Component::from_stmt(stmt, None, program_text, url)) 706 | .concat() 707 | } 708 | 709 | fn from_struct_stmts( 710 | module_info: &ModuleInfo, 711 | struct_stmts: &[&Statement], 712 | program_text: &ProgramText, 713 | url: &Url, 714 | ) -> Vec { 715 | struct_stmts 716 | .iter() 717 | .map(|stmt| Component::from_stmt(stmt, Some(module_info), program_text, url)) 718 | .concat() 719 | } 720 | 721 | /// Statement から Component を生成する。 722 | /// Component は複数出てくることもあるため、戻り値はベクトル。というのも 723 | /// let (x, y) = ... 724 | /// みたいな式では x, y という2つの Component が作成されるため。 725 | fn from_stmt( 726 | stmt: &Statement, 727 | module_info: Option<&ModuleInfo>, 728 | program_text: &ProgramText, 729 | url: &Url, 730 | ) -> Vec { 731 | match stmt { 732 | Statement::Let { pat, expr, .. } => { 733 | let vars = pat.pickup(Rule::var); 734 | let scope = { 735 | let start = expr.span.end; 736 | let end = if let Some(info) = module_info { 737 | info.module_span.end 738 | } else { 739 | program_text.cst.span.end 740 | }; 741 | Span { start, end } 742 | }; 743 | vars.into_iter() 744 | .map(|var| Component::new_variable(var, scope, module_info, program_text, url)) 745 | .collect() 746 | } 747 | 748 | Statement::LetRec(inners) => { 749 | let scope = { 750 | // recursive のため自身の関数の定義内で自身の関数を呼び出せる 751 | let start = inners.get(0).unwrap().pattern.span.end; 752 | let end = if let Some(info) = module_info { 753 | info.module_span.end 754 | } else { 755 | program_text.cst.span.end 756 | }; 757 | Span { start, end } 758 | }; 759 | inners 760 | .iter() 761 | .map(|LetRecInner { pattern, .. }| { 762 | let vars = pattern.pickup(Rule::var); 763 | vars.into_iter() 764 | .map(|var| { 765 | Component::new_variable(var, scope, module_info, program_text, url) 766 | }) 767 | .collect() 768 | }) 769 | .concat() 770 | } 771 | 772 | Statement::LetInline { cmd, expr, .. } => { 773 | let name = program_text.get_text(cmd).to_owned(); 774 | let scope = { 775 | let start = expr.span.end; 776 | let end = if let Some(info) = module_info { 777 | info.module_span.end 778 | } else { 779 | program_text.cst.span.end 780 | }; 781 | Span { start, end } 782 | }; 783 | let pos_definition = cmd.span; 784 | let (visibility, pos_declaration, signature) = { 785 | if let Some(info) = module_info { 786 | let sig_val_map = info.map_val(program_text); 787 | let sig_direct_map = info.map_direct(program_text); 788 | let name = program_text.get_text(cmd); 789 | match (sig_direct_map.get(name), sig_val_map.get(name)) { 790 | (Some(Signature::Direct { var, signature, .. }), _) => { 791 | (Visibility::Direct, Some(var.span), Some(signature)) 792 | } 793 | (None, Some(Signature::Val { var, signature, .. })) => { 794 | (Visibility::Public, Some(var.span), Some(signature)) 795 | } 796 | _ => (Visibility::Private, None, None), 797 | } 798 | } else { 799 | (Visibility::Public, None, None) 800 | } 801 | }; 802 | let body = if let Some(signature) = signature { 803 | let text = program_text.get_text(signature); 804 | let csts = type_inline_cmd(text).ok().unwrap().inner; 805 | ComponentBody::InlineCmd { 806 | type_declaration: Some(signature.span), 807 | type_args: csts 808 | .into_iter() 809 | .map(|cst| text[cst.span.start..cst.span.end].to_owned()) 810 | .collect_vec(), 811 | } 812 | } else { 813 | ComponentBody::InlineCmd { 814 | type_declaration: None, 815 | type_args: vec![], 816 | } 817 | }; 818 | vec![Component { 819 | name, 820 | body, 821 | scope, 822 | pos_definition, 823 | visibility, 824 | pos_declaration, 825 | url: url.clone(), 826 | }] 827 | } 828 | 829 | Statement::LetBlock { cmd, expr, .. } => { 830 | let name = program_text.get_text(cmd).to_owned(); 831 | let start = expr.span.end; 832 | let end = if let Some(info) = module_info { 833 | info.module_span.end 834 | } else { 835 | program_text.cst.span.end 836 | }; 837 | let scope = Span { start, end }; 838 | let pos_definition = cmd.span; 839 | let (visibility, pos_declaration, signature) = { 840 | if let Some(info) = module_info { 841 | let sig_val_map = info.map_val(program_text); 842 | let sig_direct_map = info.map_direct(program_text); 843 | let name = program_text.get_text(cmd); 844 | match (sig_direct_map.get(name), sig_val_map.get(name)) { 845 | (Some(Signature::Direct { var, signature, .. }), _) => { 846 | (Visibility::Direct, Some(var.span), Some(signature)) 847 | } 848 | (None, Some(Signature::Val { var, signature, .. })) => { 849 | (Visibility::Public, Some(var.span), Some(signature)) 850 | } 851 | _ => (Visibility::Private, None, None), 852 | } 853 | } else { 854 | (Visibility::Public, None, None) 855 | } 856 | }; 857 | let body = if let Some(signature) = signature { 858 | let text = program_text.get_text(signature); 859 | let csts = type_block_cmd(text).ok().unwrap().inner; 860 | ComponentBody::BlockCmd { 861 | type_declaration: Some(signature.span), 862 | type_args: csts 863 | .into_iter() 864 | .map(|cst| text[cst.span.start..cst.span.end].to_owned()) 865 | .collect_vec(), 866 | } 867 | } else { 868 | ComponentBody::BlockCmd { 869 | type_declaration: None, 870 | type_args: vec![], 871 | } 872 | }; 873 | vec![Component { 874 | name, 875 | body, 876 | scope, 877 | pos_definition, 878 | visibility, 879 | pos_declaration, 880 | url: url.clone(), 881 | }] 882 | } 883 | 884 | Statement::LetMath { cmd, expr, .. } => { 885 | let name = program_text.get_text(cmd).to_owned(); 886 | let start = expr.span.end; 887 | let end = if let Some(info) = module_info { 888 | info.module_span.end 889 | } else { 890 | program_text.cst.span.end 891 | }; 892 | let scope = Span { start, end }; 893 | let pos_definition = cmd.span; 894 | let (visibility, pos_declaration, signature) = { 895 | if let Some(info) = module_info { 896 | let sig_val_map = info.map_val(program_text); 897 | let sig_direct_map = info.map_direct(program_text); 898 | let name = program_text.get_text(cmd); 899 | match (sig_direct_map.get(name), sig_val_map.get(name)) { 900 | (Some(Signature::Direct { var, signature, .. }), _) => { 901 | (Visibility::Direct, Some(var.span), Some(signature)) 902 | } 903 | (None, Some(Signature::Val { var, signature, .. })) => { 904 | (Visibility::Public, Some(var.span), Some(signature)) 905 | } 906 | _ => (Visibility::Private, None, None), 907 | } 908 | } else { 909 | (Visibility::Public, None, None) 910 | } 911 | }; 912 | let body = if let Some(signature) = signature { 913 | let text = program_text.get_text(signature); 914 | let csts = type_math_cmd(text).ok().unwrap().inner; 915 | ComponentBody::MathCmd { 916 | type_declaration: Some(signature.span), 917 | type_args: csts 918 | .into_iter() 919 | .map(|cst| text[cst.span.start..cst.span.end].to_owned()) 920 | .collect_vec(), 921 | } 922 | } else { 923 | ComponentBody::MathCmd { 924 | type_declaration: None, 925 | type_args: vec![], 926 | } 927 | }; 928 | vec![Component { 929 | name, 930 | body, 931 | scope, 932 | pos_definition, 933 | visibility, 934 | pos_declaration, 935 | url: url.clone(), 936 | }] 937 | } 938 | 939 | Statement::LetMutable { var, expr } => { 940 | let name = program_text.get_text(var).to_owned(); 941 | let body = ComponentBody::Variable { 942 | type_declaration: None, 943 | }; 944 | let scope = { 945 | let start = expr.span.end; 946 | let end = program_text.cst.span.end; 947 | Span { start, end } 948 | }; 949 | let pos_definition = var.span; 950 | let (visibility, pos_declaration) = if let Some(info) = module_info { 951 | let sig_val_map = info.map_val(program_text); 952 | let name = program_text.get_text(var); 953 | match sig_val_map.get(name) { 954 | Some(Signature::Val { var, .. }) => { 955 | let pos_declaration = var.span; 956 | (Visibility::Public, Some(pos_declaration)) 957 | } 958 | _ => (Visibility::Private, None), 959 | } 960 | } else { 961 | (Visibility::Public, None) 962 | }; 963 | vec![Component { 964 | name, 965 | body, 966 | scope, 967 | pos_definition, 968 | visibility, 969 | pos_declaration, 970 | url: url.clone(), 971 | }] 972 | } 973 | 974 | Statement::Type(inners) => inners 975 | .iter() 976 | .map( 977 | |TypeInner { 978 | name: type_name, .. 979 | }| { 980 | let name = program_text.get_text(type_name).to_owned(); 981 | let stmt_span = program_text.cst.get_parent(type_name).unwrap().span; 982 | let body = ComponentBody::Type; 983 | let scope = { 984 | let start = stmt_span.end; 985 | let end = program_text.cst.span.end; 986 | Span { start, end } 987 | }; 988 | let pos_definition = type_name.span; 989 | let (visibility, pos_declaration) = if let Some(info) = module_info { 990 | let sig_val_map = info.map_val(program_text); 991 | let name = program_text.get_text(type_name); 992 | match sig_val_map.get(name) { 993 | Some(Signature::Val { var, .. }) => { 994 | let pos_declaration = var.span; 995 | (Visibility::Public, Some(pos_declaration)) 996 | } 997 | _ => (Visibility::Private, None), 998 | } 999 | } else { 1000 | (Visibility::Public, None) 1001 | }; 1002 | Component { 1003 | name, 1004 | body, 1005 | scope, 1006 | pos_definition, 1007 | visibility, 1008 | pos_declaration, 1009 | url: url.clone(), 1010 | } 1011 | }, 1012 | ) 1013 | .collect(), 1014 | 1015 | Statement::Module { 1016 | name: mod_name, 1017 | signature, 1018 | statements, 1019 | } => { 1020 | let name = program_text.get_text(mod_name).to_owned(); 1021 | let module_span = program_text.cst.get_parent(mod_name).unwrap().span; 1022 | let body = { 1023 | let module_info = ModuleInfo { 1024 | module_span, 1025 | sigs: &signature, 1026 | }; 1027 | let struct_stmt = statements.iter().collect_vec(); 1028 | let components = 1029 | Component::from_struct_stmts(&module_info, &struct_stmt, program_text, url); 1030 | ComponentBody::Module { components } 1031 | }; 1032 | let scope = { 1033 | let start = module_span.end; 1034 | let end = program_text.cst.span.end; 1035 | Span { start, end } 1036 | }; 1037 | let pos_definition = mod_name.span; 1038 | let visibility = Default::default(); 1039 | let pos_declaration = None; 1040 | vec![Component { 1041 | name, 1042 | body, 1043 | scope, 1044 | pos_definition, 1045 | visibility, 1046 | pos_declaration, 1047 | url: url.clone(), 1048 | }] 1049 | } 1050 | 1051 | Statement::Open(_) => vec![], 1052 | } 1053 | } 1054 | 1055 | fn new_variable( 1056 | var: &Cst, 1057 | scope: Span, 1058 | module_info: Option<&ModuleInfo>, 1059 | program_text: &ProgramText, 1060 | url: &Url, 1061 | ) -> Component { 1062 | let name = program_text.get_text(var).to_owned(); 1063 | let pos_definition = var.span; 1064 | let (visibility, pos_declaration, type_declaration) = if let Some(info) = module_info { 1065 | let sig_val_map = info.map_val(program_text); 1066 | let name = program_text.get_text(var); 1067 | match sig_val_map.get(name) { 1068 | Some(Signature::Val { var, signature, .. }) => { 1069 | let pos_declaration = var.span; 1070 | let type_declaration = signature.span; 1071 | ( 1072 | Visibility::Public, 1073 | Some(pos_declaration), 1074 | Some(type_declaration), 1075 | ) 1076 | } 1077 | _ => (Visibility::Private, None, None), 1078 | } 1079 | } else { 1080 | (Visibility::Public, None, None) 1081 | }; 1082 | let body = ComponentBody::Variable { type_declaration }; 1083 | Component { 1084 | name, 1085 | body, 1086 | scope, 1087 | pos_definition, 1088 | visibility, 1089 | pos_declaration, 1090 | url: url.clone(), 1091 | } 1092 | } 1093 | } 1094 | 1095 | #[derive(Debug)] 1096 | pub enum ComponentBody { 1097 | Module { 1098 | components: Vec, 1099 | }, 1100 | Variable { 1101 | /// let 式や signature に型情報を書いている場合、その場所。 1102 | type_declaration: Option, 1103 | }, 1104 | Type, 1105 | Variant { 1106 | /// その Variant が属する型の名前。 1107 | type_name: String, 1108 | }, 1109 | InlineCmd { 1110 | /// signature に型情報がある場合、その場所。 1111 | type_declaration: Option, 1112 | type_args: Vec, 1113 | }, 1114 | BlockCmd { 1115 | /// signature に型情報がある場合、その場所。 1116 | type_declaration: Option, 1117 | type_args: Vec, 1118 | }, 1119 | MathCmd { 1120 | /// signature に型情報がある場合、その場所。 1121 | type_declaration: Option, 1122 | type_args: Vec, 1123 | }, 1124 | } 1125 | 1126 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 1127 | pub enum Visibility { 1128 | Public, 1129 | Private, 1130 | Direct, 1131 | } 1132 | 1133 | impl Default for Visibility { 1134 | fn default() -> Self { 1135 | Visibility::Public 1136 | } 1137 | } 1138 | 1139 | #[derive(Debug)] 1140 | pub struct OpenModule { 1141 | name: String, 1142 | scope: Span, 1143 | url: Url, 1144 | } 1145 | 1146 | impl OpenModule { 1147 | fn from_preamble( 1148 | preamble: &[&Statement], 1149 | program_text: &ProgramText, 1150 | url: &Url, 1151 | ) -> Vec { 1152 | preamble 1153 | .iter() 1154 | .filter_map(|stmt| OpenModule::from_stmt(stmt, None, program_text, url)) 1155 | .collect_vec() 1156 | } 1157 | 1158 | fn from_stmt( 1159 | stmt: &Statement, 1160 | module_info: Option<&ModuleInfo>, 1161 | program_text: &ProgramText, 1162 | url: &Url, 1163 | ) -> Option { 1164 | if let Statement::Open(cst) = stmt { 1165 | let name = program_text.get_text(cst).to_owned(); 1166 | let scope = { 1167 | let start = cst.span.end; 1168 | let end = if let Some(info) = module_info { 1169 | info.module_span.end 1170 | } else { 1171 | program_text.cst.span.end 1172 | }; 1173 | Span { start, end } 1174 | }; 1175 | let url = url.clone(); 1176 | Some(OpenModule { name, scope, url }) 1177 | } else { 1178 | None 1179 | } 1180 | } 1181 | } 1182 | -------------------------------------------------------------------------------- /src/documents/environments.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use log::debug; 3 | use lspower::lsp::Url; 4 | 5 | use crate::documents::ConvertPos; 6 | 7 | use super::{DocumentData, SourceSpan}; 8 | use satysfi_parser::{Rule, Span}; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct Environments { 12 | pub package: Vec, 13 | pub variable: Vec, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct EnvPackage { 18 | name: String, 19 | url: Url, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct EnvVariable { 24 | /// 変数・コマンドの種類 25 | pub kind: VariableKind, 26 | /// 変数・コマンドの名前 27 | pub name: String, 28 | /// そのコマンドが定義されているモジュールの名前(あれば) 29 | pub mod_name: Option, 30 | /// definition が記載されている場所の Cst(テキストは Url を参照する必要がある) 31 | pub definition: SourceSpan, 32 | /// declaration が記載されている場所の Cst(テキストは Url を参照する必要がある) 33 | pub declaration: Option, 34 | // /// そのコマンドが有効な場所。補完候補を出すときや変数の shadowing があるときに重要 35 | // scope: Vec, 36 | } 37 | 38 | #[derive(Debug, PartialEq, Eq)] 39 | pub enum VariableKind { 40 | /// 変数 41 | Variable, 42 | /// 関数(変数のうち、特に 'a -> 'b の形をしているもの) 43 | Function, 44 | /// インラインコマンド 45 | InlineCmd, 46 | /// ブロックコマンド 47 | BlockCmd, 48 | /// 数式コマンド 49 | MathCmd, 50 | /// ユーザ定義型 51 | CustomType, 52 | } 53 | 54 | impl Environments { 55 | /// その url のファイル内で定義されたすべてのコマンド情報を削除する。 56 | pub fn remove_defined_in_url(&mut self, url: &Url) { 57 | // self.variable.drain_filter(|var| var.definition.url == url); 58 | let mut i = 0; 59 | while i != self.variable.len() { 60 | if &self.variable[i].definition.url == url { 61 | self.variable.remove(i); 62 | } else { 63 | i += 1; 64 | } 65 | } 66 | } 67 | 68 | pub fn update(&mut self, url: &Url, data: &DocumentData) { 69 | self.remove_defined_in_url(url); 70 | self.register_inline_cmd(url, data); 71 | self.register_block_cmd(url, data); 72 | self.register_math_cmd(url, data); 73 | self.register_variable(url, data); 74 | } 75 | 76 | fn register_inline_cmd(&mut self, url: &Url, data: &DocumentData) { 77 | if let DocumentData::ParseSuccessful(csttext) = &data { 78 | let cst = &csttext.cst; 79 | let stmts_ctx = cst.pickup(Rule::let_inline_stmt_ctx); 80 | for stmt in stmts_ctx { 81 | let cmd_name = stmt 82 | .inner 83 | .get(1) 84 | .expect("expected Rule::inline_cmd_name, got nothing"); 85 | let cmd = EnvVariable { 86 | kind: VariableKind::InlineCmd, 87 | name: csttext.get_text(cmd_name).to_owned(), 88 | mod_name: None, 89 | definition: SourceSpan { 90 | url: url.clone(), 91 | span: stmt.span, 92 | }, 93 | declaration: None, 94 | }; 95 | self.variable.push(cmd); 96 | } 97 | 98 | let stmts_noctx = cst.pickup(Rule::let_inline_stmt_noctx); 99 | for stmt in stmts_noctx { 100 | let cmd_name = stmt 101 | .inner 102 | .get(0) 103 | .expect("expected Rule::inline_cmd_name, got nothing"); 104 | let cmd = EnvVariable { 105 | kind: VariableKind::InlineCmd, 106 | name: csttext.get_text(cmd_name).to_owned(), 107 | mod_name: None, 108 | definition: SourceSpan { 109 | url: url.clone(), 110 | span: stmt.span, 111 | }, 112 | declaration: None, 113 | }; 114 | self.variable.push(cmd); 115 | } 116 | } 117 | } 118 | 119 | fn register_block_cmd(&mut self, url: &Url, data: &DocumentData) { 120 | if let DocumentData::ParseSuccessful(csttext) = &data { 121 | let cst = &csttext.cst; 122 | let stmts_ctx = cst.pickup(Rule::let_block_stmt_ctx); 123 | for stmt in stmts_ctx { 124 | let cmd_name = stmt 125 | .inner 126 | .get(1) 127 | .expect("expected Rule::block_cmd_name, got nothing"); 128 | let cmd = EnvVariable { 129 | kind: VariableKind::BlockCmd, 130 | name: csttext.get_text(cmd_name).to_owned(), 131 | mod_name: None, 132 | definition: SourceSpan { 133 | url: url.clone(), 134 | span: stmt.span, 135 | }, 136 | declaration: None, 137 | }; 138 | self.variable.push(cmd); 139 | } 140 | 141 | let stmts_noctx = cst.pickup(Rule::let_block_stmt_noctx); 142 | for stmt in stmts_noctx { 143 | let cmd_name = stmt 144 | .inner 145 | .get(0) 146 | .expect("expected Rule::block_cmd_name, got nothing"); 147 | let cmd = EnvVariable { 148 | kind: VariableKind::BlockCmd, 149 | name: csttext.get_text(cmd_name).to_owned(), 150 | mod_name: None, 151 | definition: SourceSpan { 152 | url: url.clone(), 153 | span: stmt.span, 154 | }, 155 | declaration: None, 156 | }; 157 | self.variable.push(cmd); 158 | } 159 | } 160 | } 161 | 162 | fn register_math_cmd(&mut self, url: &Url, data: &DocumentData) { 163 | if let DocumentData::ParseSuccessful(csttext) = &data { 164 | let cst = &csttext.cst; 165 | let stmts = cst.pickup(Rule::let_math_stmt); 166 | for stmt in stmts { 167 | let cmd_name = stmt 168 | .inner 169 | .get(0) 170 | .expect("expected Rule::math_cmd_name, got nothing"); 171 | let cmd = EnvVariable { 172 | kind: VariableKind::MathCmd, 173 | name: csttext.get_text(cmd_name).to_owned(), 174 | mod_name: None, 175 | definition: SourceSpan { 176 | url: url.clone(), 177 | span: stmt.span, 178 | }, 179 | declaration: None, 180 | }; 181 | self.variable.push(cmd); 182 | } 183 | } 184 | } 185 | 186 | fn register_variable(&mut self, url: &Url, data: &DocumentData) { 187 | if let DocumentData::ParseSuccessful(csttext) = &data { 188 | let cst = &csttext.cst; 189 | let stmts = cst.pickup(Rule::let_stmt); 190 | for stmt in stmts { 191 | let cst_pattern = stmt 192 | .inner 193 | .get(0) 194 | .expect("expected Rule::pattern, got nothing"); 195 | if let Some(Rule::var) = cst_pattern.inner.get(0).map(|cst| cst.rule) { 196 | let cmd_name = cst_pattern.inner.get(0).unwrap(); 197 | let cmd = EnvVariable { 198 | kind: VariableKind::Variable, 199 | name: csttext.get_text(cmd_name).to_owned(), 200 | mod_name: None, 201 | definition: SourceSpan { 202 | url: url.clone(), 203 | span: stmt.span, 204 | }, 205 | declaration: None, 206 | }; 207 | self.variable.push(cmd); 208 | }; 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/language_server.rs: -------------------------------------------------------------------------------- 1 | use log::{error, info}; 2 | use lspower::{ 3 | jsonrpc::Result as LspResult, 4 | lsp::{ 5 | CompletionParams, CompletionResponse, DidChangeTextDocumentParams, 6 | DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams, 7 | GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams, InitializeParams, 8 | InitializeResult, ServerInfo, TextEdit, 9 | }, 10 | }; 11 | use std::sync::Arc; 12 | 13 | use lspower::Client; 14 | 15 | use crate::{ 16 | config::Config, 17 | documents::{DocumentCache, DocumentData}, 18 | util::UrlPos, 19 | }; 20 | 21 | use self::diagnostics::{get_diagnostics, DiagnosticCollection}; 22 | 23 | mod capabilities; 24 | mod completion; 25 | mod definition; 26 | mod diagnostics; 27 | mod hover; 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct LanguageServer(Arc>); 31 | 32 | impl LanguageServer { 33 | pub fn new(client: Client) -> Self { 34 | Self(Arc::new(tokio::sync::Mutex::new(Inner::new(client)))) 35 | } 36 | } 37 | 38 | #[lspower::async_trait] 39 | impl lspower::LanguageServer for LanguageServer { 40 | async fn initialize(&self, params: InitializeParams) -> LspResult { 41 | self.0.lock().await.initialize(params).await 42 | } 43 | 44 | async fn completion(&self, params: CompletionParams) -> LspResult> { 45 | self.0.lock().await.get_completion(params).await 46 | } 47 | 48 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 49 | self.0.lock().await.did_change(params).await; 50 | } 51 | 52 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 53 | self.0.lock().await.did_open(params).await; 54 | } 55 | 56 | async fn hover(&self, params: HoverParams) -> LspResult> { 57 | self.0.lock().await.hover(params).await 58 | } 59 | 60 | async fn did_save(&self, params: DidSaveTextDocumentParams) { 61 | self.0.lock().await.did_save(params).await; 62 | } 63 | 64 | async fn goto_definition( 65 | &self, 66 | params: GotoDefinitionParams, 67 | ) -> LspResult> { 68 | self.0.lock().await.goto_definition(params).await 69 | } 70 | 71 | async fn formatting( 72 | &self, 73 | params: DocumentFormattingParams, 74 | ) -> LspResult>> { 75 | Ok(self.0.lock().await.formatting(params).await) 76 | } 77 | 78 | async fn shutdown(&self) -> LspResult<()> { 79 | Ok(()) 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub struct Inner { 85 | /// The LSP client that this LSP server is connected to. 86 | client: Client, 87 | /// Configuration information. 88 | config: Config, 89 | /// A collection of diagnostics from different sources. 90 | diagnostics: DiagnosticCollection, 91 | /// The "in-memory" documents in the editor which can be updated and changed. 92 | documents: DocumentCache, 93 | } 94 | 95 | impl Inner { 96 | fn new(client: Client) -> Self { 97 | Self { 98 | client, 99 | config: Config::default(), 100 | diagnostics: DiagnosticCollection::default(), 101 | documents: DocumentCache::default(), 102 | } 103 | } 104 | 105 | async fn initialize(&mut self, params: InitializeParams) -> LspResult { 106 | let capabilities = capabilities::server_capabilities(¶ms.capabilities); 107 | let server_info = ServerInfo { 108 | name: "satysfi-language-server".to_owned(), 109 | version: Some(crate::version()), 110 | }; 111 | 112 | if let Some(client_info) = params.client_info { 113 | info!( 114 | "Connected to \"{}\" {}", 115 | client_info.name, 116 | client_info.version.unwrap_or_default(), 117 | ); 118 | } 119 | 120 | Ok(InitializeResult { 121 | capabilities, 122 | server_info: Some(server_info), 123 | }) 124 | } 125 | 126 | async fn get_completion( 127 | &self, 128 | params: CompletionParams, 129 | ) -> LspResult> { 130 | let url = params.text_document_position.text_document.uri; 131 | let pos = params.text_document_position.position; 132 | let trigger = params.context.and_then(|ctx| ctx.trigger_character); 133 | if self.documents.0.get(&url).is_some() { 134 | let curpos = UrlPos { url, pos }; 135 | Ok(self 136 | .documents 137 | .get_completion_list(&curpos, trigger.as_deref())) 138 | } else { 139 | Ok(None) 140 | } 141 | } 142 | 143 | async fn formatting(&self, params: DocumentFormattingParams) -> Option> { 144 | let uri = params.text_document.uri; 145 | let d_data = self.documents.0.get(&uri).unwrap(); 146 | match d_data { 147 | DocumentData::Parsed { program_text, .. } => { 148 | let result = satysfi_formatter::formatting(&program_text.text, params.options); 149 | Some(result) 150 | } 151 | DocumentData::NotParsed { text, .. } => { 152 | let result = satysfi_formatter::formatting(&text, params.options); 153 | Some(result) 154 | } 155 | } 156 | } 157 | 158 | async fn did_change(&mut self, params: DidChangeTextDocumentParams) { 159 | let url = params.text_document.uri; 160 | if let Some(cc) = params.content_changes.into_iter().last() { 161 | let text = cc.text; 162 | let doc_data = DocumentData::new(&text, &url); 163 | 164 | if let DocumentData::Parsed { environment, .. } = &doc_data { 165 | self.documents 166 | .register_dependencies(environment.dependencies()); 167 | } 168 | 169 | let diags = get_diagnostics(&doc_data); 170 | self.documents.0.insert(url.clone(), doc_data); 171 | self.client.publish_diagnostics(url, diags, None).await; 172 | } else { 173 | error!("failed to extract changes of document {:?}!", url); 174 | } 175 | } 176 | 177 | async fn did_open(&mut self, params: DidOpenTextDocumentParams) { 178 | let url = params.text_document.uri; 179 | let text = params.text_document.text; 180 | let doc_data = DocumentData::new(&text, &url); 181 | 182 | if let DocumentData::Parsed { environment, .. } = &doc_data { 183 | self.documents 184 | .register_dependencies(environment.dependencies()); 185 | } 186 | 187 | let diags = get_diagnostics(&doc_data); 188 | self.documents.0.insert(url.clone(), doc_data); 189 | self.client.publish_diagnostics(url, diags, None).await; 190 | } 191 | 192 | async fn did_save(&mut self, params: DidSaveTextDocumentParams) { 193 | let url = params.text_document.uri; 194 | let doc_data = self.documents.0.get(&url); 195 | 196 | if let Some(doc_data) = doc_data { 197 | let diags = get_diagnostics(&doc_data); 198 | self.client.publish_diagnostics(url, diags, None).await; 199 | 200 | doc_data.show_envs_debug(); 201 | } 202 | } 203 | 204 | async fn goto_definition( 205 | &mut self, 206 | params: GotoDefinitionParams, 207 | ) -> LspResult> { 208 | let url = params.text_document_position_params.text_document.uri; 209 | let pos = params.text_document_position_params.position; 210 | 211 | if self.documents.0.get(&url).is_some() { 212 | let curpos = UrlPos { url, pos }; 213 | Ok(self.documents.get_definition_list(&curpos)) 214 | } else { 215 | Ok(None) 216 | } 217 | } 218 | 219 | async fn hover(&mut self, params: HoverParams) -> LspResult> { 220 | let url = params.text_document_position_params.text_document.uri; 221 | let pos = params.text_document_position_params.position; 222 | 223 | if self.documents.0.get(&url).is_some() { 224 | let curpos = UrlPos { url, pos }; 225 | Ok(self.documents.get_hover(&curpos)) 226 | } else { 227 | Ok(None) 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/language_server/capabilities.rs: -------------------------------------------------------------------------------- 1 | use lspower::lsp::{ 2 | ClientCapabilities, CompletionOptions, HoverProviderCapability, OneOf, ServerCapabilities, 3 | TextDocumentSyncCapability, TextDocumentSyncKind, DocumentFormattingOptions, WorkDoneProgressOptions, 4 | }; 5 | 6 | /// Client の capabilities に合わせて Server 側の capabilities を返す。 7 | /// 現在は Client 側の capabilities を一切見ずに固定の値を返す。 8 | pub fn server_capabilities(_client_capabilities: &ClientCapabilities) -> ServerCapabilities { 9 | ServerCapabilities { 10 | // text document sync は一旦 full で行う 11 | // TODO: TextDocumentSyncKind::Incremental のほうがおそらくパフォーマンスが高い 12 | text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)), 13 | selection_range_provider: None, 14 | hover_provider: Some(HoverProviderCapability::Simple(true)), 15 | completion_provider: Some(CompletionOptions { 16 | trigger_characters: Some(vec![ 17 | "\\".to_owned(), 18 | "+".to_owned(), 19 | "#".to_owned(), 20 | "@".to_owned(), 21 | ".".to_owned(), 22 | ]), 23 | ..Default::default() 24 | }), 25 | signature_help_provider: None, 26 | definition_provider: Some(OneOf::Left(true)), 27 | type_definition_provider: None, 28 | implementation_provider: None, 29 | references_provider: None, 30 | document_highlight_provider: None, 31 | document_symbol_provider: None, 32 | workspace_symbol_provider: None, 33 | code_action_provider: None, 34 | code_lens_provider: None, 35 | document_formatting_provider: Some(OneOf::Left(true)), 36 | document_range_formatting_provider: None, 37 | document_on_type_formatting_provider: None, 38 | rename_provider: None, 39 | document_link_provider: None, 40 | color_provider: None, 41 | folding_range_provider: None, 42 | declaration_provider: None, 43 | execute_command_provider: None, 44 | workspace: None, 45 | call_hierarchy_provider: None, 46 | semantic_tokens_provider: None, 47 | moniker_provider: None, 48 | linked_editing_range_provider: None, 49 | experimental: None, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/language_server/completion.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::{collections::HashMap, path::PathBuf}; 3 | 4 | use glob::glob; 5 | use itertools::Itertools; 6 | use log::{debug, info}; 7 | use lspower::lsp::{ 8 | CompletionItem, CompletionItemKind, CompletionResponse, CompletionTextEdit, Documentation, 9 | InsertTextFormat, MarkupContent, MarkupKind, Position, Range, TextEdit, Url, 10 | }; 11 | use satysfi_parser::{LineCol, Mode}; 12 | use serde::Deserialize; 13 | 14 | use crate::{ 15 | documents::{require_candidate_dirs, ComponentBody, DocumentCache, DocumentData, Visibility}, 16 | util::{ConvertPosition, UrlPos}, 17 | }; 18 | 19 | pub const COMPLETION_RESOUCES: &str = include_str!("../resource/completion_items.toml"); 20 | 21 | pub fn get_primitive_list() -> Vec { 22 | let resources = get_resouce_items(); 23 | let items = resources 24 | .into_iter() 25 | // .filter(|(key, _)| key == "primitive" || key == "statement") 26 | .map(|(_, val)| val) 27 | .concat(); 28 | // .ok_or_else(|| anyhow!("No field 'primitive' found in completion.toml."))?; 29 | items.into_iter().map(CompletionItem::from).collect() 30 | } 31 | 32 | pub fn get_resouce_items() -> HashMap> { 33 | toml::from_str(COMPLETION_RESOUCES).expect("[FATAL] Failed to read toml file.") 34 | } 35 | 36 | /// TOML ファイルに記述する completion items. 37 | #[derive(Debug, Deserialize)] 38 | pub struct CompletionResourceItem { 39 | /// The label of this completion item. By default also the text that is inserted when selecting 40 | /// this completion. 41 | pub label: String, 42 | /// A human-readable string with additional information about this item, like type or symbol 43 | /// information. 44 | pub detail: Option, 45 | /// A human-readable string that represents a doc-comment. 46 | pub documentation: Option, 47 | /// A string that should be inserted a document when selecting this completion. When falsy the 48 | /// label is used. 49 | pub insert_text: Option, 50 | /// The format of the insert text. The format applies to both the insertText property and the 51 | /// newText property of a provided textEdit. 52 | pub insert_text_format: Option, 53 | } 54 | 55 | impl From for CompletionItem { 56 | fn from(resource_item: CompletionResourceItem) -> Self { 57 | CompletionItem { 58 | label: resource_item.label, 59 | detail: resource_item.detail, 60 | insert_text: resource_item.insert_text, 61 | insert_text_format: if resource_item.insert_text_format == Some("snippet".to_owned()) { 62 | Some(InsertTextFormat::SNIPPET) 63 | } else { 64 | None 65 | }, 66 | documentation: resource_item.documentation.map(|s| { 67 | Documentation::MarkupContent(MarkupContent { 68 | kind: MarkupKind::Markdown, 69 | value: s, 70 | }) 71 | }), 72 | ..Default::default() 73 | } 74 | } 75 | } 76 | 77 | impl DocumentCache { 78 | pub fn get_completion_list( 79 | &self, 80 | curpos: &UrlPos, 81 | trigger: Option<&str>, 82 | ) -> Option { 83 | let line_str = self.get_line(curpos); 84 | 85 | match self.get_mode(curpos) { 86 | Mode::Program => Some(CompletionResponse::Array( 87 | self.get_completion_list_program(curpos, trigger)?, 88 | )), 89 | Mode::ProgramType => None, 90 | Mode::Vertical => Some(CompletionResponse::Array( 91 | self.get_completion_list_vertical(curpos, line_str)?, 92 | )), 93 | Mode::Horizontal => Some(CompletionResponse::Array( 94 | self.get_completion_list_horizontal(curpos, line_str)?, 95 | )), 96 | Mode::Math => Some(CompletionResponse::Array( 97 | self.get_completion_list_math(curpos, line_str)?, 98 | )), 99 | Mode::Header => Some(CompletionResponse::Array( 100 | self.get_completion_list_header(curpos)?, 101 | )), 102 | Mode::Literal => None, 103 | Mode::Comment => None, 104 | } 105 | } 106 | 107 | fn get_mode(&self, curpos: &UrlPos) -> Mode { 108 | let UrlPos { url, pos } = curpos; 109 | if let Some(DocumentData::Parsed { program_text, .. }) = self.get(url) { 110 | let pos_usize = program_text.from_position(pos); 111 | pos_usize 112 | .map(|pos| program_text.cst.mode(pos)) 113 | .unwrap_or(Mode::Comment) 114 | } else { 115 | Mode::Comment 116 | } 117 | } 118 | 119 | fn get_completion_list_program( 120 | &self, 121 | curpos: &UrlPos, 122 | trigger: Option<&str>, 123 | ) -> Option> { 124 | if trigger == Some(".") { 125 | return self.get_completion_list_with_module(curpos); 126 | } 127 | 128 | let UrlPos { url, pos } = curpos; 129 | let doc_data = self.get(url)?; 130 | let (program_text, environment) = self.get_doc_info(url)?; 131 | let pos_usize = program_text.from_position(pos)?; 132 | // { 133 | // let csts = program_text.cst.dig(pos_usize - 1); 134 | // if let Some(cst) = csts.get(0) { 135 | // info!("{:?}", cst.rule); 136 | // } 137 | // } 138 | 139 | let local_variables = environment 140 | .variables() 141 | .iter() 142 | .filter(|var| var.scope.includes(pos_usize)) 143 | .map(|var| { 144 | variable_completion_item( 145 | var.name.clone(), 146 | "variable defined in this file".to_owned(), 147 | if let ComponentBody::Variable { 148 | type_declaration: Some(span), 149 | } = var.body 150 | { 151 | self.get_text_from_span(&var.url, span) 152 | .map(|s| s.to_owned()) 153 | } else { 154 | None 155 | }, 156 | ) 157 | }) 158 | .collect_vec(); 159 | 160 | let local_modules = environment 161 | .modules() 162 | .iter() 163 | .filter(|module| module.scope.includes(pos_usize)) 164 | .map(|module| { 165 | module_completion_item( 166 | module.name.clone(), 167 | "module defined in this file".to_owned(), 168 | ) 169 | }) 170 | .collect_vec(); 171 | 172 | // TODO: 直接 require/import していない変数も取れるようにする 173 | // let open_in = program_text.cst.dig(curpos).iter().filter(|cst| cst.rule == Rule::bind_stmt && cst.inner[0].rule == Rule::open_stmt) 174 | let deps_variables = self 175 | .get_dependencies_recursive(environment.dependencies()) 176 | .iter() 177 | .map(|dep| { 178 | if let Some(DocumentData::Parsed { 179 | environment: env_dep, 180 | .. 181 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 182 | { 183 | let modules = [ 184 | doc_data.get_open_modules(pos_usize), 185 | doc_data.get_localized_modules(pos_usize), 186 | ] 187 | .concat(); 188 | env_dep 189 | .variables_external(&modules) 190 | .iter() 191 | .map(|var| { 192 | variable_completion_item( 193 | var.name.clone(), 194 | format!("variable defined in package `{}`", dep.name), 195 | if let ComponentBody::Variable { 196 | type_declaration: Some(span), 197 | } = var.body 198 | { 199 | self.get_text_from_span(&var.url, span) 200 | .map(|s| s.to_owned()) 201 | } else { 202 | None 203 | }, 204 | ) 205 | }) 206 | .collect_vec() 207 | } else { 208 | vec![] 209 | } 210 | }) 211 | .concat(); 212 | 213 | let deps_modules = self 214 | .get_dependencies_recursive(environment.dependencies()) 215 | .iter() 216 | .map(|dep| { 217 | if let Some(DocumentData::Parsed { 218 | environment: env_dep, 219 | .. 220 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 221 | { 222 | env_dep 223 | .modules() 224 | .iter() 225 | .map(|module| { 226 | module_completion_item( 227 | module.name.clone(), 228 | format!("module defined in package {}", dep.name), 229 | ) 230 | }) 231 | .collect_vec() 232 | } else { 233 | vec![] 234 | } 235 | }) 236 | .concat(); 237 | 238 | let primitives = get_primitive_list(); 239 | 240 | Some( 241 | [ 242 | local_variables, 243 | deps_variables, 244 | primitives, 245 | local_modules, 246 | deps_modules, 247 | ] 248 | .concat(), 249 | ) 250 | } 251 | 252 | fn get_completion_list_with_module(&self, curpos: &UrlPos) -> Option> { 253 | let UrlPos { url, pos } = curpos; 254 | let (program_text, environment) = self.get_doc_info(url)?; 255 | let pos_usize = program_text.from_position(pos)?; 256 | let LineCol { line, .. } = program_text.get_line_col(pos_usize)?; 257 | let start = *program_text.lines.get(line)?; 258 | let line_until_cursor = &program_text.text[start..pos_usize]; 259 | 260 | let module_name = { 261 | let mod_name = Regex::new(r#"([A-Z][a-zA-Z0-9-]*)\.$"#).unwrap(); 262 | let caps = mod_name.captures(line_until_cursor)?; 263 | caps.get(1).unwrap().as_str() 264 | }; 265 | 266 | let module = environment 267 | .modules() 268 | .into_iter() 269 | .filter(|module| module.scope.includes(pos_usize)) 270 | .chain( 271 | self.get_dependencies_recursive(environment.dependencies()) 272 | .iter() 273 | .map(|dep| { 274 | if let Some(DocumentData::Parsed { 275 | environment: env_dep, 276 | .. 277 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 278 | { 279 | env_dep.modules() 280 | } else { 281 | vec![] 282 | } 283 | .into_iter() 284 | }) 285 | .flatten(), 286 | ) 287 | .find(|module| module.name == module_name)?; 288 | 289 | if let ComponentBody::Module { components } = &module.body { 290 | let items = components 291 | .iter() 292 | .filter(|c| matches!(c.body, ComponentBody::Variable { .. })) 293 | .filter(|c| { 294 | c.visibility == Visibility::Public || c.visibility == Visibility::Direct 295 | }) 296 | .map(|c| CompletionItem { 297 | label: c.name.clone(), 298 | kind: Some(CompletionItemKind::VARIABLE), 299 | detail: if let ComponentBody::Variable { 300 | type_declaration: Some(span), 301 | } = &c.body 302 | { 303 | self.get_text_from_span(&c.url, *span).map(|s| s.to_owned()) 304 | } else { 305 | None 306 | }, 307 | documentation: None, 308 | deprecated: None, 309 | preselect: None, 310 | sort_text: None, 311 | filter_text: None, 312 | insert_text: None, 313 | insert_text_format: None, 314 | insert_text_mode: None, 315 | text_edit: None, 316 | additional_text_edits: None, 317 | command: None, 318 | commit_characters: None, 319 | data: None, 320 | tags: None, 321 | }) 322 | .collect_vec(); 323 | return Some(items); 324 | } 325 | None 326 | } 327 | 328 | fn get_completion_list_horizontal( 329 | &self, 330 | curpos: &UrlPos, 331 | text: Option<&str>, 332 | ) -> Option> { 333 | let UrlPos { url, pos } = curpos; 334 | let doc_data = self.get(url)?; 335 | let (program_text, environment) = self.get_doc_info(url)?; 336 | let pos_usize = program_text.from_position(pos)?; 337 | 338 | // そのコマンドの開始位置(最後に出現した '\\')を求める 339 | let command_range = text.and_then(|text| Self::get_cmd_range(pos, text, 0x5c)); 340 | 341 | let local_commands = environment 342 | .inline_cmds() 343 | .iter() 344 | .filter(|var| var.scope.includes(pos_usize)) 345 | .map(|cmd| { 346 | self.command_completion_item( 347 | cmd.name.clone(), 348 | "inline-cmd defined in this file".to_owned(), 349 | &cmd.body, 350 | &cmd.url, 351 | command_range, 352 | ) 353 | }) 354 | .collect_vec(); 355 | 356 | // TODO: 直接 require/import していない変数も取れるようにする 357 | let deps_commands = self 358 | .get_dependencies_recursive(environment.dependencies()) 359 | .iter() 360 | .map(|dep| { 361 | if let Some(DocumentData::Parsed { 362 | environment: env_dep, 363 | .. 364 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 365 | { 366 | env_dep 367 | .inline_cmds_external(&doc_data.get_open_modules(pos_usize)) 368 | .iter() 369 | .filter(|&cmd| { 370 | matches!(cmd.visibility, Visibility::Public | Visibility::Direct) 371 | }) 372 | .map(|cmd| { 373 | self.command_completion_item( 374 | cmd.name.clone(), 375 | format!("inline-cmd defined in package `{}`", dep.name), 376 | &cmd.body, 377 | &cmd.url, 378 | command_range, 379 | ) 380 | }) 381 | .collect_vec() 382 | } else { 383 | vec![] 384 | } 385 | }) 386 | .concat(); 387 | 388 | Some([local_commands, deps_commands].concat()) 389 | } 390 | 391 | fn get_completion_list_vertical( 392 | &self, 393 | curpos: &UrlPos, 394 | text: Option<&str>, 395 | ) -> Option> { 396 | let UrlPos { url, pos } = curpos; 397 | let doc_data = self.get(url)?; 398 | let (program_text, environment) = self.get_doc_info(url)?; 399 | let pos_usize = program_text.from_position(pos)?; 400 | 401 | // そのコマンドの開始位置(最後に出現した '+')を求める 402 | let command_range = text.and_then(|text| Self::get_cmd_range(pos, text, 0x2b)); 403 | 404 | let local_commands = environment 405 | .block_cmds() 406 | .iter() 407 | .filter(|var| var.scope.includes(pos_usize)) 408 | .map(|cmd| { 409 | self.command_completion_item( 410 | cmd.name.clone(), 411 | "block-cmd defined in this file".to_owned(), 412 | &cmd.body, 413 | &cmd.url, 414 | command_range, 415 | ) 416 | }) 417 | .collect_vec(); 418 | 419 | // TODO: 直接 require/import していない変数も取れるようにする 420 | let deps_commands = self 421 | .get_dependencies_recursive(environment.dependencies()) 422 | .iter() 423 | .map(|dep| { 424 | if let Some(DocumentData::Parsed { 425 | environment: env_dep, 426 | .. 427 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 428 | { 429 | env_dep 430 | .block_cmds_external(&doc_data.get_open_modules(pos_usize)) 431 | .iter() 432 | .filter(|&cmd| { 433 | matches!(cmd.visibility, Visibility::Public | Visibility::Direct) 434 | }) 435 | .map(|cmd| { 436 | self.command_completion_item( 437 | cmd.name.clone(), 438 | format!("block-cmd defined in package `{}`", dep.name), 439 | &cmd.body, 440 | &cmd.url, 441 | command_range, 442 | ) 443 | }) 444 | .collect_vec() 445 | } else { 446 | vec![] 447 | } 448 | }) 449 | .concat(); 450 | 451 | Some([local_commands, deps_commands].concat()) 452 | } 453 | 454 | fn get_completion_list_math( 455 | &self, 456 | curpos: &UrlPos, 457 | text: Option<&str>, 458 | ) -> Option> { 459 | let UrlPos { url, pos } = curpos; 460 | let doc_data = self.get(url)?; 461 | let (program_text, environment) = self.get_doc_info(url)?; 462 | let pos_usize = program_text.from_position(pos)?; 463 | 464 | // そのコマンドの開始位置(最後に出現した '\\')を求める 465 | let command_range = text.and_then(|text| Self::get_cmd_range(pos, text, 0x5c)); 466 | 467 | let local_commands = environment 468 | .math_cmds() 469 | .iter() 470 | .filter(|var| var.scope.includes(pos_usize)) 471 | .map(|cmd| { 472 | self.command_completion_item( 473 | cmd.name.clone(), 474 | "math-cmd defined in this file".to_owned(), 475 | &cmd.body, 476 | &cmd.url, 477 | command_range, 478 | ) 479 | }) 480 | .collect_vec(); 481 | 482 | // TODO: 直接 require/import していない変数も取れるようにする 483 | let deps_commands = self 484 | .get_dependencies_recursive(environment.dependencies()) 485 | .iter() 486 | .map(|dep| { 487 | if let Some(DocumentData::Parsed { 488 | environment: env_dep, 489 | .. 490 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 491 | { 492 | env_dep 493 | .math_cmds_external(&doc_data.get_open_modules(pos_usize)) 494 | .iter() 495 | .filter(|&cmd| { 496 | matches!(cmd.visibility, Visibility::Public | Visibility::Direct) 497 | }) 498 | .map(|cmd| { 499 | self.command_completion_item( 500 | cmd.name.clone(), 501 | format!("math-cmd defined in package `{}`", dep.name), 502 | &cmd.body, 503 | &cmd.url, 504 | command_range, 505 | ) 506 | }) 507 | .collect_vec() 508 | } else { 509 | vec![] 510 | } 511 | }) 512 | .concat(); 513 | 514 | Some([local_commands, deps_commands].concat()) 515 | } 516 | 517 | fn get_completion_list_header(&self, curpos: &UrlPos) -> Option> { 518 | let UrlPos { url, pos } = curpos; 519 | let text = self.get_line(curpos)?; 520 | let (program_text, _) = self.get_doc_info(url)?; 521 | let pos_usize = program_text.from_position(pos)?; 522 | let range = { 523 | let LineCol { line, .. } = program_text.get_line_col(pos_usize)?; 524 | let start = *program_text.lines.get(line)?; 525 | let start = program_text.get_position(start)?; 526 | let end = *pos; 527 | Range { start, end } 528 | }; 529 | 530 | if text.trim() == "@" { 531 | return Some(vec![ 532 | CompletionItem { 533 | label: "@require:".to_owned(), 534 | kind: None, 535 | detail: Some("Specify a installed satysfi package".to_owned()), 536 | documentation: None, 537 | deprecated: None, 538 | preselect: None, 539 | sort_text: None, 540 | filter_text: None, 541 | insert_text: None, 542 | insert_text_format: None, 543 | insert_text_mode: None, 544 | text_edit: Some(CompletionTextEdit::Edit(TextEdit { 545 | range, 546 | new_text: "@require:".to_owned(), 547 | })), 548 | additional_text_edits: None, 549 | command: None, 550 | commit_characters: None, 551 | data: None, 552 | tags: None, 553 | }, 554 | CompletionItem { 555 | label: "@import:".to_owned(), 556 | kind: None, 557 | detail: Some("Specify a relative path of the current directory".to_owned()), 558 | documentation: None, 559 | deprecated: None, 560 | preselect: None, 561 | sort_text: None, 562 | filter_text: None, 563 | insert_text: None, 564 | insert_text_format: None, 565 | insert_text_mode: None, 566 | text_edit: Some(CompletionTextEdit::Edit(TextEdit { 567 | range, 568 | new_text: "@import:".to_owned(), 569 | })), 570 | additional_text_edits: None, 571 | command: None, 572 | commit_characters: None, 573 | data: None, 574 | tags: None, 575 | }, 576 | ]); 577 | } 578 | if text.contains("@require:") { 579 | let file_path = url.to_file_path().ok(); 580 | let parent_path = file_path.as_ref().map(|p| p.parent().unwrap().to_owned()); 581 | let home_path = std::env::var("HOME").map(PathBuf::from).ok(); 582 | let dirs = require_candidate_dirs(parent_path.as_deref(), home_path.as_deref()); 583 | let mut pkg_names = vec![]; 584 | for dir in dirs { 585 | for entry in glob(&format!("{}/**/*.satyg", dir.to_string_lossy())) 586 | .ok()? 587 | .flatten() 588 | { 589 | if let Ok(relative) = entry.strip_prefix(&dir) { 590 | let pkg_name = relative 591 | .to_string_lossy() 592 | .into_owned() 593 | .strip_suffix(".satyg") 594 | .unwrap() 595 | .to_owned(); 596 | pkg_names.push(pkg_name); 597 | } else { 598 | continue; 599 | } 600 | } 601 | for entry in glob(&format!("{}/**/*.satyh", dir.to_string_lossy())) 602 | .ok()? 603 | .flatten() 604 | { 605 | if let Ok(relative) = entry.strip_prefix(&dir) { 606 | let pkg_name = relative 607 | .to_string_lossy() 608 | .into_owned() 609 | .strip_suffix(".satyh") 610 | .unwrap() 611 | .to_owned(); 612 | pkg_names.push(pkg_name); 613 | } else { 614 | continue; 615 | } 616 | } 617 | } 618 | return Some( 619 | pkg_names 620 | .into_iter() 621 | .map(|s| header_completion_item("require", &s, range)) 622 | .collect(), 623 | ); 624 | } 625 | if text.contains("@import:") { 626 | let file_path = url.to_file_path().ok(); 627 | let parent_path = file_path.as_ref().map(|p| p.parent().unwrap().to_owned())?; 628 | let mut pkg_names = vec![]; 629 | for entry in glob(&format!("{}/**/*.satyg", parent_path.to_string_lossy())) 630 | .ok()? 631 | .flatten() 632 | { 633 | if let Ok(relative) = entry.strip_prefix(&parent_path) { 634 | let pkg_name = relative 635 | .to_string_lossy() 636 | .into_owned() 637 | .strip_suffix(".satyg") 638 | .unwrap() 639 | .to_owned(); 640 | pkg_names.push(pkg_name); 641 | } else { 642 | continue; 643 | } 644 | } 645 | for entry in glob(&format!("{}/**/*.satyh", parent_path.to_string_lossy())) 646 | .ok()? 647 | .flatten() 648 | { 649 | if let Ok(relative) = entry.strip_prefix(&parent_path) { 650 | let pkg_name = relative 651 | .to_string_lossy() 652 | .into_owned() 653 | .strip_suffix(".satyh") 654 | .unwrap() 655 | .to_owned(); 656 | pkg_names.push(pkg_name); 657 | } else { 658 | continue; 659 | } 660 | } 661 | 662 | return Some( 663 | pkg_names 664 | .into_iter() 665 | .map(|s| header_completion_item("import", &s, range)) 666 | .collect(), 667 | ); 668 | } 669 | None 670 | } 671 | 672 | /// 現在補完しようとしている command の範囲を示す。 673 | fn get_cmd_range(pos: &Position, text: &str, chr: u16) -> Option { 674 | let utf16chars = text.encode_utf16().enumerate().collect_vec(); 675 | utf16chars 676 | .into_iter() 677 | .rev() 678 | .find_map(|(idx, c)| if c == chr { Some(idx) } else { None }) 679 | .map(|pos_start| Range { 680 | start: Position { 681 | line: pos.line, 682 | character: pos_start as u32, 683 | }, 684 | end: *pos, 685 | }) 686 | } 687 | 688 | fn command_completion_item( 689 | &self, 690 | name: String, 691 | desc: String, 692 | body: &ComponentBody, 693 | url: &Url, 694 | cmd_range: Option, 695 | ) -> CompletionItem { 696 | let (detail, insert_text, insert_text_format) = match body { 697 | ComponentBody::InlineCmd { 698 | type_declaration: Some(dec), 699 | type_args, 700 | } => ( 701 | self.get_text_from_span(url, *dec).map(|s| s.to_owned()), 702 | Some(form_command_text_snippet(&name, type_args)), 703 | Some(InsertTextFormat::SNIPPET), 704 | ), 705 | ComponentBody::BlockCmd { 706 | type_declaration: Some(dec), 707 | type_args, 708 | } => ( 709 | self.get_text_from_span(url, *dec).map(|s| s.to_owned()), 710 | Some(form_command_text_snippet(&name, type_args)), 711 | Some(InsertTextFormat::SNIPPET), 712 | ), 713 | ComponentBody::MathCmd { 714 | type_declaration: Some(dec), 715 | .. 716 | } => ( 717 | self.get_text_from_span(url, *dec).map(|s| s.to_owned()), 718 | None, 719 | None, 720 | ), 721 | _ => (None, None, None), 722 | }; 723 | 724 | let text_edit = cmd_range.map(|range| { 725 | CompletionTextEdit::Edit(TextEdit { 726 | range, 727 | new_text: insert_text.clone().unwrap_or_else(|| name.clone()), 728 | }) 729 | }); 730 | 731 | CompletionItem { 732 | label: name, 733 | kind: Some(CompletionItemKind::VARIABLE), 734 | detail, 735 | documentation: Some(Documentation::MarkupContent(MarkupContent { 736 | kind: MarkupKind::Markdown, 737 | value: desc, 738 | })), 739 | deprecated: None, 740 | preselect: None, 741 | sort_text: None, 742 | filter_text: None, 743 | insert_text, 744 | insert_text_format, 745 | insert_text_mode: None, 746 | text_edit, 747 | additional_text_edits: None, 748 | command: None, 749 | data: None, 750 | tags: None, 751 | commit_characters: None, 752 | } 753 | } 754 | } 755 | 756 | fn module_completion_item(name: String, desc: String) -> CompletionItem { 757 | CompletionItem { 758 | label: name, 759 | kind: Some(CompletionItemKind::MODULE), 760 | detail: None, 761 | documentation: Some(Documentation::MarkupContent(MarkupContent { 762 | kind: MarkupKind::Markdown, 763 | value: desc, 764 | })), 765 | deprecated: None, 766 | preselect: None, 767 | sort_text: None, 768 | filter_text: None, 769 | insert_text: None, 770 | insert_text_format: None, 771 | insert_text_mode: None, 772 | text_edit: None, 773 | additional_text_edits: None, 774 | command: None, 775 | data: None, 776 | tags: None, 777 | commit_characters: None, 778 | } 779 | } 780 | 781 | fn header_completion_item(import_type: &str, path: &str, range: Range) -> CompletionItem { 782 | let text_edit = Some(CompletionTextEdit::Edit(TextEdit { 783 | range, 784 | new_text: format!("@{import_type}: {path}"), 785 | })); 786 | CompletionItem { 787 | label: path.to_owned(), 788 | kind: Some(CompletionItemKind::MODULE), 789 | detail: None, 790 | documentation: None, 791 | deprecated: None, 792 | preselect: None, 793 | sort_text: None, 794 | filter_text: None, 795 | insert_text: None, 796 | insert_text_format: None, 797 | insert_text_mode: None, 798 | text_edit, 799 | additional_text_edits: None, 800 | command: None, 801 | commit_characters: None, 802 | data: None, 803 | tags: None, 804 | } 805 | } 806 | 807 | fn variable_completion_item( 808 | name: String, 809 | desc: String, 810 | type_declaration: Option, 811 | ) -> CompletionItem { 812 | CompletionItem { 813 | label: name, 814 | kind: Some(CompletionItemKind::FUNCTION), 815 | detail: type_declaration, 816 | documentation: Some(Documentation::MarkupContent(MarkupContent { 817 | kind: MarkupKind::Markdown, 818 | value: desc, 819 | })), 820 | deprecated: None, 821 | preselect: None, 822 | sort_text: None, 823 | filter_text: None, 824 | insert_text: None, 825 | insert_text_format: None, 826 | insert_text_mode: None, 827 | text_edit: None, 828 | additional_text_edits: None, 829 | command: None, 830 | data: None, 831 | tags: None, 832 | commit_characters: None, 833 | } 834 | } 835 | 836 | /// コマンド名と型情報からコマンドのスニペットを自動生成する。 837 | fn form_command_text_snippet(name: &str, type_args: &[String]) -> String { 838 | let args_str = type_args 839 | .iter() 840 | .map(|arg| ArgType::from_str(arg.as_str())) 841 | .filter(|arg| !arg.optional) // オプショナル引数はスニペットに含めない 842 | .collect_vec(); // rev() を行うため一旦 Vec に格納 843 | 844 | let mut snips = vec![]; 845 | 846 | let mut require_semicolon = true; 847 | let mut compactible = true; 848 | for (idx, arg) in args_str.iter().enumerate().rev() { 849 | if !arg.is_compactible() { 850 | compactible = false; 851 | } 852 | snips.push(arg.as_snippet(idx + 1, compactible)); 853 | if compactible { 854 | require_semicolon = false; 855 | } 856 | } 857 | 858 | snips.reverse(); 859 | 860 | let name = if let Some("\\") = name.get(0..1) { 861 | // `\` はスニペットの場合エスケープする必要がある 862 | format!("\\{}", name) 863 | } else { 864 | name.to_owned() 865 | }; 866 | 867 | format!( 868 | "{name}{args}{semicolon}$0", 869 | name = name, 870 | args = snips.into_iter().join(""), 871 | semicolon = if require_semicolon { ";" } else { "" } 872 | ) 873 | } 874 | 875 | struct ArgType<'a> { 876 | name: &'a str, 877 | optional: bool, 878 | } 879 | 880 | impl<'a> ArgType<'a> { 881 | fn as_snippet(&self, idx: usize, short: bool) -> String { 882 | if self.optional { 883 | // 今のところはこのコードは使わない(はず)だけど一応 884 | let name = self.name; 885 | if name.len() > 5 && &name[name.len() - 4..] == "list" { 886 | return format!("${{{}:?:[]}}", idx); 887 | } 888 | return format!("${{{}:?:()}}", idx); 889 | } 890 | match self.name { 891 | "inline-text" => { 892 | if short { 893 | format!("{{${}}}", idx) 894 | } else { 895 | format!("({{${}}})", idx) 896 | } 897 | } 898 | "inline-text list" => { 899 | if short { 900 | format!("{{|${}|}}", idx) 901 | } else { 902 | format!("({{|${}|}})", idx) 903 | } 904 | } 905 | "itemize" => { 906 | if short { 907 | format!("{{\n * ${}\n}}", idx) 908 | } else { 909 | format!("({{* ${}}})", idx) 910 | } 911 | } 912 | "block-text" => { 913 | if short { 914 | format!("<\n ${}\n>", idx) 915 | } else { 916 | format!("('<${}>)", idx) 917 | } 918 | } 919 | s if s.len() > 5 && &s[s.len() - 4..] == "list" => format!("[${}]", idx), 920 | _ => format!("(${})", idx), 921 | } 922 | } 923 | 924 | fn from_str(text: &'a str) -> Self { 925 | let text = text.trim(); 926 | if let Some('?') = text.chars().last() { 927 | ArgType { 928 | name: &text[..text.len() - 1].trim(), 929 | optional: true, 930 | } 931 | } else { 932 | ArgType { 933 | name: text, 934 | optional: false, 935 | } 936 | } 937 | } 938 | 939 | fn is_compactible(&self) -> bool { 940 | !self.optional 941 | && matches!( 942 | self.name, 943 | "inline-text" | "inline-text list" | "itemize" | "block-text" 944 | ) 945 | } 946 | } 947 | -------------------------------------------------------------------------------- /src/language_server/definition.rs: -------------------------------------------------------------------------------- 1 | use lspower::lsp::{GotoDefinitionResponse, Location, Range}; 2 | use satysfi_parser::{Cst, Rule}; 3 | 4 | use crate::{ 5 | documents::{Component, DocumentCache, DocumentData}, 6 | util::{ConvertPosition, UrlPos}, 7 | }; 8 | 9 | impl DocumentCache { 10 | pub fn get_definition_list(&self, curpos: &UrlPos) -> Option { 11 | let (url, pos_definition) = self 12 | .find_component_under_cursor(curpos) 13 | .map(|(_, comp)| (&comp.url, comp.pos_definition))?; 14 | 15 | let range = if let DocumentData::Parsed { program_text, .. } = self.get(url).unwrap() { 16 | Range { 17 | start: program_text.get_position(pos_definition.start).unwrap(), 18 | end: program_text.get_position(pos_definition.end).unwrap(), 19 | } 20 | } else { 21 | unreachable!() 22 | }; 23 | let loc = Location { 24 | uri: url.to_owned(), 25 | range, 26 | }; 27 | let resp = GotoDefinitionResponse::Scalar(loc); 28 | 29 | Some(resp) 30 | } 31 | 32 | pub fn _find_word_under_cursor<'a>(&'a self, curpos: &UrlPos) -> Option<&'a Cst> { 33 | let UrlPos { url, pos } = curpos; 34 | if let Some(DocumentData::Parsed { program_text, .. }) = self.0.get(&url) { 35 | let pos_usize = program_text.from_position(&pos).unwrap(); 36 | // カーソル上にある variable や inline-cmd の CST を抽出する 37 | program_text.cst.dig(pos_usize).into_iter().find(|&cst| { 38 | [ 39 | Rule::var, 40 | Rule::type_name, 41 | Rule::variant_name, 42 | Rule::module_name, 43 | Rule::inline_cmd_name, 44 | Rule::block_cmd_name, 45 | Rule::math_cmd_name, 46 | ] 47 | .contains(&cst.rule) 48 | }) 49 | } else { 50 | None 51 | } 52 | } 53 | 54 | /// カーソル下にあるコンポーネント(変数、コマンド、型など)と同じものを検索する。 55 | pub fn find_component_under_cursor<'a>( 56 | &'a self, 57 | curpos: &UrlPos, 58 | ) -> Option<(&'a Cst, &'a Component)> { 59 | let UrlPos { url, pos } = curpos; 60 | let doc_data = self.get(url)?; 61 | let (program_text, environment) = self.get_doc_info(url)?; 62 | let pos_usize = program_text.from_position(pos)?; 63 | 64 | // カーソル上にある variable や inline-cmd の CST を抽出する 65 | let cst = program_text.cst.dig(pos_usize).into_iter().find(|&cst| { 66 | [ 67 | Rule::var, 68 | Rule::type_name, 69 | Rule::variant_name, 70 | Rule::module_name, 71 | Rule::inline_cmd_name, 72 | Rule::block_cmd_name, 73 | Rule::math_cmd_name, 74 | ] 75 | .contains(&cst.rule) 76 | })?; 77 | // カーソル上にある variable や inline-cmd の CST 78 | // 検索したい変数・コマンド名 79 | let name = program_text.get_text(cst); 80 | 81 | let component = match cst.rule { 82 | Rule::var => { 83 | // カーソルがスコープ内にあって、かつ名前の一致するもの 84 | let local = environment 85 | .variables_external(&doc_data.get_open_modules(pos_usize)) 86 | .into_iter() 87 | .find(|&var| var.scope.includes(pos_usize) && var.name == name); 88 | 89 | // dependency 内にある public な変数で、名前が一致するもの 90 | let deps = environment.dependencies().iter().find_map(|dep| { 91 | if let Some(DocumentData::Parsed { 92 | environment: env_dep, 93 | .. 94 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 95 | { 96 | env_dep 97 | .variables_external(&doc_data.get_open_modules(pos_usize)) 98 | .into_iter() 99 | .find(|&var| var.name == name) 100 | } else { 101 | None 102 | } 103 | }); 104 | local.or(deps) 105 | } 106 | Rule::type_name => None, 107 | Rule::variant_name => None, 108 | Rule::module_name => None, 109 | Rule::inline_cmd_name => { 110 | // カーソルがスコープ内にあって、かつ名前の一致するもの 111 | let local = environment 112 | .inline_cmds_external(&doc_data.get_open_modules(pos_usize)) 113 | .into_iter() 114 | .find(|&cmd| cmd.scope.includes(pos_usize) && cmd.name == name); 115 | 116 | // dependency 内にある public な変数で、名前が一致するもの 117 | let deps = environment.dependencies().iter().find_map(|dep| { 118 | if let Some(DocumentData::Parsed { 119 | environment: env_dep, 120 | .. 121 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 122 | { 123 | env_dep 124 | .inline_cmds_external(&doc_data.get_open_modules(pos_usize)) 125 | .into_iter() 126 | .find(|&cmd| cmd.name == name) 127 | } else { 128 | None 129 | } 130 | }); 131 | local.or(deps) 132 | } 133 | Rule::block_cmd_name => { 134 | // カーソルがスコープ内にあって、かつ名前の一致するもの 135 | let local = environment 136 | .block_cmds_external(&doc_data.get_open_modules(pos_usize)) 137 | .into_iter() 138 | .find(|&cmd| cmd.scope.includes(pos_usize) && cmd.name == name); 139 | 140 | // dependency 内にある public な変数で、名前が一致するもの 141 | let deps = environment.dependencies().iter().find_map(|dep| { 142 | if let Some(DocumentData::Parsed { 143 | environment: env_dep, 144 | .. 145 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 146 | { 147 | env_dep 148 | .block_cmds_external(&doc_data.get_open_modules(pos_usize)) 149 | .into_iter() 150 | .find(|&cmd| cmd.name == name) 151 | } else { 152 | None 153 | } 154 | }); 155 | local.or(deps) 156 | } 157 | Rule::math_cmd_name => { 158 | // カーソルがスコープ内にあって、かつ名前の一致するもの 159 | let local = environment 160 | .math_cmds_external(&doc_data.get_open_modules(pos_usize)) 161 | .into_iter() 162 | .find(|&cmd| cmd.scope.includes(pos_usize) && cmd.name == name); 163 | 164 | // dependency 内にある public な変数で、名前が一致するもの 165 | let deps = environment.dependencies().iter().find_map(|dep| { 166 | if let Some(DocumentData::Parsed { 167 | environment: env_dep, 168 | .. 169 | }) = dep.url.as_ref().and_then(|url| self.get(url)) 170 | { 171 | env_dep 172 | .math_cmds_external(&doc_data.get_open_modules(pos_usize)) 173 | .into_iter() 174 | .find(|&cmd| cmd.name == name) 175 | } else { 176 | None 177 | } 178 | }); 179 | local.or(deps) 180 | } 181 | _ => unreachable!(), 182 | }; 183 | component.map(|c| (cst, c)) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/language_server/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use log::info; 3 | use lspower::lsp::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; 4 | use satysfi_parser::Rule; 5 | use std::collections::HashMap; 6 | 7 | use crate::{documents::DocumentData, util::ConvertPosition}; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct DiagnosticCollection { 11 | map: HashMap>, 12 | } 13 | 14 | const DUMMY_RULES: &[Rule] = &[ 15 | Rule::dummy_stmt, 16 | Rule::dummy_header, 17 | Rule::dummy_sig_stmt, 18 | Rule::dummy_block_cmd_incomplete, 19 | Rule::dummy_inline_cmd_incomplete, 20 | ]; 21 | 22 | pub fn get_diagnostics(doc_data: &DocumentData) -> Vec { 23 | match doc_data { 24 | DocumentData::Parsed { 25 | program_text: csttext, 26 | .. 27 | } => { 28 | let dummy_csts = DUMMY_RULES 29 | .iter() 30 | .map(|&dummy_rule| csttext.cst.pickup(dummy_rule)) 31 | .concat(); 32 | dummy_csts 33 | .into_iter() 34 | .map(|cst| { 35 | let range = Range { 36 | start: csttext.get_position(cst.span.start).unwrap(), 37 | end: csttext.get_position(cst.span.end).unwrap(), 38 | }; 39 | Diagnostic { 40 | range, 41 | severity: Some(DiagnosticSeverity::ERROR), 42 | code: None, 43 | code_description: None, 44 | source: Some("Syntax Error".to_owned()), 45 | message: cst.rule.error_description().unwrap(), 46 | related_information: None, 47 | tags: None, 48 | data: None, 49 | } 50 | }) 51 | .collect() 52 | } 53 | DocumentData::NotParsed { 54 | linecol, 55 | expect, 56 | text, 57 | } => { 58 | info!("Not parsed!: {:?}", linecol); 59 | let line_text = text.split('\n').nth(linecol.line).unwrap(); 60 | let character = line_text 61 | .chars() 62 | .take(linecol.column) 63 | .collect::() 64 | .encode_utf16() 65 | .collect_vec() 66 | .len(); 67 | let pos = Position { 68 | line: linecol.line as u32, 69 | character: character as u32, 70 | }; 71 | let range = Range { 72 | start: pos, 73 | end: pos, 74 | }; 75 | 76 | let message = format!( 77 | "Unexpected character. Expected:\n{}", 78 | expect.iter().map(|s| format!("- {}", s)).join("\n") 79 | ); 80 | 81 | let diagnostics = Diagnostic { 82 | range, 83 | severity: Some(DiagnosticSeverity::ERROR), 84 | code: None, 85 | code_description: None, 86 | source: Some("Syntax Error".to_owned()), 87 | message, 88 | related_information: None, 89 | tags: None, 90 | data: None, 91 | }; 92 | vec![diagnostics] 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/language_server/hover.rs: -------------------------------------------------------------------------------- 1 | use lspower::lsp::{Hover, HoverContents, LanguageString, MarkedString, Range}; 2 | 3 | use crate::{ 4 | documents::{ComponentBody, DocumentCache, DocumentData}, 5 | util::{ConvertPosition, UrlPos}, 6 | }; 7 | 8 | impl DocumentCache { 9 | pub fn get_hover(&self, curpos: &UrlPos) -> Option { 10 | let UrlPos { url, .. } = curpos; 11 | let (cst, component) = self.find_component_under_cursor(curpos)?; 12 | 13 | if let DocumentData::Parsed { program_text, .. } = self.get(url).unwrap() { 14 | let range = Range { 15 | start: program_text.get_position(cst.span.start).unwrap(), 16 | end: program_text.get_position(cst.span.end).unwrap(), 17 | }; 18 | 19 | let contents = match &component.body { 20 | ComponentBody::Module { .. } => { 21 | vec![MarkedString::String("module".to_owned())] 22 | } 23 | ComponentBody::Variable { type_declaration } => { 24 | let mut v = vec![MarkedString::String("variable".to_owned())]; 25 | if let Some(span) = type_declaration { 26 | v.push(MarkedString::LanguageString(LanguageString { 27 | language: "satysfi".to_owned(), 28 | value: self.get_text_from_span(&component.url, *span)?.to_owned(), 29 | })); 30 | } 31 | v 32 | } 33 | ComponentBody::Type => { 34 | vec![MarkedString::String("type".to_owned())] 35 | } 36 | ComponentBody::Variant { type_name } => { 37 | vec![MarkedString::String(format!( 38 | "variant of type {}", 39 | type_name 40 | ))] 41 | } 42 | ComponentBody::InlineCmd { 43 | type_declaration, .. 44 | } => { 45 | let mut v = vec![MarkedString::String("inline command".to_owned())]; 46 | if let Some(span) = type_declaration { 47 | v.push(MarkedString::LanguageString(LanguageString { 48 | language: "satysfi".to_owned(), 49 | value: self.get_text_from_span(&component.url, *span)?.to_owned(), 50 | })) 51 | } 52 | v 53 | } 54 | ComponentBody::BlockCmd { 55 | type_declaration, .. 56 | } => { 57 | let mut v = vec![MarkedString::String("block command".to_owned())]; 58 | if let Some(span) = type_declaration { 59 | v.push(MarkedString::LanguageString(LanguageString { 60 | language: "satysfi".to_owned(), 61 | value: self.get_text_from_span(&component.url, *span)?.to_owned(), 62 | })) 63 | } 64 | v 65 | } 66 | ComponentBody::MathCmd { 67 | type_declaration, .. 68 | } => { 69 | let mut v = vec![MarkedString::String("math command".to_owned())]; 70 | if let Some(span) = type_declaration { 71 | v.push(MarkedString::LanguageString(LanguageString { 72 | language: "satysfi".to_owned(), 73 | value: self.get_text_from_span(&component.url, *span)?.to_owned(), 74 | })) 75 | } 76 | v 77 | } 78 | }; 79 | 80 | Some(Hover { 81 | contents: HoverContents::Array(contents), 82 | range: Some(range), 83 | }) 84 | } else { 85 | unreachable!() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 monaqa. All rights reserved. MIT license. 2 | // This code (including submodules declared in this file) partially uses 3 | // the source code from the project `deno` [^1]. 4 | // [^1]: https://github.com/denoland/deno 5 | //! The SATySFi language server. 6 | 7 | mod language_server; 8 | 9 | mod config; 10 | mod documents; 11 | mod util; 12 | 13 | pub use language_server::LanguageServer; 14 | 15 | pub fn version() -> String { 16 | env!("CARGO_PKG_VERSION").to_owned() 17 | } 18 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 monaqa. All rights reserved. MIT license. 2 | // This code partially uses the source code from the project `deno` [^1]. 3 | // [^1]: https://github.com/denoland/deno 4 | 5 | use anyhow::Result; 6 | use lspower::{LspService, Server}; 7 | 8 | use structopt::StructOpt; 9 | use tokio::net::TcpListener; 10 | 11 | #[derive(Debug, StructOpt)] 12 | struct Opts { 13 | #[structopt(long)] 14 | tcp: bool, 15 | #[structopt(short, long, default_value = "9527")] 16 | port: u32, 17 | } 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<()> { 21 | let opts = Opts::from_args(); 22 | 23 | if opts.tcp { 24 | if std::env::var("RUST_LOG").is_err() { 25 | std::env::set_var("RUST_LOG", "info") 26 | } 27 | env_logger::init(); 28 | 29 | let listener = TcpListener::bind(format!("127.0.0.1:{}", opts.port)).await?; 30 | let (stream, _) = listener.accept().await?; 31 | let (read, write) = tokio::io::split(stream); 32 | 33 | let (service, messages) = LspService::new(satysfi_language_server::LanguageServer::new); 34 | Server::new(read, write) 35 | .interleave(messages) 36 | .serve(service) 37 | .await; 38 | } else { 39 | let stdin = tokio::io::stdin(); 40 | let stdout = tokio::io::stdout(); 41 | 42 | let (service, messages) = LspService::new(satysfi_language_server::LanguageServer::new); 43 | Server::new(stdin, stdout) 44 | .interleave(messages) 45 | .serve(service) 46 | .await; 47 | }; 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /src/resource/completion_items.toml: -------------------------------------------------------------------------------- 1 | # fixed completion items 2 | 3 | [[statement]] 4 | label = "let-inline" 5 | detail = "inline-cmd definition" 6 | insert_text = 'let-inline ${1:ctx} \\${2:cmd-name} ${3:args} = $0' 7 | insert_text_format = "snippet" 8 | documentation = ''' 9 | Declare new inline-cmd. 10 | 11 | ``` 12 | let-inline \pangram = {The quick fox jumps over the lazy dog.} 13 | let-inline ctx \textbf = {The quick fox jumps over the lazy dog.} 14 | ``` 15 | ''' 16 | 17 | [[statement]] 18 | label = "let-block" 19 | detail = "block-cmd definition" 20 | insert_text = 'let-block ${1:ctx} +${2:cmd-name} ${3:args} = $0' 21 | insert_text_format = "snippet" 22 | documentation = ''' 23 | Declare new block-cmd. 24 | ''' 25 | 26 | [[statement]] 27 | label = "let-math" 28 | detail = "math-cmd definition" 29 | insert_text = 'let-math \\${1:cmd-name} ${2:args} = $0' 30 | insert_text_format = "snippet" 31 | documentation = ''' 32 | Declare new math-cmd. 33 | ''' 34 | 35 | [[statement]] 36 | label = 'direct inline-cmd' 37 | detail = "inline-cmd declaration (direct)" 38 | insert_text = 'direct \\${1:cmd-name} : [$0] inline-cmd' 39 | insert_text_format = "snippet" 40 | 41 | [[statement]] 42 | label = 'direct block-cmd' 43 | detail = "block-cmd declaration (direct)" 44 | insert_text = 'direct +${1:cmd-name} : [$0] block-cmd' 45 | insert_text_format = "snippet" 46 | 47 | [[statement]] 48 | label = 'direct math-cmd' 49 | detail = "math-cmd declaration (direct)" 50 | insert_text = 'direct \\${1:cmd-name} : [$0] math-cmd' 51 | insert_text_format = "snippet" 52 | 53 | [[statement]] 54 | label = "module" 55 | detail = "module definition" 56 | insert_text = """ 57 | module ${1:ModName} : sig 58 | end = struct 59 | $0 60 | end 61 | """ 62 | insert_text_format = "snippet" 63 | documentation = ''' 64 | Define new module. 65 | ''' 66 | 67 | [[primitive]] 68 | label = "inline-fil" 69 | detail = "inline-boxes" 70 | documentation = ''' 71 | Infinitely extending glue. Often appended to the end of a paragraph. 72 | 73 | ``` 74 | let bb = line-break true true ctx (ib ++ inline-fil) 75 | ``` 76 | ''' 77 | 78 | 79 | [[primitive]] 80 | label = "abort-with-message" 81 | detail = "string -> unit" 82 | documentation = ''' 83 | Abort with message. 84 | ''' 85 | 86 | [[primitive]] 87 | label = "acos" 88 | detail = "float -> float" 89 | documentation = ''' 90 | Inverse cosine function. 91 | ''' 92 | 93 | [[primitive]] 94 | label = "add-footnote" 95 | detail = "block-boxes -> inline-boxes" 96 | 97 | [[primitive]] 98 | label = "arabic" 99 | detail = "int -> string" 100 | documentation = ''' 101 | Convert integer to string with Arabic notation. 102 | ''' 103 | 104 | [[primitive]] 105 | label = "asin" 106 | detail = "float -> float" 107 | documentation = ''' 108 | Inverse sine function. 109 | ''' 110 | 111 | [[primitive]] 112 | label = "atan" 113 | detail = "float -> float" 114 | documentation = ''' 115 | Inverse tangent function. 116 | ''' 117 | 118 | [[primitive]] 119 | label = "atan2" 120 | detail = "float -> float -> float" 121 | documentation = ''' 122 | `atan2 y x` returns the inverse tangent of `(y /. x)`, 123 | where the signs of `x` and `y` is used to determine the quadrant. 124 | The return value is between -π and π. 125 | ''' 126 | 127 | [[primitive]] 128 | label = "bezier-to" 129 | detail = "point -> point -> point -> pre-path -> pre-path" 130 | 131 | [[primitive]] 132 | label = "block-frame-breakable" 133 | detail = "context -> paddings -> (deco * deco * deco * deco) -> (context -> block-boxes) -> block-boxes" 134 | 135 | [[primitive]] 136 | label = "block-skip" 137 | detail = "length -> block-boxes" 138 | 139 | [[primitive]] 140 | label = "break" 141 | 142 | [[primitive]] 143 | label = "close-with-bezier" 144 | detail = "point -> point-> pre-path -> path" 145 | 146 | [[primitive]] 147 | label = "close-with-line" 148 | detail = "pre-path -> path" 149 | 150 | [[primitive]] 151 | label = "convert-string-for-math" 152 | 153 | [[primitive]] 154 | label = "cos" 155 | detail = "float -> float" 156 | documentation = ''' 157 | Cosine function. 158 | ''' 159 | 160 | [[primitive]] 161 | label = "dashed-stroke" 162 | 163 | [[primitive]] 164 | label = "deepen-indent" 165 | 166 | [[primitive]] 167 | label = "discretionary" 168 | 169 | [[primitive]] 170 | label = "display-message" 171 | detail = "string -> unit" 172 | documentation = ''' 173 | Display a message to console. 174 | ''' 175 | 176 | [[primitive]] 177 | label = "draw-text" 178 | detail = "(length * length) -> inline-boxes -> graphics" 179 | insert_text = "draw-text ${1:(x, y)} ${2:ib}" 180 | insert_text_format = "snippet" 181 | documentation = ''' 182 | Draw a text (inline-boxes) at the specified point. 183 | ''' 184 | 185 | [[primitive]] 186 | label = "embed-block-bottom" 187 | 188 | [[primitive]] 189 | label = "embed-block-breakable" 190 | 191 | [[primitive]] 192 | label = "embed-block-top" 193 | detail = "context -> length -> (context -> block-boxes) -> inline-boxes" 194 | insert_text = "embed-block-top ${1:ctx} ${2:wid} ${3:(fun ctx -> read-block ctx bt)}" 195 | insert_text_format = "snippet" 196 | 197 | [[primitive]] 198 | label = "embed-math" 199 | detail = "context -> math -> inline-boxes" 200 | insert_text = "embed-math ${1:ctx} ${2:m}" 201 | insert_text_format = "snippet" 202 | documentation = ''' 203 | Convert math to inline-boxes based on the given context. 204 | ''' 205 | 206 | [[primitive]] 207 | label = "embed-string" 208 | 209 | [[primitive]] 210 | label = "exp" 211 | 212 | [[primitive]] 213 | label = "extract-string" 214 | 215 | [[primitive]] 216 | label = "fill" 217 | detail = "color -> path -> graphics" 218 | insert_text = "fill ${1:Color.black} ${2:path}" 219 | insert_text_format = "snippet" 220 | documentation = ''' 221 | Fill the path with the specified color. 222 | ''' 223 | 224 | [[primitive]] 225 | label = "float" 226 | 227 | [[primitive]] 228 | label = "get-axis-height" 229 | 230 | [[primitive]] 231 | label = "get-cross-reference" 232 | 233 | [[primitive]] 234 | label = "get-dominant-narrow-script" 235 | 236 | [[primitive]] 237 | label = "get-dominant-wide-script" 238 | 239 | [[primitive]] 240 | label = "get-every-word-break" 241 | 242 | [[primitive]] 243 | label = "get-font" 244 | 245 | [[primitive]] 246 | label = "get-font-size" 247 | 248 | [[primitive]] 249 | label = "get-initial-context" 250 | 251 | [[primitive]] 252 | label = "get-initial-text-info" 253 | 254 | [[primitive]] 255 | label = "get-input-position" 256 | 257 | [[primitive]] 258 | label = "get-language" 259 | 260 | [[primitive]] 261 | label = "get-left-math-class" 262 | 263 | [[primitive]] 264 | label = "get-leftmost-script" 265 | 266 | [[primitive]] 267 | label = "get-natural-length" 268 | 269 | [[primitive]] 270 | label = "get-natural-metrics" 271 | 272 | [[primitive]] 273 | label = "get-path-bbox" 274 | 275 | [[primitive]] 276 | label = "get-right-math-class" 277 | 278 | [[primitive]] 279 | label = "get-rightmost-script" 280 | 281 | [[primitive]] 282 | label = "get-space-ratio-between-scripts" 283 | 284 | [[primitive]] 285 | label = "get-text-color" 286 | 287 | [[primitive]] 288 | label = "get-text-width" 289 | 290 | [[primitive]] 291 | label = "hook-page-break" 292 | 293 | [[primitive]] 294 | label = "inline-frame-breakable" 295 | 296 | [[primitive]] 297 | label = "inline-frame-fixed" 298 | 299 | [[primitive]] 300 | label = "inline-frame-inner" 301 | 302 | [[primitive]] 303 | label = "inline-frame-outer" 304 | 305 | [[primitive]] 306 | label = "inline-glue" 307 | 308 | [[primitive]] 309 | label = "inline-graphics" 310 | 311 | [[primitive]] 312 | label = "inline-graphics-outer" 313 | 314 | [[primitive]] 315 | label = "inline-skip" 316 | 317 | [[primitive]] 318 | label = "lift-float" 319 | 320 | [[primitive]] 321 | label = "lift-int" 322 | 323 | [[primitive]] 324 | label = "lift-length" 325 | 326 | [[primitive]] 327 | label = "lift-string" 328 | 329 | [[primitive]] 330 | label = "line-break" 331 | detail = "bool -> bool -> context -> inline-boxes -> block-boxes" 332 | insert_text = "line-break ${1:true} ${2:true} ${3:ctx} ${4:ib}" 333 | insert_text_format = "snippet" 334 | documentation = ''' 335 | Perform row splitting for the given inline-boxes. 336 | 337 | ```satysfi 338 | let ib = read-inline ctx {The quick fox jumps...} 339 | let bb = line-break true true ctx (ib ++ inline-fil) 340 | ``` 341 | ''' 342 | 343 | [[primitive]] 344 | label = "line-stack-bottom" 345 | 346 | [[primitive]] 347 | label = "line-stack-top" 348 | 349 | [[primitive]] 350 | label = "line-to" 351 | 352 | [[primitive]] 353 | label = "linear-transform-graphics" 354 | 355 | [[primitive]] 356 | label = "linear-transform-path" 357 | 358 | [[primitive]] 359 | label = "load-image" 360 | 361 | [[primitive]] 362 | label = "load-pdf-image" 363 | 364 | [[primitive]] 365 | label = "log" 366 | 367 | [[primitive]] 368 | label = "math-big-char" 369 | 370 | [[primitive]] 371 | label = "math-big-char-with-kern" 372 | 373 | [[primitive]] 374 | label = "math-char" 375 | 376 | [[primitive]] 377 | label = "math-char-class" 378 | 379 | [[primitive]] 380 | label = "math-char-with-kern" 381 | 382 | [[primitive]] 383 | label = "math-color" 384 | 385 | [[primitive]] 386 | label = "math-concat" 387 | 388 | [[primitive]] 389 | label = "math-frac" 390 | 391 | [[primitive]] 392 | label = "math-group" 393 | 394 | [[primitive]] 395 | label = "math-lower" 396 | 397 | [[primitive]] 398 | label = "math-paren" 399 | 400 | [[primitive]] 401 | label = "math-paren-with-middle" 402 | 403 | [[primitive]] 404 | label = "math-pull-in-scripts" 405 | 406 | [[primitive]] 407 | label = "math-radical" 408 | 409 | [[primitive]] 410 | label = "math-sub" 411 | 412 | [[primitive]] 413 | label = "math-sup" 414 | 415 | [[primitive]] 416 | label = "math-upper" 417 | 418 | [[primitive]] 419 | label = "math-variant-char" 420 | 421 | [[primitive]] 422 | label = "mod" 423 | 424 | [[primitive]] 425 | label = "not" 426 | 427 | [[primitive]] 428 | label = "page-break" 429 | 430 | [[primitive]] 431 | label = "page-break-two-column" 432 | 433 | [[primitive]] 434 | label = "probe-cross-reference" 435 | 436 | [[primitive]] 437 | label = "raise-inline" 438 | 439 | [[primitive]] 440 | label = "read-block" 441 | 442 | [[primitive]] 443 | label = "read-inline" 444 | detail = "context -> inline-text -> inline-boxes" 445 | insert_text = "read-inline ${1:ctx} ${2:it}" 446 | insert_text_format = "snippet" 447 | documentation = ''' 448 | Convert inline-text into inline-boxes based on the given context. 449 | 450 | ```satysfi 451 | let ib = read-inline ctx {The quick fox jumps...} 452 | ``` 453 | ''' 454 | 455 | [[primitive]] 456 | label = "regexp-of-string" 457 | 458 | [[primitive]] 459 | label = "register-cross-reference" 460 | 461 | [[primitive]] 462 | label = "register-destination" 463 | 464 | [[primitive]] 465 | label = "register-link-to-location" 466 | 467 | [[primitive]] 468 | label = "register-link-to-uri" 469 | 470 | [[primitive]] 471 | label = "register-outline" 472 | 473 | [[primitive]] 474 | label = "round" 475 | 476 | [[primitive]] 477 | label = "script-guard" 478 | 479 | [[primitive]] 480 | label = "script-guard-both" 481 | 482 | [[primitive]] 483 | label = "set-adjacent-stretch-ratio" 484 | 485 | [[primitive]] 486 | label = "set-code-text-command" 487 | 488 | [[primitive]] 489 | label = "set-dominant-narrow-script" 490 | 491 | [[primitive]] 492 | label = "set-dominant-wide-script" 493 | 494 | [[primitive]] 495 | label = "set-every-word-break" 496 | 497 | [[primitive]] 498 | label = "set-font" 499 | 500 | [[primitive]] 501 | label = "set-font-size" 502 | 503 | [[primitive]] 504 | label = "set-hyphen-min" 505 | 506 | [[primitive]] 507 | label = "set-hyphen-penalty" 508 | 509 | [[primitive]] 510 | label = "set-language" 511 | 512 | [[primitive]] 513 | label = "set-leading" 514 | 515 | [[primitive]] 516 | label = "set-manual-rising" 517 | 518 | [[primitive]] 519 | label = "set-math-command" 520 | 521 | [[primitive]] 522 | label = "set-math-font" 523 | 524 | [[primitive]] 525 | label = "set-math-variant-char" 526 | 527 | [[primitive]] 528 | label = "set-min-gap-of-lines" 529 | 530 | [[primitive]] 531 | label = "set-min-paragraph-ascender-and-descender" 532 | 533 | [[primitive]] 534 | label = "set-paragraph-margin" 535 | 536 | [[primitive]] 537 | label = "set-space-ratio" 538 | 539 | [[primitive]] 540 | label = "set-space-ratio-between-scripts" 541 | 542 | [[primitive]] 543 | label = "set-text-color" 544 | 545 | [[primitive]] 546 | label = "set-word-break-penalty" 547 | 548 | [[primitive]] 549 | label = "shift-graphics" 550 | 551 | [[primitive]] 552 | label = "shift-path" 553 | 554 | [[primitive]] 555 | label = "show-float" 556 | 557 | [[primitive]] 558 | label = "sin" 559 | 560 | [[primitive]] 561 | label = "space-between-maths" 562 | 563 | [[primitive]] 564 | label = "split-into-lines" 565 | 566 | [[primitive]] 567 | label = "split-on-regexp" 568 | 569 | [[primitive]] 570 | label = "start-path" 571 | 572 | [[primitive]] 573 | label = "string-byte-length" 574 | 575 | [[primitive]] 576 | label = "string-explode" 577 | 578 | [[primitive]] 579 | label = "string-length" 580 | 581 | [[primitive]] 582 | label = "string-match" 583 | 584 | [[primitive]] 585 | label = "string-same" 586 | 587 | [[primitive]] 588 | label = "string-scan" 589 | 590 | [[primitive]] 591 | label = "string-sub" 592 | 593 | [[primitive]] 594 | label = "string-sub-bytes" 595 | 596 | [[primitive]] 597 | label = "string-unexplode" 598 | 599 | [[primitive]] 600 | label = "stringify-block" 601 | 602 | [[primitive]] 603 | label = "stringify-inline" 604 | 605 | [[primitive]] 606 | label = "stroke" 607 | 608 | [[primitive]] 609 | label = "tabular" 610 | 611 | [[primitive]] 612 | label = "tan" 613 | 614 | [[primitive]] 615 | label = "terminate-path" 616 | 617 | [[primitive]] 618 | label = "text-in-math" 619 | 620 | [[primitive]] 621 | label = "unite-path" 622 | 623 | [[primitive]] 624 | label = "use-image-by-width" 625 | 626 | [[primitive]] 627 | label = "get-graphics-bbox" 628 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use lspower::lsp::{Position, Url}; 3 | use satysfi_parser::{structure::ProgramText, CstText, LineCol, Span}; 4 | 5 | /// Position を convert する関数の提供。 6 | pub trait ConvertPosition { 7 | fn get_position(&self, pos: usize) -> Option; 8 | fn from_position(&self, pos: &Position) -> Option; 9 | } 10 | 11 | impl ConvertPosition for CstText { 12 | fn get_position(&self, pos: usize) -> Option { 13 | let LineCol { line, .. } = self.get_line_col(pos)?; 14 | let span = { 15 | let start = self.from_line_col(line, 0).unwrap(); 16 | Span { start, end: pos } 17 | }; 18 | let text = self.get_text_from_span(span); 19 | let character = text.encode_utf16().collect_vec().len(); 20 | Some(Position { 21 | line: line as u32, 22 | character: character as u32, 23 | }) 24 | } 25 | 26 | fn from_position(&self, pos: &Position) -> Option { 27 | let &Position { line, character } = pos; 28 | // position が属する行のテキストを取り出す。 29 | let text = { 30 | let start = self.from_line_col(line as usize, 0).unwrap(); 31 | let end = self 32 | .from_line_col((line + 1) as usize, 0) 33 | .unwrap_or(self.cst.span.end); 34 | self.get_text_from_span(Span { start, end }) 35 | }; 36 | let vec_utf16 = text.encode_utf16().take(character as usize).collect_vec(); 37 | let text = String::from_utf16_lossy(&vec_utf16); 38 | let column = text.len(); 39 | self.from_line_col(line as usize, column) 40 | } 41 | } 42 | 43 | impl ConvertPosition for ProgramText { 44 | fn get_position(&self, pos: usize) -> Option { 45 | let LineCol { line, .. } = self.get_line_col(pos)?; 46 | let span = { 47 | let start = self.from_line_col(line, 0).unwrap(); 48 | Span { start, end: pos } 49 | }; 50 | let text = self.get_text_from_span(span); 51 | let character = text.encode_utf16().collect_vec().len(); 52 | Some(Position { 53 | line: line as u32, 54 | character: character as u32, 55 | }) 56 | } 57 | 58 | fn from_position(&self, pos: &Position) -> Option { 59 | let &Position { line, character } = pos; 60 | // position が属する行のテキストを取り出す。 61 | let text = { 62 | let start = self.from_line_col(line as usize, 0).unwrap(); 63 | let end = self 64 | .from_line_col((line + 1) as usize, 0) 65 | .unwrap_or(self.cst.span.end); 66 | self.get_text_from_span(Span { start, end }) 67 | }; 68 | let vec_utf16 = text.encode_utf16().take(character as usize).collect_vec(); 69 | let text = String::from_utf16_lossy(&vec_utf16); 70 | let column = text.len(); 71 | self.from_line_col(line as usize, column) 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub struct UrlPos { 77 | pub url: Url, 78 | pub pos: Position, 79 | } 80 | --------------------------------------------------------------------------------