├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── DEVELOPMENT ├── README.md ├── autoload ├── lspc.vim └── lspc │ ├── buffer.vim │ └── command.vim ├── design ├── README.md └── components.puml ├── plugin └── lspc.vim └── src ├── bin └── neovim_lspc.rs ├── lib.rs ├── lspc.rs ├── lspc ├── handler.rs ├── msg.rs ├── tracking_file.rs └── types.rs ├── neovim.rs └── rpc.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Formatting Checks 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v1 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | override: true 18 | 19 | - name: Install rustfmt 20 | run: rustup component add rustfmt 21 | 22 | - name: Check Formatting 23 | uses: actions-rs/cargo@v1 24 | with: 25 | command: fmt 26 | args: --all -- --check 27 | 28 | test: 29 | name: Run Tests 30 | runs-on: ${{ matrix.os }} 31 | strategy: 32 | matrix: 33 | os: [ubuntu-latest, macos-latest, windows-latest] 34 | 35 | steps: 36 | - name: Checkout sources 37 | uses: actions/checkout@v1 38 | 39 | - name: Install stable toolchain 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: stable 43 | override: true 44 | - name: Run Tests 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | 49 | build: 50 | name: Build Binaries 51 | runs-on: ${{ matrix.os }} 52 | strategy: 53 | matrix: 54 | os: [ubuntu-latest, macos-latest, windows-latest] 55 | 56 | steps: 57 | - name: Checkout sources 58 | uses: actions/checkout@v1 59 | 60 | - name: Install stable toolchain 61 | uses: actions-rs/toolchain@v1 62 | with: 63 | toolchain: stable 64 | override: true 65 | - name: Run Build 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: build 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log.txt 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /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 = "arrayref" 7 | version = "0.3.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 10 | 11 | [[package]] 12 | name = "arrayvec" 13 | version = "0.5.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 16 | 17 | [[package]] 18 | name = "autocfg" 19 | version = "0.1.7" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 22 | 23 | [[package]] 24 | name = "backtrace" 25 | version = "0.3.40" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 28 | dependencies = [ 29 | "backtrace-sys", 30 | "cfg-if", 31 | "libc", 32 | "rustc-demangle", 33 | ] 34 | 35 | [[package]] 36 | name = "backtrace-sys" 37 | version = "0.1.32" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 40 | dependencies = [ 41 | "cc", 42 | "libc", 43 | ] 44 | 45 | [[package]] 46 | name = "base64" 47 | version = "0.10.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 50 | dependencies = [ 51 | "byteorder", 52 | ] 53 | 54 | [[package]] 55 | name = "bimap" 56 | version = "0.4.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "783204f24fd7724ea274d327619cfa6a6018047bb0561a68aadff6f56787591b" 59 | dependencies = [ 60 | "cfg-if", 61 | ] 62 | 63 | [[package]] 64 | name = "bitflags" 65 | version = "1.2.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 68 | 69 | [[package]] 70 | name = "blake2b_simd" 71 | version = "0.5.9" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" 74 | dependencies = [ 75 | "arrayref", 76 | "arrayvec", 77 | "constant_time_eq", 78 | ] 79 | 80 | [[package]] 81 | name = "byteorder" 82 | version = "1.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 85 | 86 | [[package]] 87 | name = "cc" 88 | version = "1.0.47" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" 91 | 92 | [[package]] 93 | name = "cfg-if" 94 | version = "0.1.10" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 97 | 98 | [[package]] 99 | name = "cloudabi" 100 | version = "0.0.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 103 | dependencies = [ 104 | "bitflags", 105 | ] 106 | 107 | [[package]] 108 | name = "constant_time_eq" 109 | version = "0.1.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" 112 | 113 | [[package]] 114 | name = "crossbeam" 115 | version = "0.7.3" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" 118 | dependencies = [ 119 | "cfg-if", 120 | "crossbeam-channel", 121 | "crossbeam-deque", 122 | "crossbeam-epoch", 123 | "crossbeam-queue", 124 | "crossbeam-utils 0.7.0", 125 | ] 126 | 127 | [[package]] 128 | name = "crossbeam-channel" 129 | version = "0.4.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" 132 | dependencies = [ 133 | "crossbeam-utils 0.7.0", 134 | "maybe-uninit", 135 | ] 136 | 137 | [[package]] 138 | name = "crossbeam-deque" 139 | version = "0.7.4" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" 142 | dependencies = [ 143 | "crossbeam-epoch", 144 | "crossbeam-utils 0.7.0", 145 | "maybe-uninit", 146 | ] 147 | 148 | [[package]] 149 | name = "crossbeam-epoch" 150 | version = "0.8.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" 153 | dependencies = [ 154 | "autocfg", 155 | "cfg-if", 156 | "crossbeam-utils 0.7.0", 157 | "lazy_static", 158 | "memoffset", 159 | "scopeguard", 160 | ] 161 | 162 | [[package]] 163 | name = "crossbeam-queue" 164 | version = "0.2.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" 167 | dependencies = [ 168 | "crossbeam-utils 0.7.0", 169 | ] 170 | 171 | [[package]] 172 | name = "crossbeam-utils" 173 | version = "0.6.6" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 176 | dependencies = [ 177 | "cfg-if", 178 | "lazy_static", 179 | ] 180 | 181 | [[package]] 182 | name = "crossbeam-utils" 183 | version = "0.7.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" 186 | dependencies = [ 187 | "autocfg", 188 | "cfg-if", 189 | "lazy_static", 190 | ] 191 | 192 | [[package]] 193 | name = "dirs" 194 | version = "2.0.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 197 | dependencies = [ 198 | "cfg-if", 199 | "dirs-sys", 200 | ] 201 | 202 | [[package]] 203 | name = "dirs-sys" 204 | version = "0.3.4" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 207 | dependencies = [ 208 | "cfg-if", 209 | "libc", 210 | "redox_users", 211 | "winapi", 212 | ] 213 | 214 | [[package]] 215 | name = "failure" 216 | version = "0.1.6" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 219 | dependencies = [ 220 | "backtrace", 221 | "failure_derive", 222 | ] 223 | 224 | [[package]] 225 | name = "failure_derive" 226 | version = "0.1.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 229 | dependencies = [ 230 | "proc-macro2", 231 | "quote", 232 | "syn", 233 | "synstructure", 234 | ] 235 | 236 | [[package]] 237 | name = "fuchsia-cprng" 238 | version = "0.1.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 241 | 242 | [[package]] 243 | name = "idna" 244 | version = "0.1.5" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 247 | dependencies = [ 248 | "matches", 249 | "unicode-bidi", 250 | "unicode-normalization", 251 | ] 252 | 253 | [[package]] 254 | name = "idna" 255 | version = "0.2.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 258 | dependencies = [ 259 | "matches", 260 | "unicode-bidi", 261 | "unicode-normalization", 262 | ] 263 | 264 | [[package]] 265 | name = "itoa" 266 | version = "0.4.4" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 269 | 270 | [[package]] 271 | name = "lazy_static" 272 | version = "1.4.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 275 | 276 | [[package]] 277 | name = "libc" 278 | version = "0.2.65" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 281 | 282 | [[package]] 283 | name = "log" 284 | version = "0.4.8" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 287 | dependencies = [ 288 | "cfg-if", 289 | ] 290 | 291 | [[package]] 292 | name = "lsp-types" 293 | version = "0.61.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "fa3268fbe8beb2795c2fb327bf44f4f3d24f5fe9ebc18d7e2980afd444d72bcf" 296 | dependencies = [ 297 | "bitflags", 298 | "serde", 299 | "serde_json", 300 | "serde_repr", 301 | "url 2.1.0", 302 | ] 303 | 304 | [[package]] 305 | name = "lspc" 306 | version = "0.1.0" 307 | dependencies = [ 308 | "bimap", 309 | "crossbeam", 310 | "dirs", 311 | "lazy_static", 312 | "log", 313 | "lsp-types", 314 | "rmpv", 315 | "ropey", 316 | "serde", 317 | "serde_bytes", 318 | "serde_json", 319 | "simple-logging", 320 | "url 2.1.0", 321 | "url_serde", 322 | ] 323 | 324 | [[package]] 325 | name = "matches" 326 | version = "0.1.8" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 329 | 330 | [[package]] 331 | name = "maybe-uninit" 332 | version = "2.0.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 335 | 336 | [[package]] 337 | name = "memoffset" 338 | version = "0.5.3" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" 341 | dependencies = [ 342 | "rustc_version", 343 | ] 344 | 345 | [[package]] 346 | name = "num-traits" 347 | version = "0.2.8" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 350 | dependencies = [ 351 | "autocfg", 352 | ] 353 | 354 | [[package]] 355 | name = "percent-encoding" 356 | version = "1.0.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 359 | 360 | [[package]] 361 | name = "percent-encoding" 362 | version = "2.1.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 365 | 366 | [[package]] 367 | name = "proc-macro2" 368 | version = "1.0.6" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 371 | dependencies = [ 372 | "unicode-xid", 373 | ] 374 | 375 | [[package]] 376 | name = "quote" 377 | version = "1.0.2" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 380 | dependencies = [ 381 | "proc-macro2", 382 | ] 383 | 384 | [[package]] 385 | name = "rand_core" 386 | version = "0.3.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 389 | dependencies = [ 390 | "rand_core 0.4.2", 391 | ] 392 | 393 | [[package]] 394 | name = "rand_core" 395 | version = "0.4.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 398 | 399 | [[package]] 400 | name = "rand_os" 401 | version = "0.1.3" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 404 | dependencies = [ 405 | "cloudabi", 406 | "fuchsia-cprng", 407 | "libc", 408 | "rand_core 0.4.2", 409 | "rdrand", 410 | "winapi", 411 | ] 412 | 413 | [[package]] 414 | name = "rdrand" 415 | version = "0.4.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 418 | dependencies = [ 419 | "rand_core 0.3.1", 420 | ] 421 | 422 | [[package]] 423 | name = "redox_syscall" 424 | version = "0.1.56" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 427 | 428 | [[package]] 429 | name = "redox_users" 430 | version = "0.3.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 433 | dependencies = [ 434 | "failure", 435 | "rand_os", 436 | "redox_syscall", 437 | "rust-argon2", 438 | ] 439 | 440 | [[package]] 441 | name = "rmp" 442 | version = "0.8.8" 443 | source = "git+https://github.com/3Hren/msgpack-rust#ef42c6b22156e7aa677f64d195018ef2d28c9b13" 444 | dependencies = [ 445 | "byteorder", 446 | "num-traits", 447 | ] 448 | 449 | [[package]] 450 | name = "rmpv" 451 | version = "0.4.2" 452 | source = "git+https://github.com/3Hren/msgpack-rust#ef42c6b22156e7aa677f64d195018ef2d28c9b13" 453 | dependencies = [ 454 | "num-traits", 455 | "rmp", 456 | "serde", 457 | "serde_bytes", 458 | ] 459 | 460 | [[package]] 461 | name = "ropey" 462 | version = "1.1.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "ba326a8508a4add47e7b260333aa2d896213a5f3572fde11ed6e9130241b7f71" 465 | dependencies = [ 466 | "smallvec", 467 | ] 468 | 469 | [[package]] 470 | name = "rust-argon2" 471 | version = "0.5.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 474 | dependencies = [ 475 | "base64", 476 | "blake2b_simd", 477 | "crossbeam-utils 0.6.6", 478 | ] 479 | 480 | [[package]] 481 | name = "rustc-demangle" 482 | version = "0.1.16" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 485 | 486 | [[package]] 487 | name = "rustc_version" 488 | version = "0.2.3" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 491 | dependencies = [ 492 | "semver", 493 | ] 494 | 495 | [[package]] 496 | name = "ryu" 497 | version = "1.0.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 500 | 501 | [[package]] 502 | name = "scopeguard" 503 | version = "1.0.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 506 | 507 | [[package]] 508 | name = "semver" 509 | version = "0.9.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 512 | dependencies = [ 513 | "semver-parser", 514 | ] 515 | 516 | [[package]] 517 | name = "semver-parser" 518 | version = "0.7.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 521 | 522 | [[package]] 523 | name = "serde" 524 | version = "1.0.102" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" 527 | dependencies = [ 528 | "serde_derive", 529 | ] 530 | 531 | [[package]] 532 | name = "serde_bytes" 533 | version = "0.11.2" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "45af0182ff64abaeea290235eb67da3825a576c5d53e642c4d5b652e12e6effc" 536 | dependencies = [ 537 | "serde", 538 | ] 539 | 540 | [[package]] 541 | name = "serde_derive" 542 | version = "1.0.102" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" 545 | dependencies = [ 546 | "proc-macro2", 547 | "quote", 548 | "syn", 549 | ] 550 | 551 | [[package]] 552 | name = "serde_json" 553 | version = "1.0.41" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" 556 | dependencies = [ 557 | "itoa", 558 | "ryu", 559 | "serde", 560 | ] 561 | 562 | [[package]] 563 | name = "serde_repr" 564 | version = "0.1.5" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "cd02c7587ec314570041b2754829f84d873ced14a96d1fd1823531e11db40573" 567 | dependencies = [ 568 | "proc-macro2", 569 | "quote", 570 | "syn", 571 | ] 572 | 573 | [[package]] 574 | name = "simple-logging" 575 | version = "2.0.2" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "b00d48e85675326bb182a2286ea7c1a0b264333ae10f27a937a72be08628b542" 578 | dependencies = [ 579 | "lazy_static", 580 | "log", 581 | "thread-id", 582 | ] 583 | 584 | [[package]] 585 | name = "smallvec" 586 | version = "0.6.13" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 589 | dependencies = [ 590 | "maybe-uninit", 591 | ] 592 | 593 | [[package]] 594 | name = "syn" 595 | version = "1.0.8" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" 598 | dependencies = [ 599 | "proc-macro2", 600 | "quote", 601 | "unicode-xid", 602 | ] 603 | 604 | [[package]] 605 | name = "synstructure" 606 | version = "0.12.3" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 609 | dependencies = [ 610 | "proc-macro2", 611 | "quote", 612 | "syn", 613 | "unicode-xid", 614 | ] 615 | 616 | [[package]] 617 | name = "thread-id" 618 | version = "3.3.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" 621 | dependencies = [ 622 | "libc", 623 | "redox_syscall", 624 | "winapi", 625 | ] 626 | 627 | [[package]] 628 | name = "unicode-bidi" 629 | version = "0.3.4" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 632 | dependencies = [ 633 | "matches", 634 | ] 635 | 636 | [[package]] 637 | name = "unicode-normalization" 638 | version = "0.1.9" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf" 641 | dependencies = [ 642 | "smallvec", 643 | ] 644 | 645 | [[package]] 646 | name = "unicode-xid" 647 | version = "0.2.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 650 | 651 | [[package]] 652 | name = "url" 653 | version = "1.7.2" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 656 | dependencies = [ 657 | "idna 0.1.5", 658 | "matches", 659 | "percent-encoding 1.0.1", 660 | ] 661 | 662 | [[package]] 663 | name = "url" 664 | version = "2.1.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" 667 | dependencies = [ 668 | "idna 0.2.0", 669 | "matches", 670 | "percent-encoding 2.1.0", 671 | "serde", 672 | ] 673 | 674 | [[package]] 675 | name = "url_serde" 676 | version = "0.2.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "74e7d099f1ee52f823d4bdd60c93c3602043c728f5db3b97bdb548467f7bddea" 679 | dependencies = [ 680 | "serde", 681 | "url 1.7.2", 682 | ] 683 | 684 | [[package]] 685 | name = "winapi" 686 | version = "0.3.8" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 689 | dependencies = [ 690 | "winapi-i686-pc-windows-gnu", 691 | "winapi-x86_64-pc-windows-gnu", 692 | ] 693 | 694 | [[package]] 695 | name = "winapi-i686-pc-windows-gnu" 696 | version = "0.4.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 699 | 700 | [[package]] 701 | name = "winapi-x86_64-pc-windows-gnu" 702 | version = "0.4.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 705 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lspc" 3 | version = "0.1.0" 4 | authors = ["Unreal Hoang "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = { version = "*", features = ["derive"] } 9 | serde_bytes = "*" 10 | crossbeam = "*" 11 | rmpv = { git = "https://github.com/3Hren/msgpack-rust", version = "*", features = ["with-serde"] } 12 | url_serde = "*" 13 | serde_json = "*" 14 | log = "*" 15 | lazy_static = "1.3.0" 16 | lsp-types = { version = "0.61.0", features = ["proposed"] } 17 | simple-logging = "*" 18 | url = "*" 19 | dirs = "2.0.2" 20 | bimap = "0.4" 21 | ropey = "*" 22 | -------------------------------------------------------------------------------- /DEVELOPMENT: -------------------------------------------------------------------------------- 1 | HOW TO SETUP A DEVELOPMENT ENVIRONMENT 2 | ------------------------------------- 3 | 4 | The following describe the steps to setup a minimal VIM environment 5 | that you can use to test LSPC. 6 | 7 | First, create a new VIM profile folder at whereever you want, with 8 | the following folder structure, for example: 9 | 10 | ~/development/vim 11 | . 12 | ├── autoload 13 | │   └── plug.vim 14 | └── init.vim 15 | 16 | You can download the plug.vim file from VimPlug repository [1] 17 | 18 | The content of init.vim can be this simple: 19 | 20 | call plug#begin() 21 | Plug '~/code/play/lspc/' 22 | call plug#end() 23 | 24 | let g:lspc = { 25 | \ 'rust': { 26 | \ 'root_markers': ['Cargo.lock'], 27 | \ 'command': ['rustup', 'run', 'stable', 'ra_lsp_server'], 28 | \ }, 29 | \ } 30 | 31 | Then you can start VIM with the -u parameter pointing to your dev 32 | profile: 33 | 34 | vim -u ~/development/vim/init.vim 35 | 36 | And that's it. 37 | 38 | [1]: https://github.com/junegunn/vim-plug 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lsp Client 2 | 3 | Language server protocol client implement in Rust. 4 | First target editor is neovim. 5 | First target language server is [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) 6 | 7 | # Development setup 8 | 9 | 1. Clone project 10 | 2. Add path to vim-plug: 11 | ``` 12 | Plug '~/path/to/lspc' 13 | ``` 14 | 15 | 3. Build project 16 | ``` 17 | cargo build 18 | ``` 19 | 20 | 4. Config your vim: 21 | ``` 22 | let g:lspc = { 23 | \ 'rust': { 24 | \ 'root_markers': ['Cargo.lock'], 25 | \ 'command': ['rustup', 'run', 'stable', 'ra_lsp_server'], 26 | \ }, 27 | \ } 28 | ``` 29 | 30 | 5. Start Rust handler: 31 | ``` 32 | :LspcStart 33 | ``` 34 | or 35 | ``` 36 | :call lspc#init() 37 | ``` 38 | 39 | 6. Test command: 40 | ``` 41 | :call lspc#hello_from_the_other_side() 42 | ``` 43 | 44 | 7. Start Language Server for current buffer 45 | ``` 46 | :call lspc#start_lang_server() 47 | ``` 48 | 49 | 8. View debug log at `log.txt` 50 | 51 | -------------------------------------------------------------------------------- /autoload/lspc.vim: -------------------------------------------------------------------------------- 1 | let s:config = { 2 | \ 'auto_start': v:true, 3 | \ } 4 | 5 | function! lspc#output(log) 6 | " if !exists('s:output_buffer') || !nvim_buf_is_loaded(s:output_buffer) 7 | " let s:output_buffer = nvim_create_buf(v:true, v:false) 8 | " call nvim_buf_set_name(s:output_buffer, "[LSPC Output]") 9 | " call nvim_buf_set_option(s:output_buffer, "buftype", "nofile") 10 | " call nvim_buf_set_option(s:output_buffer, "buflisted", v:true) 11 | " endif 12 | 13 | " let l:line = nvim_buf_line_count(s:output_buffer) - 1 14 | " call nvim_buf_set_lines(s:output_buffer, l:line, l:line, v:true, a:log) 15 | echom string(a:log) 16 | endfunction 17 | 18 | function! s:echo_handler(job_id, data, name) 19 | let l:log = "[LSPC][" . a:name ."]. Data: " . string(a:data) 20 | call lspc#output(l:log) 21 | endfunction 22 | 23 | function! lspc#init() 24 | call extend(s:config, g:lspc) 25 | 26 | if lspc#started() 27 | return 28 | endif 29 | 30 | let s:lang_servers = [] 31 | 32 | let l:binpath = s:root . '/target/debug/neovim_lspc' 33 | 34 | call setenv('RUST_BACKTRACE', '1') 35 | let s:job_id = jobstart([l:binpath], { 36 | \ 'rpc': v:true, 37 | \ 'on_stderr': function('s:echo_handler'), 38 | \ 'on_exit': function('s:echo_handler'), 39 | \ }) 40 | if s:job_id == -1 41 | echom "[LSPC] Cannot execute " . l:binpath 42 | endif 43 | endfunction 44 | 45 | function! lspc#started() abort 46 | return exists('s:job_id') 47 | endfunction 48 | 49 | function! lspc#destroy() 50 | call jobstop(s:job_id) 51 | unlet! s:job_id 52 | endfunction 53 | 54 | function! lspc#start_lang_server() 55 | if exists('b:current_syntax') 56 | let l:lang_id = b:current_syntax 57 | if has_key(s:config, l:lang_id) && !lspc#lang_server_started(l:lang_id) 58 | let l:config = s:config[l:lang_id] 59 | let l:cur_path = lspc#buffer#filename() 60 | call add(s:lang_servers, l:lang_id) 61 | call rpcnotify(s:job_id, 'start_lang_server', l:lang_id, l:config, l:cur_path) 62 | endif 63 | endif 64 | endfunction 65 | 66 | function! lspc#lang_server_started(lang_id) 67 | return index(s:lang_servers, a:lang_id) >= 0 68 | endfunction 69 | 70 | function! lspc#hover() 71 | let l:buf_id = bufnr() 72 | let l:cur_path = lspc#buffer#filename() 73 | let l:position = lspc#buffer#position() 74 | call rpcnotify(s:job_id, 'hover', l:buf_id, l:cur_path, l:position) 75 | endfunction 76 | 77 | function! lspc#reference() 78 | let l:buf_id = bufnr() 79 | let l:cur_path = lspc#buffer#filename() 80 | let l:position = lspc#buffer#position() 81 | let l:include_declaration = v:false 82 | call rpcnotify(s:job_id, 'references', l:buf_id, l:cur_path, l:position, l:include_declaration) 83 | endfunction 84 | 85 | function! lspc#track_all_buffers() 86 | let l:all_buffers = range(1, bufnr('$')) 87 | let l:listed_buffers = filter(l:all_buffers, 'buflisted(v:val)') 88 | for l:buf_id in listed_buffers 89 | let l:buf_path = expand('#' . buf_id . ':p') 90 | call rpcnotify(s:job_id, 'did_open', l:buf_id, l:buf_path) 91 | endfor 92 | endfunction 93 | 94 | function! lspc#did_open() 95 | let l:buf_id = bufnr() 96 | let l:cur_path = lspc#buffer#filename() 97 | if s:config['auto_start'] 98 | call lspc#start_lang_server() 99 | endif 100 | call rpcnotify(s:job_id, 'did_open', l:buf_id, l:cur_path) 101 | endfunction 102 | 103 | function! lspc#goto_definition() 104 | let l:buf_id = bufnr() 105 | let l:cur_path = lspc#buffer#filename() 106 | let l:position = lspc#buffer#position() 107 | call rpcnotify(s:job_id, 'goto_definition', l:buf_id, l:cur_path, l:position) 108 | endfunction 109 | 110 | function! lspc#inlay_hints() 111 | let l:buf_id = bufnr() 112 | let l:cur_path = lspc#buffer#filename() 113 | call rpcnotify(s:job_id, 'inlay_hints', l:buf_id, l:cur_path) 114 | endfunction 115 | 116 | function! lspc#format_doc() 117 | let l:buf_id = bufnr() 118 | let l:cur_path = lspc#buffer#filename() 119 | let l:lines = lspc#buffer#text() 120 | call rpcnotify(s:job_id, 'format_doc', l:buf_id, l:cur_path, l:lines) 121 | endfunction 122 | 123 | function! lspc#hello_from_the_other_side() 124 | call rpcnotify(s:job_id, 'hello') 125 | endfunction 126 | 127 | function! lspc#debug() 128 | echo "Output Buffer: " . s:output_buffer 129 | endfunction 130 | 131 | let s:root = expand(':p:h:h') 132 | 133 | if !exists('s:job_id') 134 | call lspc#init() 135 | endif 136 | -------------------------------------------------------------------------------- /autoload/lspc/buffer.vim: -------------------------------------------------------------------------------- 1 | function! lspc#buffer#filename() abort 2 | " When executing autocommand, `%` might have already changed. 3 | let l:filename = expand(':p') 4 | if !l:filename 5 | let l:filename = expand('%:p') 6 | endif 7 | return l:filename 8 | endfunction 9 | 10 | " This function will return buffer text as required by LSP. 11 | " 12 | " The main difference with getbufline is that it checks fixendofline settings 13 | " and add extra line at ending if appropriate. 14 | function! lspc#buffer#text(...) abort 15 | let l:buf = get(a:000, 0, '') 16 | 17 | let l:lines = getbufline(l:buf, 1, '$') 18 | if len(l:lines) > 0 && l:lines[-1] !=# '' && &fixendofline 19 | let l:lines += [''] 20 | endif 21 | return l:lines 22 | endfunction 23 | 24 | function! lspc#buffer#line() abort 25 | return line('.') - 1 26 | endfunction 27 | 28 | function! lspc#buffer#character() abort 29 | return col('.') - 1 30 | endfunction 31 | 32 | function! lspc#buffer#range_start_line() abort 33 | let l:lnum = v:lnum ? v:lnum : getpos("'<")[1] 34 | return l:lnum - 1 35 | endfunction 36 | 37 | function! lspc#buffer#range_end_line() abort 38 | if v:lnum 39 | return v:lnum - 1 + v:count 40 | endif 41 | 42 | return getpos("'>")[1] 43 | endfunction 44 | 45 | function! lspc#buffer#viewport() abort 46 | return { 47 | \ 'start': line('w0') - 1, 48 | \ 'end': line('w$'), 49 | \ } 50 | endfunction 51 | 52 | function! lspc#buffer#position() abort 53 | return { 54 | \ 'line': lspc#buffer#line(), 55 | \ 'character': lspc#buffer#character(), 56 | \ } 57 | endfunction 58 | -------------------------------------------------------------------------------- /autoload/lspc/command.vim: -------------------------------------------------------------------------------- 1 | let s:FLOAT_WINDOW_AVAILABLE = has('nvim') && exists('*nvim_open_win') 2 | 3 | function! lspc#command#close_floatwin_on_cursor_move(win_id, opened) abort 4 | if getpos('.') == a:opened 5 | " Just after opening floating window, CursorMoved event is run. 6 | " To avoid closing floating window immediately, check the cursor 7 | " was really moved 8 | return 9 | endif 10 | autocmd! plugin-lspc-close-hover 11 | let winnr = win_id2win(a:win_id) 12 | if winnr == 0 13 | return 14 | endif 15 | execute winnr . 'wincmd c' 16 | endfunction 17 | 18 | function! lspc#command#close_floatwin_on_buf_enter(win_id, bufnr) abort 19 | let winnr = win_id2win(a:win_id) 20 | if winnr == 0 21 | " Float window was already closed 22 | autocmd! plugin-lspc-close-hover 23 | return 24 | endif 25 | if winnr == winnr() 26 | " Cursor is moving into floating window. Do not close it 27 | return 28 | endif 29 | if bufnr('%') == a:bufnr 30 | " When current buffer opened hover window, it's not another buffer. Skipped 31 | return 32 | endif 33 | autocmd! plugin-lspc-close-hover 34 | execute winnr . 'wincmd c' 35 | endfunction 36 | 37 | " Open preview window. Window is open in: 38 | " - Floating window on Neovim (0.4.0 or later) 39 | " - Preview window on Neovim (0.3.0 or earlier) or Vim 40 | function! lspc#command#open_hover_preview(bufname, lines, filetype) abort 41 | " Use local variable since parameter is not modifiable 42 | let lines = a:lines 43 | let bufnr = bufnr('%') 44 | 45 | let use_float_win = s:FLOAT_WINDOW_AVAILABLE 46 | if use_float_win 47 | let pos = getpos('.') 48 | 49 | " Calculate width and height and give margin to lines 50 | let width = 0 51 | for index in range(len(lines)) 52 | let line = lines[index] 53 | if line !=# '' 54 | " Give a left margin 55 | let line = ' ' . line 56 | endif 57 | let lw = strdisplaywidth(line) 58 | if lw > width 59 | let width = lw 60 | endif 61 | let lines[index] = line 62 | endfor 63 | 64 | " Give margin 65 | let width += 1 66 | let lines = [''] + lines + [''] 67 | let height = len(lines) 68 | 69 | " Calculate anchor 70 | " Prefer North, but if there is no space, fallback into South 71 | let bottom_line = line('w0') + winheight(0) - 1 72 | if pos[1] + height <= bottom_line 73 | let vert = 'N' 74 | let row = 1 75 | else 76 | let vert = 'S' 77 | let row = 0 78 | endif 79 | 80 | " Prefer West, but if there is no space, fallback into East 81 | if pos[2] + width <= &columns 82 | let hor = 'W' 83 | let col = 0 84 | else 85 | let hor = 'E' 86 | let col = 1 87 | endif 88 | 89 | let float_win_id = nvim_open_win(bufnr, v:true, { 90 | \ 'relative': 'cursor', 91 | \ 'anchor': vert . hor, 92 | \ 'row': row, 93 | \ 'col': col, 94 | \ 'width': width, 95 | \ 'height': height, 96 | \ }) 97 | 98 | execute 'noswapfile edit!' a:bufname 99 | 100 | setlocal winhl=Normal:CursorLine 101 | else 102 | execute 'silent! noswapfile pedit!' a:bufname 103 | wincmd P 104 | endif 105 | 106 | setlocal buftype=nofile nobuflisted bufhidden=wipe nonumber norelativenumber signcolumn=no modifiable 107 | 108 | if a:filetype isnot v:null 109 | let &filetype = a:filetype 110 | endif 111 | 112 | call setline(1, lines) 113 | setlocal nomodified nomodifiable 114 | 115 | wincmd p 116 | 117 | if use_float_win 118 | " Unlike preview window, :pclose does not close window. Instead, close 119 | " hover window automatically when cursor is moved. 120 | let call_after_move = printf('lspc#command#close_floatwin_on_cursor_move(%d, %s)', float_win_id, string(pos)) 121 | let call_on_bufenter = printf('lspc#command#close_floatwin_on_buf_enter(%d, %d)', float_win_id, bufnr) 122 | augroup plugin-lspc-close-hover 123 | execute 'autocmd CursorMoved,CursorMovedI,InsertEnter call ' . call_after_move 124 | execute 'autocmd BufEnter * call ' . call_on_bufenter 125 | augroup END 126 | endif 127 | endfunction 128 | 129 | function! lspc#command#open_reference_preview(references) abort 130 | let references = a:references 131 | for reference in references 132 | let buf_id = bufnr(reference.filename . '$') 133 | if buf_id >= 0 134 | let content = getbufline(buf_id, reference.lnum)[0] 135 | else 136 | let content = readfile(reference.filename)[reference.lnum - 1] 137 | endif 138 | let reference.text = content 139 | endfor 140 | call setqflist([], 'r', {'title' : 'Lspc references view', 'items': references}) 141 | exec 'copen' 142 | endfunction 143 | -------------------------------------------------------------------------------- /design/README.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | ## Components 4 | 5 | ![Components](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/unrealhoang/lspc/master/design/components.puml) 6 | 7 | Thick arrow heads are for function call, slim arrow heads are for enqueue messages. 8 | 9 | * Lspc: Single Global State of system, events are handled synchronously. 10 | * LangServerHandler: Store state of a LSP Server it's controlling. There can be multiple LSPHandler instances running. 11 | * RpcClient: Handle I/O. 12 | -------------------------------------------------------------------------------- /design/components.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Editor ->> Lspc: Notify 4 | Lspc -> LangServerHandler: Notify/Request 5 | LangServerHandler ->> RpcClient: Notify/Request 6 | ... 7 | RpcClient ->> Lspc: Notify/Response 8 | Lspc -> LangServerHandler: Notify/Response 9 | LangServerHandler ->> Lspc: Update editor 10 | Lspc -> Editor: Update 11 | Editor --> Lspc: Response 12 | 13 | @enduml 14 | -------------------------------------------------------------------------------- /plugin/lspc.vim: -------------------------------------------------------------------------------- 1 | " Commands 2 | command! -nargs=0 LspcStart call lspc#init() 3 | 4 | augroup lspc 5 | autocmd! 6 | if !lspc#started() 7 | autocmd VimEnter * call lspc#init() 8 | endif 9 | autocmd BufNewFile,BufRead * call lspc#did_open() 10 | autocmd VimLeave * call lspc#destroy() 11 | augroup END 12 | -------------------------------------------------------------------------------- /src/bin/neovim_lspc.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Stdin, StdinLock, Stdout, StdoutLock}; 2 | 3 | use lspc::neovim::{Neovim, NvimMessage}; 4 | use lspc::rpc::Client; 5 | use lspc::Lspc; 6 | use std::error::Error; 7 | 8 | use lazy_static::lazy_static; 9 | 10 | lazy_static! { 11 | static ref STDIN: Stdin = io::stdin(); 12 | static ref STDOUT: Stdout = io::stdout(); 13 | } 14 | 15 | pub fn stdinlock() -> StdinLock<'static> { 16 | STDIN.lock() 17 | } 18 | 19 | pub fn stdoutlock() -> StdoutLock<'static> { 20 | STDOUT.lock() 21 | } 22 | 23 | fn main() -> Result<(), Box> { 24 | let mut log_dir = dirs::home_dir().expect("Home directory not found"); 25 | log_dir.push(".vim"); 26 | std::fs::create_dir_all(&log_dir).expect("Cannot create log directory"); 27 | 28 | log_dir.push("lspc_log.txt"); 29 | simple_logging::log_to_file(log_dir, log::LevelFilter::Debug).expect("Can not open log file"); 30 | 31 | let nvim_rpc = Client::::new(stdinlock, stdoutlock); 32 | let neovim = Neovim::new(nvim_rpc); 33 | let lspc = Lspc::new(neovim); 34 | 35 | lspc.main_loop(); 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub type Result = std::result::Result>; 2 | 3 | pub mod lspc; 4 | pub mod neovim; 5 | pub mod rpc; 6 | 7 | pub use crate::lspc::Lspc; 8 | -------------------------------------------------------------------------------- /src/lspc.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | // Custom LSP types 3 | pub mod msg; 4 | mod tracking_file; 5 | pub mod types; 6 | 7 | use std::{ 8 | collections::HashMap, 9 | io, 10 | path::{Path, PathBuf}, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use crossbeam::channel::{tick, Receiver, Select}; 15 | use lsp_types::{ 16 | self as lsp, notification as noti, 17 | request::{ 18 | Formatting, GotoDefinition, GotoDefinitionResponse, HoverRequest, Initialize, References, 19 | }, 20 | DocumentFormattingParams, FormattingOptions, Hover, Location, Position, ShowMessageParams, 21 | TextDocumentIdentifier, TextEdit, 22 | }; 23 | use serde::{Deserialize, Serialize}; 24 | use url::Url; 25 | 26 | use self::{ 27 | handler::{LangServerHandler, LangSettings}, 28 | msg::{LspMessage, RawNotification, RawRequest, RawResponse}, 29 | tracking_file::TrackingFile, 30 | types::{InlayHint, InlayHints, InlayHintsParams}, 31 | }; 32 | 33 | pub const SYNC_DELAY_MS: u64 = 500; 34 | pub const TIMER_TICK_MS: u64 = 100; 35 | 36 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 37 | pub struct LsConfig { 38 | pub command: Vec, 39 | pub root_markers: Vec, 40 | #[serde(default)] 41 | pub indentation: u64, 42 | #[serde(default)] 43 | pub indentation_with_space: bool, 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | pub enum Event { 48 | Hello, 49 | StartServer { 50 | lang_id: String, 51 | config: LsConfig, 52 | cur_path: String, 53 | }, 54 | Hover { 55 | text_document: TextDocumentIdentifier, 56 | position: Position, 57 | }, 58 | GotoDefinition { 59 | text_document: TextDocumentIdentifier, 60 | position: Position, 61 | }, 62 | InlayHints { 63 | text_document: TextDocumentIdentifier, 64 | }, 65 | FormatDoc { 66 | text_document_lines: Vec, 67 | text_document: TextDocumentIdentifier, 68 | }, 69 | DidOpen { 70 | text_document: TextDocumentIdentifier, 71 | }, 72 | DidChange { 73 | text_document: TextDocumentIdentifier, 74 | version: i64, 75 | content_change: lsp::TextDocumentContentChangeEvent, 76 | }, 77 | DidClose { 78 | text_document: TextDocumentIdentifier, 79 | }, 80 | References { 81 | text_document: TextDocumentIdentifier, 82 | position: Position, 83 | include_declaration: bool, 84 | }, 85 | } 86 | 87 | #[derive(Debug)] 88 | pub enum EditorError { 89 | Timeout, 90 | Parse(&'static str), 91 | CommandDataInvalid(&'static str), 92 | UnexpectedResponse(&'static str), 93 | UnexpectedMessage(String), 94 | Failed(String), 95 | RootPathNotFound, 96 | } 97 | 98 | impl From for LspcError { 99 | fn from(e: EditorError) -> Self { 100 | LspcError::Editor(e) 101 | } 102 | } 103 | 104 | #[derive(Debug)] 105 | pub enum LangServerError { 106 | Process(io::Error), 107 | ServerDisconnected, 108 | InvalidRequest(String), 109 | InvalidNotification(String), 110 | InvalidResponse(String), 111 | } 112 | 113 | impl From for LangServerError { 114 | fn from(r: RawRequest) -> Self { 115 | LangServerError::InvalidRequest(format!("{:?}", r)) 116 | } 117 | } 118 | 119 | impl From for LangServerError { 120 | fn from(r: RawNotification) -> Self { 121 | LangServerError::InvalidNotification(format!("{:?}", r)) 122 | } 123 | } 124 | 125 | impl From for LangServerError { 126 | fn from(r: RawResponse) -> Self { 127 | LangServerError::InvalidResponse(format!("{:?}", r)) 128 | } 129 | } 130 | 131 | impl From for LspcError { 132 | fn from(e: MainLoopError) -> Self { 133 | LspcError::MainLoop(e) 134 | } 135 | } 136 | 137 | impl From for LspcError 138 | where 139 | T: Into, 140 | { 141 | fn from(r: T) -> Self { 142 | LspcError::LangServer(r.into()) 143 | } 144 | } 145 | 146 | #[derive(Debug)] 147 | pub enum MainLoopError { 148 | IgnoredMessage, 149 | } 150 | 151 | #[derive(Debug)] 152 | pub enum LspcError { 153 | Editor(EditorError), 154 | LangServer(LangServerError), 155 | MainLoop(MainLoopError), 156 | // Requested lang_id server is not started 157 | NotStarted, 158 | } 159 | 160 | pub trait BufferId: Eq + std::fmt::Debug + std::hash::Hash + Copy + 'static {} 161 | 162 | pub trait Editor: 'static { 163 | type BufferId: BufferId; 164 | 165 | fn events(&self) -> Receiver; 166 | fn capabilities(&self) -> lsp_types::ClientCapabilities; 167 | fn say_hello(&self) -> Result<(), EditorError>; 168 | fn message(&mut self, msg: &str) -> Result<(), EditorError>; 169 | fn show_hover( 170 | &mut self, 171 | text_document: &TextDocumentIdentifier, 172 | hover: &Hover, 173 | ) -> Result<(), EditorError>; 174 | fn inline_hints( 175 | &mut self, 176 | text_document: &TextDocumentIdentifier, 177 | hints: &Vec, 178 | ) -> Result<(), EditorError>; 179 | fn show_message(&mut self, show_message_params: &ShowMessageParams) -> Result<(), EditorError>; 180 | fn show_references(&mut self, locations: &Vec) -> Result<(), EditorError>; 181 | fn goto(&mut self, location: &Location) -> Result<(), EditorError>; 182 | fn apply_edits(&self, lines: &Vec, edits: &Vec) -> Result<(), EditorError>; 183 | fn track_all_buffers(&self) -> Result<(), EditorError>; 184 | fn watch_file_events( 185 | &mut self, 186 | text_document: &TextDocumentIdentifier, 187 | ) -> Result<(), EditorError>; 188 | } 189 | 190 | pub struct Lspc { 191 | editor: E, 192 | lsp_handlers: Vec>, 193 | tracking_files: HashMap, 194 | next_handler_id: u64, 195 | } 196 | 197 | #[derive(Debug)] 198 | enum SelectedMsg { 199 | Editor(Event), 200 | Lsp(usize, LspMessage), 201 | TimerTick, 202 | } 203 | 204 | fn select( 205 | event_receiver: &Receiver, 206 | timer_tick: &Receiver, 207 | handlers: &Vec>, 208 | ) -> SelectedMsg { 209 | let mut sel = Select::new(); 210 | 211 | sel.recv(event_receiver); 212 | sel.recv(timer_tick); 213 | 214 | for lsp_client in handlers.iter() { 215 | sel.recv(&lsp_client.receiver()); 216 | } 217 | 218 | let oper = sel.select(); 219 | match oper.index() { 220 | 0 => { 221 | let nvim_msg = oper.recv(event_receiver).unwrap(); 222 | SelectedMsg::Editor(nvim_msg) 223 | } 224 | 1 => { 225 | oper.recv(timer_tick).unwrap(); 226 | SelectedMsg::TimerTick 227 | } 228 | i => { 229 | let lsp_msg = oper.recv(handlers[i - 2].receiver()).unwrap(); 230 | 231 | SelectedMsg::Lsp(i - 2, lsp_msg) 232 | } 233 | } 234 | } 235 | 236 | fn find_root_path<'a>(mut cur_path: &'a Path, root_marker: &Vec) -> Option<&'a Path> { 237 | if cur_path.is_file() { 238 | cur_path = cur_path.parent()?; 239 | } 240 | loop { 241 | if root_marker 242 | .iter() 243 | .any(|marker| cur_path.join(marker).exists()) 244 | { 245 | return Some(cur_path); 246 | } 247 | cur_path = cur_path.parent()?; 248 | } 249 | } 250 | 251 | fn to_file_url(s: &str) -> Option { 252 | Url::from_file_path(s).ok() 253 | } 254 | 255 | // Get the handler of a file by checking 256 | // if that handler's root is ancestor of `file_path` 257 | fn handler_of<'a, E>( 258 | handlers: &'a mut Vec>, 259 | file_path: &str, 260 | ) -> Option<&'a mut LangServerHandler> 261 | where 262 | E: Editor, 263 | { 264 | handlers 265 | .iter_mut() 266 | .find(|handler| handler.include_file(file_path)) 267 | } 268 | 269 | impl Lspc { 270 | fn handler_for_file( 271 | &mut self, 272 | uri: &Url, 273 | ) -> Option<(&mut LangServerHandler, &mut TrackingFile, &mut E)> { 274 | let tracking_file = self.tracking_files.get_mut(uri)?; 275 | let handler = self 276 | .lsp_handlers 277 | .iter_mut() 278 | .find(|handler| handler.id == tracking_file.handler_id)?; 279 | Some((handler, tracking_file, &mut self.editor)) 280 | } 281 | 282 | fn handle_editor_event(&mut self, event: Event) -> Result<(), LspcError> { 283 | match event { 284 | Event::Hello => { 285 | self.editor.say_hello().map_err(|e| LspcError::Editor(e))?; 286 | } 287 | Event::StartServer { 288 | lang_id, 289 | config, 290 | cur_path, 291 | } => { 292 | let capabilities = self.editor.capabilities(); 293 | let lang_settings = LangSettings { 294 | indentation: config.indentation, 295 | indentation_with_space: config.indentation_with_space, 296 | }; 297 | 298 | let cur_path = PathBuf::from(cur_path); 299 | let root = find_root_path(&cur_path, &config.root_markers) 300 | .map(|path| path.to_str()) 301 | .ok_or_else(|| LspcError::Editor(EditorError::RootPathNotFound))? 302 | .ok_or_else(|| LspcError::Editor(EditorError::RootPathNotFound))?; 303 | 304 | let root_url = 305 | to_file_url(&root).ok_or(LspcError::Editor(EditorError::RootPathNotFound))?; 306 | 307 | self.next_handler_id += 1; 308 | let mut lsp_handler = LangServerHandler::new( 309 | self.next_handler_id, 310 | lang_id, 311 | &config.command[0], 312 | lang_settings, 313 | &config.command[1..], 314 | root.to_owned(), 315 | ) 316 | .map_err(|e| LspcError::LangServer(e))?; 317 | 318 | let init_params = lsp_types::InitializeParams { 319 | process_id: Some(std::process::id() as u64), 320 | root_path: Some(root.into()), 321 | root_uri: Some(root_url), 322 | initialization_options: None, 323 | capabilities, 324 | trace: None, 325 | workspace_folders: None, 326 | }; 327 | lsp_handler.lsp_request::( 328 | &init_params, 329 | Box::new(|editor: &mut E, handler, response| { 330 | handler.initialize_response(response)?; 331 | 332 | editor.message("LangServer initialized")?; 333 | editor.track_all_buffers()?; 334 | Ok(()) 335 | }), 336 | )?; 337 | 338 | self.lsp_handlers.push(lsp_handler); 339 | } 340 | Event::Hover { 341 | text_document, 342 | position, 343 | } => { 344 | let (handler, _, _) = 345 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 346 | log::info!("Nontracking file: {:?}", text_document); 347 | MainLoopError::IgnoredMessage 348 | })?; 349 | let text_document_clone = text_document.clone(); 350 | let params = lsp_types::TextDocumentPositionParams { 351 | text_document, 352 | position, 353 | }; 354 | handler.lsp_request::( 355 | ¶ms, 356 | Box::new(move |editor: &mut E, _handler, response| { 357 | if let Some(hover) = response { 358 | editor.show_hover(&text_document_clone, &hover)?; 359 | } 360 | Ok(()) 361 | }), 362 | )?; 363 | } 364 | Event::GotoDefinition { 365 | text_document, 366 | position, 367 | } => { 368 | let (handler, _, _) = 369 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 370 | log::info!("Nontracking file: {:?}", text_document); 371 | MainLoopError::IgnoredMessage 372 | })?; 373 | let params = lsp_types::TextDocumentPositionParams { 374 | text_document, 375 | position, 376 | }; 377 | handler.lsp_request::( 378 | ¶ms, 379 | Box::new(move |editor: &mut E, _handler, response| { 380 | if let Some(definition) = response { 381 | match definition { 382 | GotoDefinitionResponse::Scalar(location) => { 383 | editor.goto(&location)?; 384 | } 385 | GotoDefinitionResponse::Array(array) => { 386 | if array.len() == 1 { 387 | editor.goto(&array[0])?; 388 | } 389 | } 390 | _ => { 391 | // FIXME: support Array & Link 392 | } 393 | } 394 | } 395 | 396 | Ok(()) 397 | }), 398 | )?; 399 | } 400 | Event::InlayHints { text_document } => { 401 | let (handler, _, _) = 402 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 403 | log::info!("Nontracking file: {:?}", text_document); 404 | MainLoopError::IgnoredMessage 405 | })?; 406 | let text_document_clone = text_document.clone(); 407 | let params = InlayHintsParams { text_document }; 408 | handler.lsp_request::( 409 | ¶ms, 410 | Box::new(move |editor: &mut E, _handler, response| { 411 | editor.inline_hints(&text_document_clone, &response)?; 412 | 413 | Ok(()) 414 | }), 415 | )?; 416 | } 417 | Event::FormatDoc { 418 | text_document_lines, 419 | text_document, 420 | } => { 421 | let (handler, _, _) = 422 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 423 | log::info!("Nontracking file: {:?}", text_document); 424 | MainLoopError::IgnoredMessage 425 | })?; 426 | let options = FormattingOptions { 427 | tab_size: handler.lang_settings.indentation, 428 | insert_spaces: handler.lang_settings.indentation_with_space, 429 | properties: HashMap::new(), 430 | }; 431 | let params = DocumentFormattingParams { 432 | text_document, 433 | options, 434 | }; 435 | handler.lsp_request::( 436 | ¶ms, 437 | Box::new(move |editor: &mut E, _handler, response| { 438 | if let Some(edits) = response { 439 | editor.apply_edits(&text_document_lines, &edits)?; 440 | } 441 | 442 | Ok(()) 443 | }), 444 | )?; 445 | } 446 | Event::References { 447 | text_document, 448 | position, 449 | include_declaration, 450 | } => { 451 | let (handler, _, _) = 452 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 453 | log::info!("Nontracking file: {:?}", text_document); 454 | MainLoopError::IgnoredMessage 455 | })?; 456 | let params = lsp::ReferenceParams { 457 | text_document_position: lsp::TextDocumentPositionParams { 458 | text_document, 459 | position, 460 | }, 461 | context: lsp::ReferenceContext { 462 | include_declaration, 463 | }, 464 | }; 465 | 466 | handler.lsp_request::( 467 | ¶ms, 468 | Box::new(move |editor: &mut E, _handler, response| { 469 | if let Some(locations) = response { 470 | editor.show_references(&locations)?; 471 | } 472 | 473 | Ok(()) 474 | }), 475 | )?; 476 | } 477 | Event::DidOpen { text_document } => { 478 | let file_path = text_document.uri.path(); 479 | let handler = handler_of(&mut self.lsp_handlers, &file_path).ok_or_else(|| { 480 | log::info!("Unmanaged file: {:?}", text_document.uri); 481 | MainLoopError::IgnoredMessage 482 | })?; 483 | 484 | self.editor.watch_file_events(&text_document)?; 485 | self.tracking_files.insert( 486 | text_document.uri.clone(), 487 | TrackingFile::new(handler.id, text_document.uri, handler.sync_kind()), 488 | ); 489 | } 490 | Event::DidChange { 491 | text_document, 492 | version, 493 | content_change, 494 | } => { 495 | log::debug!( 496 | "Received did change event: {:?}, {}, {:?}", 497 | text_document, 498 | version, 499 | content_change 500 | ); 501 | let (handler, tracking_file, _) = 502 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 503 | log::info!( 504 | "Received changed event for nontracking file: {:?}", 505 | text_document 506 | ); 507 | MainLoopError::IgnoredMessage 508 | })?; 509 | 510 | tracking_file.track_change(version, &content_change); 511 | 512 | if !tracking_file.sent_did_open { 513 | handler.lsp_notify::( 514 | &lsp::DidOpenTextDocumentParams { 515 | text_document: lsp::TextDocumentItem { 516 | uri: text_document.uri.clone(), 517 | language_id: handler.lang_id.clone(), 518 | version, 519 | text: content_change.text, 520 | }, 521 | }, 522 | )?; 523 | tracking_file.sent_did_open = true; 524 | } else { 525 | tracking_file.delay_sync_in(Duration::from_millis(SYNC_DELAY_MS)); 526 | } 527 | } 528 | Event::DidClose { text_document } => { 529 | let (handler, tracking_file, _) = 530 | self.handler_for_file(&text_document.uri).ok_or_else(|| { 531 | log::info!( 532 | "Received changed event for nontracking file: {:?}", 533 | text_document 534 | ); 535 | MainLoopError::IgnoredMessage 536 | })?; 537 | 538 | let pending_changes = tracking_file.fetch_pending_changes(); 539 | if let Some(params) = pending_changes { 540 | handler.lsp_notify::(¶ms)?; 541 | } 542 | handler.lsp_notify::( 543 | &lsp::DidCloseTextDocumentParams { 544 | text_document: text_document, 545 | }, 546 | )?; 547 | } 548 | } 549 | 550 | Ok(()) 551 | } 552 | 553 | fn handle_lsp_msg(&mut self, index: usize, msg: LspMessage) -> Result<(), LspcError> { 554 | let lsp_handler = &mut self.lsp_handlers[index]; 555 | match msg { 556 | LspMessage::Request(_req) => {} 557 | LspMessage::Notification(mut noti) => { 558 | noti = match noti.cast::() { 559 | Ok(params) => { 560 | self.editor.show_message(¶ms)?; 561 | 562 | return Ok(()); 563 | } 564 | Err(noti) => noti, 565 | }; 566 | 567 | log::warn!("Not supported notification: {:?}", noti); 568 | } 569 | LspMessage::Response(res) => { 570 | if let Some(callback) = lsp_handler.callback_for(res.id) { 571 | (callback.func)(&mut self.editor, lsp_handler, res)?; 572 | } else { 573 | log::error!("not requested response: {:?}", res); 574 | } 575 | } 576 | } 577 | 578 | Ok(()) 579 | } 580 | 581 | fn handle_timer_tick(&mut self) -> Result<(), LspcError> { 582 | let now = Instant::now(); 583 | let sync_due_files = self 584 | .tracking_files 585 | .iter() 586 | .filter(|(_, buf)| { 587 | if let Some(instant) = buf.scheduled_sync_at { 588 | instant <= now 589 | } else { 590 | false 591 | } 592 | }) 593 | .map(|(file_url, _)| file_url) 594 | .cloned() 595 | .collect::>(); 596 | 597 | for uri in sync_due_files { 598 | log::debug!("File changes due: {:?}", uri); 599 | let (handler, tracking_file, _) = self.handler_for_file(&uri).ok_or_else(|| { 600 | log::info!("Received changed event for nontracking file: {:?}", uri); 601 | MainLoopError::IgnoredMessage 602 | })?; 603 | let pending_changes = tracking_file.fetch_pending_changes(); 604 | if let Some(params) = pending_changes { 605 | handler.lsp_notify::(¶ms)?; 606 | } 607 | } 608 | Ok(()) 609 | } 610 | } 611 | 612 | impl Lspc { 613 | pub fn new(editor: E) -> Self { 614 | Lspc { 615 | editor, 616 | lsp_handlers: Vec::new(), 617 | tracking_files: HashMap::new(), 618 | next_handler_id: 0, 619 | } 620 | } 621 | 622 | pub fn main_loop(mut self) { 623 | let event_receiver = self.editor.events(); 624 | let timer_tick = tick(Duration::from_millis(TIMER_TICK_MS)); 625 | 626 | loop { 627 | let selected = select(&event_receiver, &timer_tick, &self.lsp_handlers); 628 | let result = match selected { 629 | SelectedMsg::Editor(event) => self.handle_editor_event(event), 630 | SelectedMsg::Lsp(index, msg) => self.handle_lsp_msg(index, msg), 631 | SelectedMsg::TimerTick => self.handle_timer_tick(), 632 | }; 633 | if let Err(e) = result { 634 | log::error!("Handle error: {:?}", e); 635 | } 636 | } 637 | } 638 | } 639 | -------------------------------------------------------------------------------- /src/lspc/handler.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | path::Path, 4 | process::{Command, Stdio}, 5 | sync::atomic::{AtomicU64, Ordering}, 6 | }; 7 | 8 | use crossbeam::channel::Receiver; 9 | use lsp_types::{ 10 | self as lsp, 11 | notification::{Initialized, Notification}, 12 | request::Request, 13 | InitializeResult, ServerCapabilities, 14 | }; 15 | use serde::{de::DeserializeOwned, Serialize}; 16 | 17 | use super::{ 18 | msg::{LspMessage, RawNotification, RawRequest, RawResponse}, 19 | Editor, LangServerError, LspcError, 20 | }; 21 | use crate::rpc; 22 | 23 | pub type RawCallback = 24 | Box, RawResponse) -> Result<(), LspcError>>; 25 | 26 | pub struct Callback { 27 | pub id: u64, 28 | pub func: RawCallback, 29 | } 30 | 31 | pub struct LangSettings { 32 | pub indentation: u64, 33 | pub indentation_with_space: bool, 34 | } 35 | 36 | pub struct LangServerHandler { 37 | pub id: u64, 38 | pub lang_id: String, 39 | rpc_client: rpc::Client, 40 | callbacks: Vec>, 41 | next_id: AtomicU64, 42 | root_path: String, 43 | // None if server is not started 44 | server_capabilities: Option, 45 | pub lang_settings: LangSettings, 46 | } 47 | 48 | impl LangServerHandler { 49 | pub fn new( 50 | id: u64, 51 | lang_id: String, 52 | command: &String, 53 | lang_settings: LangSettings, 54 | args: &[String], 55 | root_path: String, 56 | ) -> Result { 57 | let child_process = Command::new(command) 58 | .args(args) 59 | .stdin(Stdio::piped()) 60 | .stdout(Stdio::piped()) 61 | .spawn() 62 | .map_err(|e| LangServerError::Process(e))?; 63 | 64 | let _child_pid = child_process.id(); 65 | let child_stdout = child_process.stdout.unwrap(); 66 | let child_stdin = child_process.stdin.unwrap(); 67 | 68 | let rpc_client = rpc::Client::::new(move || child_stdout, move || child_stdin); 69 | 70 | Ok(LangServerHandler { 71 | id, 72 | rpc_client, 73 | lang_id, 74 | next_id: AtomicU64::new(1), 75 | root_path, 76 | callbacks: Vec::new(), 77 | server_capabilities: None, 78 | lang_settings, 79 | }) 80 | } 81 | 82 | pub fn include_file(&self, file_path: &str) -> bool { 83 | let file_path = Path::new(file_path); 84 | 85 | file_path.starts_with(&self.root_path) 86 | } 87 | 88 | pub fn sync_kind(&self) -> lsp::TextDocumentSyncKind { 89 | if let Some(ref cap) = self.server_capabilities { 90 | match cap.text_document_sync { 91 | Some(lsp::TextDocumentSyncCapability::Kind(kind)) => return kind, 92 | Some(lsp::TextDocumentSyncCapability::Options(ref opts)) => { 93 | if let Some(kind) = opts.change { 94 | return kind; 95 | } 96 | } 97 | _ => {} 98 | } 99 | } 100 | lsp::TextDocumentSyncKind::Full 101 | } 102 | 103 | fn send_msg(&self, msg: LspMessage) -> Result<(), LangServerError> { 104 | self.rpc_client 105 | .sender 106 | .send(msg) 107 | .map_err(|_| LangServerError::ServerDisconnected)?; 108 | 109 | Ok(()) 110 | } 111 | 112 | pub fn receiver(&self) -> &Receiver { 113 | &self.rpc_client.receiver 114 | } 115 | 116 | fn fetch_id(&self) -> u64 { 117 | self.next_id.fetch_add(1, Ordering::Relaxed) 118 | } 119 | 120 | pub fn callback_for(&mut self, id: u64) -> Option> { 121 | let cb_index = self.callbacks.iter().position(|cb| cb.id == id); 122 | if let Some(index) = cb_index { 123 | let callback = self.callbacks.swap_remove(index); 124 | Some(callback) 125 | } else { 126 | None 127 | } 128 | } 129 | 130 | pub fn initialize_response( 131 | &mut self, 132 | response: InitializeResult, 133 | ) -> Result<(), LangServerError> { 134 | let server_capabilities = response.capabilities; 135 | self.server_capabilities = Some(server_capabilities); 136 | 137 | self.initialized()?; 138 | 139 | Ok(()) 140 | } 141 | 142 | pub fn initialized(&mut self) -> Result<(), LangServerError> { 143 | log::debug!("Sending initialized notification"); 144 | 145 | self.lsp_notify::(&lsp_types::InitializedParams {}) 146 | } 147 | 148 | pub fn lsp_request( 149 | &mut self, 150 | params: &R::Params, 151 | cb: Box, R::Result) -> Result<(), LspcError>>, 152 | ) -> Result<(), LangServerError> 153 | where 154 | R::Params: Serialize + Debug, 155 | R::Result: DeserializeOwned + 'static, 156 | E: 'static, 157 | { 158 | log::debug!("Send LSP request: {} with {:?}", R::METHOD, params); 159 | 160 | let id = self.fetch_id(); 161 | let request = RawRequest::new::(id, params); 162 | let raw_callback: RawCallback = 163 | Box::new(move |e, handler, raw_response: RawResponse| { 164 | log::debug!("{} callback", R::METHOD); 165 | let response = raw_response.cast::()?; 166 | cb(e, handler, response) 167 | }); 168 | let func = Box::new(raw_callback); 169 | self.callbacks.push(Callback { id, func }); 170 | self.request(request) 171 | } 172 | 173 | fn request(&mut self, request: RawRequest) -> Result<(), LangServerError> { 174 | self.send_msg(LspMessage::Request(request)) 175 | } 176 | 177 | pub fn lsp_notify(&mut self, params: &R::Params) -> Result<(), LangServerError> 178 | where 179 | R::Params: Serialize + Debug, 180 | { 181 | let noti = RawNotification::new::(params); 182 | self.send_msg(LspMessage::Notification(noti)) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/lspc/msg.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | io::{BufRead, Write}, 4 | }; 5 | 6 | use log; 7 | use serde::{Deserialize, Serialize}; 8 | use serde_json::{from_str, from_value, to_string, to_value, Value}; 9 | 10 | use lsp_types::{ 11 | notification::{Exit, Notification}, 12 | request::Request, 13 | }; 14 | 15 | use crate::rpc::{Message, RpcError}; 16 | 17 | #[derive(Serialize, Deserialize, Debug, Clone)] 18 | #[serde(untagged)] 19 | pub enum LspMessage { 20 | Request(RawRequest), 21 | Notification(RawNotification), 22 | Response(RawResponse), 23 | } 24 | 25 | impl From for LspMessage { 26 | fn from(raw: RawRequest) -> LspMessage { 27 | LspMessage::Request(raw) 28 | } 29 | } 30 | 31 | impl From for LspMessage { 32 | fn from(raw: RawNotification) -> LspMessage { 33 | LspMessage::Notification(raw) 34 | } 35 | } 36 | 37 | impl From for LspMessage { 38 | fn from(raw: RawResponse) -> LspMessage { 39 | LspMessage::Response(raw) 40 | } 41 | } 42 | 43 | #[derive(Debug, Serialize, Deserialize, Clone)] 44 | pub struct RawRequest { 45 | pub id: u64, 46 | pub method: String, 47 | pub params: Value, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize, Clone)] 51 | pub struct RawResponse { 52 | // JSON RPC allows this to be null if it was impossible 53 | // to decode the request's id. Ignore this special case 54 | // and just die horribly. 55 | pub id: u64, 56 | #[serde(skip_serializing_if = "Option::is_none")] 57 | pub result: Option, 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub error: Option, 60 | } 61 | 62 | #[derive(Debug, Serialize, Deserialize, Clone)] 63 | pub struct RawResponseError { 64 | pub code: i32, 65 | pub message: String, 66 | #[serde(skip_serializing_if = "Option::is_none")] 67 | pub data: Option, 68 | } 69 | 70 | #[derive(Clone, Copy, Debug)] 71 | #[allow(unused)] 72 | pub enum ErrorCode { 73 | ParseError = -32700, 74 | InvalidRequest = -32600, 75 | MethodNotFound = -32601, 76 | InvalidParams = -32602, 77 | InternalError = -32603, 78 | ServerErrorStart = -32099, 79 | ServerErrorEnd = -32000, 80 | ServerNotInitialized = -32002, 81 | UnknownErrorCode = -32001, 82 | RequestCanceled = -32800, 83 | ContentModified = -32801, 84 | } 85 | 86 | #[derive(Debug, Serialize, Deserialize, Clone)] 87 | pub struct RawNotification { 88 | pub method: String, 89 | pub params: Value, 90 | } 91 | impl Message for LspMessage { 92 | fn read(r: &mut impl BufRead) -> Result, RpcError> { 93 | let text = match read_msg_text(r).map_err(|e| RpcError::Read(e))? { 94 | None => return Ok(None), 95 | Some(text) => text, 96 | }; 97 | let msg = from_str(&text).map_err(|e| RpcError::Deserialize(e.description().into()))?; 98 | Ok(Some(msg)) 99 | } 100 | 101 | fn write(self, w: &mut impl Write) -> Result<(), RpcError> { 102 | #[derive(Serialize)] 103 | struct JsonRpc { 104 | jsonrpc: &'static str, 105 | #[serde(flatten)] 106 | msg: LspMessage, 107 | } 108 | let text = to_string(&JsonRpc { 109 | jsonrpc: "2.0", 110 | msg: self, 111 | }) 112 | .map_err(|e| RpcError::Serialize(e.description().into()))?; 113 | write_msg_text(w, &text)?; 114 | Ok(()) 115 | } 116 | 117 | fn is_exit(&self) -> bool { 118 | match self { 119 | LspMessage::Notification(n) => n.is::(), 120 | _ => false, 121 | } 122 | } 123 | } 124 | 125 | impl RawRequest { 126 | pub fn new(id: u64, params: &R::Params) -> RawRequest 127 | where 128 | R: Request, 129 | R::Params: serde::Serialize, 130 | { 131 | RawRequest { 132 | id, 133 | method: R::METHOD.to_string(), 134 | params: to_value(params).unwrap(), 135 | } 136 | } 137 | pub fn cast(self) -> ::std::result::Result<(u64, R::Params), RawRequest> 138 | where 139 | R: Request, 140 | R::Params: serde::de::DeserializeOwned, 141 | { 142 | if self.method != R::METHOD { 143 | return Err(self); 144 | } 145 | let id = self.id; 146 | let params: R::Params = from_value(self.params).unwrap(); 147 | Ok((id, params)) 148 | } 149 | } 150 | 151 | impl RawResponse { 152 | pub fn ok(id: u64, result: &R::Result) -> RawResponse 153 | where 154 | R: Request, 155 | R::Result: serde::Serialize, 156 | { 157 | RawResponse { 158 | id, 159 | result: Some(to_value(&result).unwrap()), 160 | error: None, 161 | } 162 | } 163 | pub fn err(id: u64, code: i32, message: String) -> RawResponse { 164 | let error = RawResponseError { 165 | code, 166 | message, 167 | data: None, 168 | }; 169 | RawResponse { 170 | id, 171 | result: None, 172 | error: Some(error), 173 | } 174 | } 175 | 176 | pub fn cast(self) -> ::std::result::Result 177 | where 178 | R: Request, 179 | R::Result: serde::de::DeserializeOwned, 180 | { 181 | if let Some(result) = self.result { 182 | let result: R::Result = from_value(result).unwrap(); 183 | return Ok(result); 184 | } 185 | 186 | Err(self) 187 | } 188 | } 189 | 190 | impl RawNotification { 191 | pub fn new(params: &N::Params) -> RawNotification 192 | where 193 | N: Notification, 194 | N::Params: serde::Serialize, 195 | { 196 | RawNotification { 197 | method: N::METHOD.to_string(), 198 | params: to_value(params).unwrap(), 199 | } 200 | } 201 | pub fn is(&self) -> bool 202 | where 203 | N: Notification, 204 | { 205 | self.method == N::METHOD 206 | } 207 | pub fn cast(self) -> ::std::result::Result 208 | where 209 | N: Notification, 210 | N::Params: serde::de::DeserializeOwned, 211 | { 212 | if !self.is::() { 213 | return Err(self); 214 | } 215 | Ok(from_value(self.params).unwrap()) 216 | } 217 | } 218 | 219 | fn read_msg_text(inp: &mut impl BufRead) -> Result, String> { 220 | let mut size = None; 221 | let mut buf = String::new(); 222 | loop { 223 | buf.clear(); 224 | let read_count = inp 225 | .read_line(&mut buf) 226 | .map_err(|e| e.description().to_owned())?; 227 | if read_count == 0 { 228 | return Ok(None); 229 | } 230 | if !buf.ends_with("\r\n") { 231 | Err(format!("malformed header: {:?}", buf))?; 232 | } 233 | let buf = &buf[..buf.len() - 2]; 234 | if buf.is_empty() { 235 | break; 236 | } 237 | let mut parts = buf.splitn(2, ": "); 238 | let header_name = parts.next().unwrap(); 239 | let header_value = parts 240 | .next() 241 | .ok_or_else(|| format!("malformed header: {:?}", buf))?; 242 | if header_name == "Content-Length" { 243 | size = Some( 244 | header_value 245 | .parse::() 246 | .map_err(|_| "Failed to parse header size".to_owned())?, 247 | ); 248 | } 249 | } 250 | let size = size.ok_or("no Content-Length")?; 251 | let mut buf = buf.into_bytes(); 252 | buf.resize(size, 0); 253 | inp.read_exact(&mut buf) 254 | .map_err(|e| e.description().to_owned())?; 255 | let buf = String::from_utf8(buf).map_err(|e| e.description().to_owned())?; 256 | log::debug!("< {}", buf); 257 | Ok(Some(buf)) 258 | } 259 | 260 | fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<(), RpcError> { 261 | log::debug!("> {}", msg); 262 | write!(out, "Content-Length: {}\r\n\r\n", msg.len()) 263 | .map_err(|e| RpcError::Write(e.description().into()))?; 264 | out.write_all(msg.as_bytes()) 265 | .map_err(|e| RpcError::Write(e.description().into()))?; 266 | out.flush() 267 | .map_err(|e| RpcError::Write(e.description().into()))?; 268 | Ok(()) 269 | } 270 | -------------------------------------------------------------------------------- /src/lspc/tracking_file.rs: -------------------------------------------------------------------------------- 1 | use lsp_types::{self as lsp}; 2 | use ropey::Rope; 3 | use std::time::{Duration, Instant}; 4 | use url::Url; 5 | 6 | enum SyncData { 7 | Incremental(lsp::DidChangeTextDocumentParams), 8 | Full(Rope), 9 | None, 10 | } 11 | 12 | pub struct TrackingFile { 13 | pub handler_id: u64, 14 | pub sent_did_open: bool, 15 | pub scheduled_sync_at: Option, 16 | version: i64, 17 | uri: Url, 18 | sync_data: SyncData, 19 | } 20 | 21 | impl TrackingFile { 22 | pub fn new(handler_id: u64, uri: Url, sync_kind: lsp::TextDocumentSyncKind) -> Self { 23 | let sync_data = match sync_kind { 24 | lsp::TextDocumentSyncKind::None => SyncData::None, 25 | lsp::TextDocumentSyncKind::Incremental => { 26 | SyncData::Incremental(lsp::DidChangeTextDocumentParams { 27 | text_document: lsp::VersionedTextDocumentIdentifier { 28 | uri: uri.clone(), 29 | version: None, 30 | }, 31 | content_changes: Vec::new(), 32 | }) 33 | } 34 | lsp::TextDocumentSyncKind::Full => SyncData::Full(Rope::new()), 35 | }; 36 | 37 | TrackingFile { 38 | handler_id, 39 | sent_did_open: false, 40 | scheduled_sync_at: None, 41 | version: 0, 42 | uri, 43 | sync_data, 44 | } 45 | } 46 | 47 | pub fn track_change( 48 | &mut self, 49 | version: i64, 50 | content_change: &lsp::TextDocumentContentChangeEvent, 51 | ) { 52 | self.version = version; 53 | match self.sync_data { 54 | SyncData::Incremental(ref mut changes) => { 55 | if content_change.range.is_none() { 56 | return; 57 | } 58 | let last_content_change = changes.content_changes.iter_mut().last(); 59 | if let Some(last_content_change) = last_content_change { 60 | if last_content_change.range == content_change.range { 61 | std::mem::replace(last_content_change, content_change.clone()); 62 | } else { 63 | changes.content_changes.push(content_change.clone()); 64 | } 65 | } else { 66 | changes.content_changes.push(content_change.clone()); 67 | } 68 | } 69 | SyncData::Full(ref mut content) => { 70 | println!("Before sync content: {:?}", content); 71 | println!("Sync content change: {:?}", content_change); 72 | if content_change.range.is_none() { 73 | let new_rope = Rope::from_str(&content_change.text); 74 | std::mem::replace(content, new_rope); 75 | } else { 76 | let start_line = content_change.range.unwrap().start.line as usize; 77 | let end_line = content_change.range.unwrap().end.line as isize; 78 | let end_line = end_line as usize; 79 | let start_char = content.line_to_char(start_line); 80 | let end_char = content.line_to_char(end_line); 81 | content.remove(start_char..end_char); 82 | content.insert(start_char, &content_change.text); 83 | } 84 | println!("After sync content: {:?}", content); 85 | } 86 | SyncData::None => {} 87 | } 88 | } 89 | 90 | pub fn fetch_pending_changes(&mut self) -> Option { 91 | let mut sync_content = lsp::DidChangeTextDocumentParams { 92 | text_document: lsp::VersionedTextDocumentIdentifier { 93 | uri: self.uri.clone(), 94 | version: Some(self.version), 95 | }, 96 | content_changes: Vec::new(), 97 | }; 98 | 99 | self.scheduled_sync_at = None; 100 | match self.sync_data { 101 | SyncData::Incremental(ref mut cur_sync_content) => { 102 | std::mem::swap(cur_sync_content, &mut sync_content); 103 | if !sync_content.content_changes.is_empty() { 104 | Some(sync_content) 105 | } else { 106 | None 107 | } 108 | } 109 | SyncData::Full(ref mut content) => { 110 | sync_content 111 | .content_changes 112 | .push(lsp::TextDocumentContentChangeEvent { 113 | range: None, 114 | range_length: None, 115 | text: content.to_string(), 116 | }); 117 | Some(sync_content) 118 | } 119 | SyncData::None => None, 120 | } 121 | } 122 | 123 | pub fn delay_sync_in(&mut self, duration: Duration) { 124 | if let None = self.scheduled_sync_at { 125 | self.scheduled_sync_at = Some(Instant::now() + duration); 126 | } 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod test { 132 | use super::*; 133 | #[test] 134 | fn tracking_file_full() { 135 | #[cfg(not(target_os = "windows"))] 136 | let file_path = "/a/b/c/d"; 137 | #[cfg(target_os = "windows")] 138 | let file_path = r#"C:\\a\b\d"#; 139 | 140 | let mut tracking_file = TrackingFile::new( 141 | 1, 142 | Url::from_file_path(file_path).unwrap(), 143 | lsp::TextDocumentSyncKind::Full, 144 | ); 145 | let change_event = lsp::TextDocumentContentChangeEvent { 146 | range: None, 147 | range_length: None, 148 | text: "".to_owned(), 149 | }; 150 | 151 | tracking_file.track_change(5, &change_event); 152 | let sync_request = tracking_file.fetch_pending_changes(); 153 | 154 | assert_eq!(true, sync_request.is_some()); 155 | 156 | let sync_request = sync_request.unwrap(); 157 | assert_eq!( 158 | Url::from_file_path(file_path).unwrap(), 159 | sync_request.text_document.uri 160 | ); 161 | assert_eq!(5, sync_request.text_document.version.unwrap()); 162 | assert_eq!(1, sync_request.content_changes.len()); 163 | assert_eq!("", sync_request.content_changes[0].text); 164 | 165 | // Two lines added 166 | // nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, ["line1", "line2", "line3"], v:false] 167 | let change_event = lsp::TextDocumentContentChangeEvent { 168 | range: Some(lsp::Range { 169 | start: lsp::Position { 170 | line: 0, 171 | character: 0, 172 | }, 173 | end: lsp::Position { 174 | line: 0, 175 | character: 0, 176 | }, 177 | }), 178 | range_length: None, 179 | text: "line1\nline2\nline3".to_owned(), 180 | }; 181 | tracking_file.track_change(6, &change_event); 182 | 183 | let sync_request = tracking_file.fetch_pending_changes().unwrap(); 184 | 185 | assert_eq!(6, sync_request.text_document.version.unwrap()); 186 | assert_eq!(1, sync_request.content_changes.len()); 187 | assert_eq!("line1\nline2\nline3", sync_request.content_changes[0].text); 188 | 189 | // Remove two lines 190 | // nvim_buf_lines_event[{buf}, {changedtick}, 1, 3, [], v:false] 191 | let change_event = lsp::TextDocumentContentChangeEvent { 192 | range: Some(lsp::Range { 193 | start: lsp::Position { 194 | line: 1, 195 | character: 0, 196 | }, 197 | end: lsp::Position { 198 | line: 3, 199 | character: 0, 200 | }, 201 | }), 202 | range_length: None, 203 | text: "".to_owned(), 204 | }; 205 | tracking_file.track_change(7, &change_event); 206 | 207 | let sync_request = tracking_file.fetch_pending_changes().unwrap(); 208 | 209 | assert_eq!(7, sync_request.text_document.version.unwrap()); 210 | assert_eq!(1, sync_request.content_changes.len()); 211 | assert_eq!("line1\n", sync_request.content_changes[0].text); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/lspc/types.rs: -------------------------------------------------------------------------------- 1 | use lsp_types::{request::Request, Range, TextDocumentIdentifier}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub enum InlayHints {} 5 | 6 | impl Request for InlayHints { 7 | type Params = InlayHintsParams; 8 | type Result = Vec; 9 | const METHOD: &'static str = "rust-analyzer/inlayHints"; 10 | } 11 | 12 | #[derive(Serialize, Deserialize, Debug)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct InlayHintsParams { 15 | pub text_document: TextDocumentIdentifier, 16 | } 17 | 18 | #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] 19 | pub enum InlayKind { 20 | TypeHint, 21 | } 22 | 23 | #[derive(Debug, Deserialize, Serialize)] 24 | pub struct InlayHint { 25 | pub range: Range, 26 | pub kind: InlayKind, 27 | pub label: String, 28 | } 29 | -------------------------------------------------------------------------------- /src/neovim.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | error::Error, 4 | fmt, 5 | io::{BufRead, Write}, 6 | sync::{ 7 | atomic::{AtomicU64, Ordering}, 8 | Arc, Mutex, 9 | }, 10 | thread::{self, JoinHandle}, 11 | time::Duration, 12 | }; 13 | 14 | use bimap::BiMap; 15 | use crossbeam::channel::{self, Receiver, Sender}; 16 | 17 | use lsp_types::{ 18 | self as lsp, GotoCapability, Hover, HoverCapability, HoverContents, Location, MarkedString, 19 | MarkupContent, MarkupKind, Position, ShowMessageParams, TextDocumentClientCapabilities, 20 | TextDocumentIdentifier, TextEdit, 21 | }; 22 | use rmpv::{ 23 | decode::read_value, 24 | encode::write_value, 25 | ext::{from_value, to_value}, 26 | Value, 27 | }; 28 | use serde::{ 29 | self, 30 | de::{self, SeqAccess, Visitor}, 31 | ser::SerializeSeq, 32 | Deserialize, Serialize, 33 | }; 34 | use url::Url; 35 | 36 | use crate::lspc::{types::InlayHint, BufferId, Editor, EditorError, Event, LsConfig}; 37 | use crate::rpc::{self, Message, RpcError}; 38 | 39 | pub struct Neovim { 40 | rpc_client: rpc::Client, 41 | event_receiver: Receiver, 42 | next_id: AtomicU64, 43 | subscription_sender: Sender<(u64, Sender)>, 44 | thread: JoinHandle<()>, 45 | } 46 | 47 | pub trait ToDisplay { 48 | fn to_display(&self) -> Vec; 49 | fn vim_filetype(&self) -> Option { 50 | None 51 | } 52 | } 53 | 54 | impl ToDisplay for MarkedString { 55 | fn to_display(&self) -> Vec { 56 | let s = match self { 57 | MarkedString::String(ref s) => s, 58 | MarkedString::LanguageString(ref ls) => &ls.value, 59 | }; 60 | s.lines().map(String::from).collect() 61 | } 62 | 63 | fn vim_filetype(&self) -> Option { 64 | match self { 65 | MarkedString::String(_) => Some("markdown".to_string()), 66 | MarkedString::LanguageString(ref ls) => Some(ls.language.clone()), 67 | } 68 | } 69 | } 70 | 71 | impl ToDisplay for MarkupContent { 72 | fn to_display(&self) -> Vec { 73 | self.value.lines().map(str::to_string).collect() 74 | } 75 | 76 | fn vim_filetype(&self) -> Option { 77 | match self.kind { 78 | MarkupKind::Markdown => Some("markdown".to_string()), 79 | MarkupKind::PlainText => Some("text".to_string()), 80 | } 81 | } 82 | } 83 | 84 | impl ToDisplay for Hover { 85 | fn to_display(&self) -> Vec { 86 | match self.contents { 87 | HoverContents::Scalar(ref ms) => ms.to_display(), 88 | HoverContents::Array(ref arr) => arr 89 | .iter() 90 | .flat_map(|ms| { 91 | if let MarkedString::LanguageString(ref ls) = ms { 92 | let mut buf = Vec::new(); 93 | 94 | buf.push(format!("```{}", ls.language)); 95 | buf.extend(ls.value.lines().map(String::from)); 96 | buf.push("```".to_string()); 97 | 98 | buf 99 | } else { 100 | ms.to_display() 101 | } 102 | }) 103 | .collect(), 104 | HoverContents::Markup(ref mc) => mc.to_display(), 105 | } 106 | } 107 | 108 | fn vim_filetype(&self) -> Option { 109 | match self.contents { 110 | HoverContents::Scalar(ref ms) => ms.vim_filetype(), 111 | HoverContents::Array(_) => Some("markdown".to_string()), 112 | HoverContents::Markup(ref mc) => mc.vim_filetype(), 113 | } 114 | } 115 | } 116 | 117 | impl ToDisplay for str { 118 | fn to_display(&self) -> Vec { 119 | self.lines().map(String::from).collect() 120 | } 121 | } 122 | 123 | fn apply_edits(lines: &Vec, edits: &Vec) -> String { 124 | let mut sorted_edits = edits.clone(); 125 | let mut editted_content = lines.join("\n"); 126 | sorted_edits.sort_by_key(|i| (i.range.start.line, i.range.start.character)); 127 | let mut last_modified_offset = editted_content.len(); 128 | for edit in sorted_edits.iter().rev() { 129 | let start_offset = to_document_offset(&lines, edit.range.start); 130 | let end_offset = to_document_offset(&lines, edit.range.end); 131 | 132 | if end_offset <= last_modified_offset { 133 | editted_content = format!( 134 | "{}{}{}", 135 | &editted_content[..start_offset], 136 | edit.new_text, 137 | &editted_content[end_offset..] 138 | ); 139 | } else { 140 | log::debug!("Overlapping edit!"); 141 | } 142 | 143 | last_modified_offset = start_offset; 144 | } 145 | editted_content 146 | } 147 | 148 | fn to_document_offset(lines: &Vec, pos: Position) -> usize { 149 | lines[..pos.line as usize] 150 | .iter() 151 | .map(String::len) 152 | .fold(0, |acc, current| acc + current + 1) 153 | + pos.character as usize 154 | } 155 | 156 | fn text_document_from_path_str<'de, D>(deserializer: D) -> Result 157 | where 158 | D: serde::Deserializer<'de>, 159 | { 160 | let s: String = Deserialize::deserialize(deserializer)?; 161 | let uri = Url::from_file_path(s) 162 | .map_err(|_| ::custom("could not convert path to URI"))?; 163 | 164 | Ok(TextDocumentIdentifier::new(uri)) 165 | } 166 | 167 | fn to_event(msg: NvimMessage, buf_mapper: &Mutex>) -> Result { 168 | log::debug!("Trying to convert msg: {:?} to event", msg); 169 | match msg { 170 | NvimMessage::RpcNotification { method, params } => { 171 | // Command messages 172 | if method == "hello" { 173 | Ok(Event::Hello) 174 | } else if method == "start_lang_server" { 175 | #[derive(Deserialize)] 176 | struct StartLangServerParams(String, LsConfig, String); 177 | 178 | let start_lang_params: StartLangServerParams = Deserialize::deserialize(params) 179 | .map_err(|_e| EditorError::Parse("failed to parse start lang server params"))?; 180 | 181 | Ok(Event::StartServer { 182 | lang_id: start_lang_params.0, 183 | config: start_lang_params.1, 184 | cur_path: start_lang_params.2, 185 | }) 186 | } else if method == "hover" { 187 | #[derive(Deserialize)] 188 | struct HoverParams( 189 | i64, 190 | #[serde(deserialize_with = "text_document_from_path_str")] 191 | TextDocumentIdentifier, 192 | Position, 193 | ); 194 | 195 | let hover_params: HoverParams = Deserialize::deserialize(params) 196 | .map_err(|_e| EditorError::Parse("failed to parse hover params"))?; 197 | 198 | let buf_id = BufferHandler(hover_params.0); 199 | let text_document = hover_params.1; 200 | 201 | buf_mapper 202 | .lock() 203 | .unwrap() 204 | .insert(buf_id.0, text_document.uri.clone()); 205 | 206 | Ok(Event::Hover { 207 | text_document, 208 | position: hover_params.2, 209 | }) 210 | } else if method == "goto_definition" { 211 | #[derive(Deserialize)] 212 | struct GotoDefinitionParams( 213 | i64, 214 | #[serde(deserialize_with = "text_document_from_path_str")] 215 | TextDocumentIdentifier, 216 | Position, 217 | ); 218 | 219 | let goto_definition_params: GotoDefinitionParams = Deserialize::deserialize(params) 220 | .map_err(|_e| EditorError::Parse("failed to parse goto definition params"))?; 221 | 222 | let buf_id = BufferHandler(goto_definition_params.0); 223 | let text_document = goto_definition_params.1; 224 | 225 | buf_mapper 226 | .lock() 227 | .unwrap() 228 | .insert(buf_id.0, text_document.uri.clone()); 229 | 230 | Ok(Event::GotoDefinition { 231 | text_document, 232 | position: goto_definition_params.2, 233 | }) 234 | } else if method == "inlay_hints" { 235 | #[derive(Deserialize)] 236 | struct InlayHintsParams( 237 | i64, 238 | #[serde(deserialize_with = "text_document_from_path_str")] 239 | TextDocumentIdentifier, 240 | ); 241 | 242 | let inlay_hints_params: InlayHintsParams = Deserialize::deserialize(params) 243 | .map_err(|_e| EditorError::Parse("failed to parse inlay hints params"))?; 244 | 245 | let buf_id = BufferHandler(inlay_hints_params.0); 246 | let text_document = inlay_hints_params.1; 247 | 248 | buf_mapper 249 | .lock() 250 | .unwrap() 251 | .insert(buf_id.0, text_document.uri.clone()); 252 | 253 | Ok(Event::InlayHints { text_document }) 254 | } else if method == "format_doc" { 255 | #[derive(Deserialize)] 256 | struct FormatDocParams( 257 | i64, 258 | #[serde(deserialize_with = "text_document_from_path_str")] 259 | TextDocumentIdentifier, 260 | Vec, 261 | ); 262 | 263 | let format_doc_params: FormatDocParams = Deserialize::deserialize(params) 264 | .map_err(|_e| EditorError::Parse("failed to parse goto definition params"))?; 265 | 266 | let buf_id = BufferHandler(format_doc_params.0); 267 | let text_document = format_doc_params.1; 268 | 269 | buf_mapper 270 | .lock() 271 | .unwrap() 272 | .insert(buf_id.0, text_document.uri.clone()); 273 | 274 | Ok(Event::FormatDoc { 275 | text_document, 276 | text_document_lines: format_doc_params.2, 277 | }) 278 | } else if method == "did_open" { 279 | #[derive(Deserialize)] 280 | struct DidOpenParams( 281 | i64, 282 | #[serde(deserialize_with = "text_document_from_path_str")] 283 | TextDocumentIdentifier, 284 | ); 285 | let did_open_params: DidOpenParams = Deserialize::deserialize(params) 286 | .map_err(|_e| EditorError::Parse("failed to parse did_open params"))?; 287 | 288 | let text_document = did_open_params.1; 289 | let buf_id = BufferHandler(did_open_params.0); 290 | 291 | buf_mapper 292 | .lock() 293 | .unwrap() 294 | .insert(buf_id.0, text_document.uri.clone()); 295 | 296 | Ok(Event::DidOpen { text_document }) 297 | 298 | // Callback messages 299 | } else if method == "nvim_buf_lines_event" { 300 | #[derive(Deserialize)] 301 | struct NvimBufLinesEvent( 302 | NvimHandle, // bufnr 303 | Option, // changedtick 304 | i64, // firstline 305 | i64, // lastline 306 | Vec, // linedata 307 | #[serde(default)] Option, // { more } 308 | ); 309 | let buf_line_event: NvimBufLinesEvent = 310 | Deserialize::deserialize(params).map_err(|_e| { 311 | EditorError::Parse("failed to parse nvim_buf_lines_event params") 312 | })?; 313 | 314 | if !(buf_line_event.0).is_buf() { 315 | return Err(EditorError::UnexpectedResponse("Expect buffer handler")); 316 | } 317 | if buf_line_event.1.is_none() { 318 | return Err(EditorError::UnexpectedResponse( 319 | "Not support null changedtick", 320 | )); 321 | } 322 | 323 | let buf_handler = buf_line_event.0.unwrap_buf(); 324 | let version = buf_line_event.1.unwrap(); 325 | let range = if buf_line_event.3 == -1 { 326 | None 327 | } else { 328 | Some(lsp::Range { 329 | start: lsp::Position::new(buf_line_event.2 as u64, 0), 330 | end: lsp::Position::new(buf_line_event.3 as u64, 0), 331 | }) 332 | }; 333 | let content_change = lsp::TextDocumentContentChangeEvent { 334 | range, 335 | range_length: None, 336 | text: buf_line_event.4.join("\n"), 337 | }; 338 | let text_document = { 339 | let unlocked_buf_mapper = buf_mapper.lock().unwrap(); 340 | let uri = unlocked_buf_mapper 341 | .get_by_left(&buf_handler.0) 342 | .ok_or(EditorError::UnexpectedResponse("Unknown bufnr"))?; 343 | TextDocumentIdentifier { uri: uri.clone() } 344 | }; 345 | 346 | Ok(Event::DidChange { 347 | text_document, 348 | version, 349 | content_change, 350 | }) 351 | } else if method == "nvim_buf_detach_event" { 352 | #[derive(Deserialize)] 353 | struct NvimBufDetachEvent((NvimHandle,)); 354 | 355 | let buf_detach_event: NvimBufDetachEvent = Deserialize::deserialize(params) 356 | .map_err(|_e| { 357 | EditorError::Parse("failed to parse nvim_buf_detach_event params") 358 | })?; 359 | 360 | if !((buf_detach_event.0).0).is_buf() { 361 | return Err(EditorError::UnexpectedResponse("Expect buffer handler")); 362 | } 363 | let buf_handler = (buf_detach_event.0).0.unwrap_buf(); 364 | 365 | let text_document = { 366 | let unlocked_buf_mapper = buf_mapper.lock().unwrap(); 367 | let uri = unlocked_buf_mapper 368 | .get_by_left(&buf_handler.0) 369 | .ok_or(EditorError::UnexpectedResponse("Unknown bufnr"))?; 370 | TextDocumentIdentifier { uri: uri.clone() } 371 | }; 372 | 373 | Ok(Event::DidClose { text_document }) 374 | } else if method == "references" { 375 | #[derive(Deserialize)] 376 | struct ReferencesParams( 377 | i64, 378 | #[serde(deserialize_with = "text_document_from_path_str")] 379 | TextDocumentIdentifier, 380 | Position, 381 | bool, 382 | ); 383 | 384 | let references_params: ReferencesParams = Deserialize::deserialize(params) 385 | .map_err(|_e| EditorError::Parse("failed to parse goto definition params"))?; 386 | 387 | let buf_id = references_params.0; 388 | let text_document = references_params.1; 389 | 390 | buf_mapper 391 | .lock() 392 | .unwrap() 393 | .insert(buf_id, text_document.uri.clone()); 394 | 395 | Ok(Event::References { 396 | text_document, 397 | position: references_params.2, 398 | include_declaration: references_params.3, 399 | }) 400 | } else { 401 | Err(EditorError::UnexpectedMessage(format!( 402 | "unexpected notification {:?} {:?}", 403 | method, params 404 | ))) 405 | } 406 | } 407 | _ => Err(EditorError::UnexpectedMessage(format!("{:?}", msg))), 408 | } 409 | } 410 | 411 | impl Neovim { 412 | pub fn new(rpc_client: rpc::Client) -> Self { 413 | let (event_sender, event_receiver) = channel::unbounded(); 414 | let (subscription_sender, subscription_receiver) = 415 | channel::bounded::<(u64, Sender)>(16); 416 | 417 | let rpc_receiver = rpc_client.receiver.clone(); 418 | let buf_mapper = Arc::new(Mutex::new(BiMap::new())); 419 | let buf_mapper_clone = Arc::clone(&buf_mapper); 420 | 421 | let thread = thread::spawn(move || { 422 | let mut subscriptions = Vec::<(u64, Sender)>::new(); 423 | 424 | for nvim_msg in rpc_receiver { 425 | log::debug!("< Neovim: {:?}", nvim_msg); 426 | if let NvimMessage::RpcResponse { msgid, .. } = nvim_msg { 427 | while let Ok(sub) = subscription_receiver.try_recv() { 428 | subscriptions.push(sub); 429 | } 430 | if let Some(index) = subscriptions.iter().position(|item| item.0 == msgid) { 431 | let sub = subscriptions.swap_remove(index); 432 | sub.1.send(nvim_msg).unwrap(); 433 | } else { 434 | log::error!("Received non-requested response: {}", msgid); 435 | } 436 | } else { 437 | match to_event(nvim_msg, &buf_mapper_clone) { 438 | Ok(event) => event_sender.send(event).unwrap(), 439 | Err(e) => log::error!("Cannot convert nvim msg to editor event: {:?}", e), 440 | } 441 | } 442 | } 443 | }); 444 | 445 | Neovim { 446 | next_id: AtomicU64::new(1), 447 | subscription_sender, 448 | event_receiver, 449 | rpc_client, 450 | thread, 451 | } 452 | } 453 | 454 | // using nvim_call_atomic rpc call 455 | #[allow(dead_code)] 456 | fn call_atomic(&self, calls: Value) -> Result, EditorError> { 457 | let response = self.request("nvim_call_atomic", calls); 458 | log::debug!("Response: {:?}", response); 459 | if let NvimMessage::RpcResponse { result, error, .. } = response? { 460 | if let Some(error) = error.as_array() { 461 | let error_msg = error 462 | .get(1) 463 | .ok_or(EditorError::UnexpectedResponse("Expected error message"))? 464 | .as_str() 465 | .ok_or(EditorError::UnexpectedResponse( 466 | "Expected String error message", 467 | ))?; 468 | 469 | return Err(EditorError::Failed(error_msg.into())); 470 | } 471 | 472 | if let Value::Array(results) = result { 473 | Ok(results) 474 | } else { 475 | Err(EditorError::UnexpectedResponse("Expect result array")) 476 | } 477 | } else { 478 | Err(EditorError::UnexpectedResponse("Expected response")) 479 | } 480 | } 481 | 482 | pub fn request(&self, method: &str, params: Value) -> Result { 483 | let msgid = self.next_id.fetch_add(1, Ordering::Relaxed); 484 | let req = NvimMessage::RpcRequest { 485 | msgid, 486 | method: method.into(), 487 | params: params, 488 | }; 489 | 490 | let (response_sender, response_receiver) = channel::bounded::(1); 491 | self.subscription_sender 492 | .send((msgid, response_sender)) 493 | .unwrap(); 494 | self.rpc_client.sender.send(req).unwrap(); 495 | 496 | response_receiver 497 | .recv_timeout(Duration::from_secs(60)) 498 | .map_err(|_| EditorError::Timeout) 499 | } 500 | 501 | pub fn notify(&self, method: &str, params: &[Value]) -> Result<(), EditorError> { 502 | let noti = NvimMessage::RpcNotification { 503 | method: method.into(), 504 | params: Value::from(params.to_owned()), 505 | }; 506 | // FIXME: add RpcQueueFull to EditorError?? 507 | self.rpc_client.sender.send(noti).unwrap(); 508 | 509 | Ok(()) 510 | } 511 | 512 | pub fn command(&self, command: &str) -> Result { 513 | let params = vec![Value::from(command)].into(); 514 | self.request("nvim_command", params) 515 | } 516 | 517 | // Call VimL function 518 | pub fn call_function(&self, func: &str, args: Value) -> Result { 519 | let params = vec![func.into(), args].into(); 520 | self.request("nvim_call_function", params) 521 | } 522 | 523 | pub fn create_namespace(&self, ns_name: &str) -> Result { 524 | let params = vec![Value::from(ns_name)].into(); 525 | let response = self.request("nvim_create_namespace", params)?; 526 | log::debug!("Create namespace response: {:?}", response); 527 | if let NvimMessage::RpcResponse { ref result, .. } = response { 528 | Ok(result.as_u64().ok_or(EditorError::UnexpectedResponse( 529 | "Expected nvim_create_namespace respsonse", 530 | ))?) 531 | } else { 532 | Err(EditorError::UnexpectedResponse( 533 | "Expected nvim_create_namespace respsonse", 534 | )) 535 | } 536 | } 537 | 538 | pub fn set_virtual_text( 539 | &self, 540 | buffer_id: u64, 541 | ns_id: u64, 542 | line: u64, 543 | chunks: Vec<(&str, &str)>, 544 | ) -> Result<(), EditorError> { 545 | let chunks = chunks 546 | .into_iter() 547 | .map(|(label, hl_group)| Value::Array(vec![Value::from(label), Value::from(hl_group)])) 548 | .collect::>() 549 | .into(); 550 | self.notify( 551 | "nvim_buf_set_virtual_text", 552 | &vec![ 553 | buffer_id.into(), 554 | ns_id.into(), 555 | line.into(), 556 | chunks, 557 | Value::Map(Vec::new()), 558 | ], 559 | )?; 560 | 561 | Ok(()) 562 | } 563 | 564 | pub fn receiver(&self) -> &Receiver { 565 | &self.rpc_client.receiver 566 | } 567 | 568 | pub fn close(self) { 569 | self.thread.join().unwrap(); 570 | } 571 | } 572 | 573 | impl BufferId for BufferHandler {} 574 | 575 | impl Editor for Neovim { 576 | type BufferId = BufferHandler; 577 | 578 | fn events(&self) -> Receiver { 579 | self.event_receiver.clone() 580 | } 581 | 582 | fn capabilities(&self) -> lsp_types::ClientCapabilities { 583 | lsp_types::ClientCapabilities { 584 | workspace: None, 585 | text_document: Some(TextDocumentClientCapabilities { 586 | hover: Some(HoverCapability { 587 | dynamic_registration: None, 588 | content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]), 589 | }), 590 | definition: Some(GotoCapability { 591 | dynamic_registration: None, 592 | link_support: None, 593 | }), 594 | ..Default::default() 595 | }), 596 | window: None, 597 | experimental: None, 598 | } 599 | } 600 | 601 | fn say_hello(&self) -> Result<(), EditorError> { 602 | let params = vec![Value::from("echo 'hello from the other side'")].into(); 603 | self.request("nvim_command", params) 604 | .map_err(|_| EditorError::Timeout)?; 605 | 606 | Ok(()) 607 | } 608 | 609 | fn message(&mut self, msg: &str) -> Result<(), EditorError> { 610 | self.command(&format!("echo '{}'", msg))?; 611 | Ok(()) 612 | } 613 | 614 | fn show_hover( 615 | &mut self, 616 | _text_document: &TextDocumentIdentifier, 617 | hover: &Hover, 618 | ) -> Result<(), EditorError> { 619 | // FIXME: check current buffer is `text_document` 620 | let bufname = "__LanguageClient__"; 621 | let filetype = if let Some(ft) = &hover.vim_filetype() { 622 | ft.as_str().into() 623 | } else { 624 | Value::Nil 625 | }; 626 | let lines = hover 627 | .to_display() 628 | .iter() 629 | .map(|item| Value::from(item.as_str())) 630 | .collect::>() 631 | .into(); 632 | self.call_function( 633 | "lspc#command#open_hover_preview", 634 | vec![bufname.into(), lines, filetype].into(), 635 | )?; 636 | 637 | Ok(()) 638 | } 639 | 640 | fn inline_hints( 641 | &mut self, 642 | text_document: &TextDocumentIdentifier, 643 | hints: &Vec, 644 | ) -> Result<(), EditorError> { 645 | // FIXME: check current buffer is `text_document` 646 | let ns_id = self.create_namespace(text_document.uri.path())?; 647 | for hint in hints { 648 | self.set_virtual_text( 649 | 0, 650 | ns_id, 651 | hint.range.start.line, 652 | vec![(&hint.label, "error")], 653 | )?; 654 | } 655 | 656 | Ok(()) 657 | } 658 | 659 | fn show_message(&mut self, params: &ShowMessageParams) -> Result<(), EditorError> { 660 | self.command(&format!("echo '[LS-{:?}] {}'", params.typ, params.message))?; 661 | 662 | Ok(()) 663 | } 664 | 665 | fn goto(&mut self, location: &Location) -> Result<(), EditorError> { 666 | let filepath = location 667 | .uri 668 | .to_file_path() 669 | .map_err(|_| EditorError::CommandDataInvalid("Location URI is not file path"))?; 670 | let filepath = filepath 671 | .to_str() 672 | .ok_or(EditorError::CommandDataInvalid("Filepath is not UTF-8"))?; 673 | self.command(&format!("edit {}", filepath))?; 674 | let line = location.range.start.line + 1; 675 | let col = location.range.start.character + 1; 676 | let params = Value::Array(vec![line.into(), col.into()]); 677 | self.call_function("cursor", params)?; 678 | 679 | Ok(()) 680 | } 681 | 682 | fn apply_edits(&self, lines: &Vec, edits: &Vec) -> Result<(), EditorError> { 683 | let editted_content = apply_edits(lines, edits); 684 | let new_lines: Vec = editted_content.split("\n").map(|e| e.into()).collect(); 685 | let end_line = if new_lines.len() > lines.len() { 686 | new_lines.len() - 1 687 | } else { 688 | lines.len() - 1 689 | }; 690 | let params = Value::Array(vec![ 691 | 0.into(), // 0 for current buff 692 | 0.into(), 693 | end_line.into(), 694 | false.into(), 695 | Value::Array(new_lines), 696 | ]); 697 | self.call_function("nvim_buf_set_lines", params)?; 698 | Ok(()) 699 | } 700 | 701 | fn show_references(&mut self, locations: &Vec) -> Result<(), EditorError> { 702 | let mut items: Vec = Vec::new(); 703 | for location in locations { 704 | let mut item: Vec<(Value, Value)> = Vec::new(); 705 | item.push(("filename".into(), location.uri.path().into())); 706 | item.push(("lnum".into(), (location.range.start.line + 1).into())); 707 | item.push(("col".into(), (location.range.start.character + 1).into())); 708 | items.push(Value::from(item)); 709 | } 710 | self.call_function( 711 | "lspc#command#open_reference_preview", 712 | Value::Array(vec![items.into()]), 713 | )?; 714 | 715 | Ok(()) 716 | } 717 | 718 | fn track_all_buffers(&self) -> Result<(), EditorError> { 719 | self.call_function("lspc#track_all_buffers", Value::Array(vec![]))?; 720 | Ok(()) 721 | } 722 | 723 | fn watch_file_events( 724 | &mut self, 725 | _text_document: &TextDocumentIdentifier, 726 | ) -> Result<(), EditorError> { 727 | // FIXME: check current buffer is `text_document` 728 | // 729 | // nvim_buf_attach({buffer}, {send_buffer}, {opts}) 730 | #[derive(Serialize)] 731 | struct AttachBufParams(i64, bool, HashMap<(), ()>); 732 | 733 | let attach_buf_params = AttachBufParams(0, true, HashMap::new()); 734 | let params = to_value(attach_buf_params).map_err(|e| { 735 | EditorError::Failed(format!("Failed to encode params: {}", e.description())) 736 | })?; 737 | let _result = self.request("nvim_buf_attach", params)?; 738 | 739 | Ok(()) 740 | } 741 | } 742 | 743 | impl Message for NvimMessage { 744 | fn read(r: &mut impl BufRead) -> Result, RpcError> { 745 | let value = read_value(r).map_err(|e| RpcError::Read(e.description().into()))?; 746 | log::debug!("< Nvim: {:?}", value); 747 | let inner: NvimMessage = 748 | from_value(value).map_err(|e| RpcError::Deserialize(e.description().into()))?; 749 | let r = Some(inner); 750 | 751 | Ok(r) 752 | } 753 | 754 | fn write(self, w: &mut impl Write) -> Result<(), RpcError> { 755 | log::debug!("> Nvim: {:?}", self); 756 | 757 | let value = to_value(self).map_err(|e| RpcError::Serialize(e.description().into()))?; 758 | write_value(w, &value).map_err(|e| RpcError::Write(e.description().into()))?; 759 | w.flush() 760 | .map_err(|e| RpcError::Write(e.description().into()))?; 761 | 762 | Ok(()) 763 | } 764 | 765 | fn is_exit(&self) -> bool { 766 | match self { 767 | NvimMessage::RpcNotification { method, .. } => method == "exit", 768 | _ => false, 769 | } 770 | } 771 | } 772 | 773 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 774 | pub struct BufferHandler(i64); 775 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 776 | pub struct WindowHandler(i64); 777 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 778 | pub struct TabpageHandler(i64); 779 | 780 | #[derive(Debug, PartialEq, Clone, Copy)] 781 | pub enum NvimHandle { 782 | Buffer(BufferHandler), 783 | Window(WindowHandler), 784 | Tabpage(TabpageHandler), 785 | } 786 | 787 | impl NvimHandle { 788 | pub fn is_buf(&self) -> bool { 789 | match self { 790 | NvimHandle::Buffer(_) => true, 791 | _ => false, 792 | } 793 | } 794 | 795 | pub fn is_win(&self) -> bool { 796 | match self { 797 | NvimHandle::Window(_) => true, 798 | _ => false, 799 | } 800 | } 801 | 802 | pub fn is_tab(&self) -> bool { 803 | match self { 804 | NvimHandle::Tabpage(_) => true, 805 | _ => false, 806 | } 807 | } 808 | 809 | pub fn unwrap_buf(self) -> BufferHandler { 810 | match self { 811 | NvimHandle::Buffer(buf) => buf, 812 | _ => panic!("unwrap_buf on non buf"), 813 | } 814 | } 815 | 816 | pub fn unwrap_win(self) -> WindowHandler { 817 | match self { 818 | NvimHandle::Window(win) => win, 819 | _ => panic!("unwrap_win on non win"), 820 | } 821 | } 822 | 823 | pub fn unwrap_tab(self) -> TabpageHandler { 824 | match self { 825 | NvimHandle::Tabpage(tab) => tab, 826 | _ => panic!("unwrap_tab on non tab"), 827 | } 828 | } 829 | } 830 | 831 | impl<'de> Deserialize<'de> for NvimHandle { 832 | fn deserialize(deserializer: D) -> Result 833 | where 834 | D: serde::Deserializer<'de>, 835 | { 836 | struct NvimHandleVisitor; 837 | impl<'de> Visitor<'de> for NvimHandleVisitor { 838 | type Value = NvimHandle; 839 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 840 | formatter.write_str("_ExtStruct((i8, &[u8]))") 841 | } 842 | 843 | fn visit_newtype_struct(self, deserializer: D) -> Result 844 | where 845 | D: serde::Deserializer<'de>, 846 | { 847 | let (tag, bytes): (i8, serde_bytes::ByteBuf) = 848 | Deserialize::deserialize(deserializer)?; 849 | 850 | let handle: i64 = match bytes.len() { 851 | 1 => bytes[0] as i8 as i64, 852 | 2 => { 853 | let mut b: [u8; 2] = [0; 2]; 854 | b.copy_from_slice(&bytes[..]); 855 | i16::from_le_bytes(b) as i64 856 | } 857 | 4 => { 858 | let mut b: [u8; 4] = [0; 4]; 859 | b.copy_from_slice(&bytes[..]); 860 | i32::from_le_bytes(b) as i64 861 | } 862 | 8 => { 863 | let mut b: [u8; 8] = [0; 8]; 864 | b.copy_from_slice(&bytes[..]); 865 | i64::from_le_bytes(b) 866 | } 867 | _ => return Err(::custom("invalid bytes length")), 868 | }; 869 | 870 | match tag { 871 | 0 => Ok(NvimHandle::Buffer(BufferHandler(handle))), 872 | 1 => Ok(NvimHandle::Window(WindowHandler(handle))), 873 | 2 => Ok(NvimHandle::Tabpage(TabpageHandler(handle))), 874 | _ => Err(::custom("unknown tag")), 875 | } 876 | } 877 | } 878 | 879 | deserializer.deserialize_newtype_struct(rmpv::MSGPACK_EXT_STRUCT_NAME, NvimHandleVisitor) 880 | } 881 | } 882 | 883 | impl Serialize for NvimHandle { 884 | fn serialize(&self, serializer: S) -> Result 885 | where 886 | S: serde::Serializer, 887 | { 888 | let ext_data = match self { 889 | NvimHandle::Buffer(BufferHandler(buf_id)) => (0, buf_id.to_le_bytes()), 890 | NvimHandle::Window(WindowHandler(win_id)) => (1, win_id.to_le_bytes()), 891 | NvimHandle::Tabpage(TabpageHandler(tab_id)) => (2, tab_id.to_le_bytes()), 892 | }; 893 | 894 | serializer.serialize_newtype_struct(rmpv::MSGPACK_EXT_STRUCT_NAME, &ext_data) 895 | } 896 | } 897 | 898 | #[derive(Debug, PartialEq, Clone)] 899 | pub enum NvimMessage { 900 | RpcRequest { 901 | msgid: u64, 902 | method: String, 903 | params: Value, 904 | }, // 0 905 | RpcResponse { 906 | msgid: u64, 907 | error: Value, 908 | result: Value, 909 | }, // 1 910 | RpcNotification { 911 | method: String, 912 | params: Value, 913 | }, // 2 914 | } 915 | 916 | impl Serialize for NvimMessage { 917 | fn serialize(&self, serializer: S) -> Result 918 | where 919 | S: serde::Serializer, 920 | { 921 | use NvimMessage::*; 922 | 923 | match self { 924 | RpcRequest { 925 | msgid, 926 | method, 927 | params, 928 | } => { 929 | let mut seq = serializer.serialize_seq(Some(4))?; 930 | seq.serialize_element(&0)?; 931 | seq.serialize_element(&msgid)?; 932 | seq.serialize_element(&method)?; 933 | seq.serialize_element(¶ms)?; 934 | seq.end() 935 | } 936 | RpcResponse { 937 | msgid, 938 | error, 939 | result, 940 | } => { 941 | let mut seq = serializer.serialize_seq(Some(4))?; 942 | seq.serialize_element(&1)?; 943 | seq.serialize_element(&msgid)?; 944 | seq.serialize_element(&error)?; 945 | seq.serialize_element(&result)?; 946 | seq.end() 947 | } 948 | RpcNotification { method, params } => { 949 | let mut seq = serializer.serialize_seq(Some(3))?; 950 | seq.serialize_element(&2)?; 951 | seq.serialize_element(&method)?; 952 | seq.serialize_element(¶ms)?; 953 | seq.end() 954 | } 955 | } 956 | } 957 | } 958 | 959 | struct RpcVisitor; 960 | 961 | impl<'de> Visitor<'de> for RpcVisitor { 962 | type Value = NvimMessage; 963 | 964 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 965 | formatter.write_str("seq (tag, [msgid], method, params)") 966 | } 967 | 968 | fn visit_seq(self, mut seq: V) -> Result 969 | where 970 | V: SeqAccess<'de>, 971 | { 972 | use NvimMessage::*; 973 | 974 | let tag: i64 = seq 975 | .next_element()? 976 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 977 | if tag == 0 { 978 | let msgid = seq 979 | .next_element()? 980 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 981 | let method: String = seq 982 | .next_element()? 983 | .ok_or_else(|| de::Error::invalid_length(2, &self))?; 984 | let params: Value = seq 985 | .next_element()? 986 | .ok_or_else(|| de::Error::invalid_length(3, &self))?; 987 | 988 | Ok(RpcRequest { 989 | msgid, 990 | method, 991 | params, 992 | }) 993 | } else if tag == 1 { 994 | let msgid = seq 995 | .next_element()? 996 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 997 | let error: Value = seq 998 | .next_element()? 999 | .ok_or_else(|| de::Error::invalid_length(2, &self))?; 1000 | let result: Value = seq 1001 | .next_element()? 1002 | .ok_or_else(|| de::Error::invalid_length(3, &self))?; 1003 | 1004 | Ok(RpcResponse { 1005 | msgid, 1006 | error, 1007 | result, 1008 | }) 1009 | } else if tag == 2 { 1010 | let method: String = seq 1011 | .next_element()? 1012 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 1013 | let params: Value = seq 1014 | .next_element()? 1015 | .ok_or_else(|| de::Error::invalid_length(2, &self))?; 1016 | 1017 | Ok(RpcNotification { method, params }) 1018 | } else { 1019 | Err(de::Error::invalid_value( 1020 | de::Unexpected::Other("invalid tag"), 1021 | &self, 1022 | )) 1023 | } 1024 | } 1025 | } 1026 | 1027 | impl<'de> Deserialize<'de> for NvimMessage { 1028 | fn deserialize(deserializer: D) -> Result 1029 | where 1030 | D: serde::Deserializer<'de>, 1031 | { 1032 | deserializer.deserialize_seq(RpcVisitor) 1033 | } 1034 | } 1035 | 1036 | #[cfg(test)] 1037 | mod tests { 1038 | use super::*; 1039 | use lsp_types::{Position, Range, TextEdit}; 1040 | 1041 | fn mock_buf_mapper() -> Mutex> { 1042 | Mutex::new(BiMap::new()) 1043 | } 1044 | 1045 | #[test] 1046 | fn test_apply_edits() { 1047 | let original_content = String::from("fn a() {\n print!(\"hello\");\n}"); 1048 | let lines = original_content 1049 | .split("\n") 1050 | .map(String::from) 1051 | .collect::>(); 1052 | let edits = vec![ 1053 | TextEdit::new( 1054 | Range::new(Position::new(0, 3), Position::new(0, 5)), 1055 | String::from(""), 1056 | ), 1057 | TextEdit::new( 1058 | Range::new(Position::new(1, 0), Position::new(1, 0)), 1059 | String::from(" "), 1060 | ), 1061 | ]; 1062 | let editted_content = apply_edits(&lines, &edits); 1063 | let expected_content = String::from("fn a() {\n print!(\"hello\");\n}"); 1064 | assert_eq!(editted_content, expected_content); 1065 | } 1066 | 1067 | #[test] 1068 | fn test_deserialize_ls_config() { 1069 | let value = Value::Map(vec![ 1070 | ( 1071 | Value::from("root_markers"), 1072 | Value::from(vec![Value::from("Cargo.lock")]), 1073 | ), 1074 | ( 1075 | Value::from("command"), 1076 | Value::from(vec![Value::from("rustup"), Value::from("run")]), 1077 | ), 1078 | (Value::from("indentation"), Value::from(4)), 1079 | (Value::from("indentation_with_space"), Value::from(true)), 1080 | ]); 1081 | 1082 | let ls_config: LsConfig = Deserialize::deserialize(value).unwrap(); 1083 | let expected = LsConfig { 1084 | command: vec!["rustup".to_owned(), "run".to_owned()], 1085 | root_markers: vec!["Cargo.lock".to_owned()], 1086 | indentation: 4, 1087 | indentation_with_space: true, 1088 | }; 1089 | 1090 | assert_eq!(expected, ls_config); 1091 | } 1092 | 1093 | #[test] 1094 | fn test_deserialize_start_lang_server_params() { 1095 | let start_lang_server_msg = NvimMessage::RpcNotification { 1096 | method: String::from("start_lang_server"), 1097 | params: Value::from(vec![ 1098 | Value::from("rust"), 1099 | Value::Map(vec![ 1100 | ( 1101 | Value::from("root_markers"), 1102 | Value::from(vec![Value::from("Cargo.lock")]), 1103 | ), 1104 | ( 1105 | Value::from("command"), 1106 | Value::from(vec![Value::from("rustup")]), 1107 | ), 1108 | (Value::from("indentation"), Value::from(4)), 1109 | (Value::from("indentation_with_space"), Value::from(true)), 1110 | ]), 1111 | Value::from("/abc"), 1112 | ]), 1113 | }; 1114 | let expected = Event::StartServer { 1115 | lang_id: String::from("rust"), 1116 | config: LsConfig { 1117 | command: vec![String::from("rustup")], 1118 | root_markers: vec![String::from("Cargo.lock")], 1119 | indentation: 4, 1120 | indentation_with_space: true, 1121 | }, 1122 | cur_path: String::from("/abc"), 1123 | }; 1124 | let buf_mapper = mock_buf_mapper(); 1125 | assert_eq!( 1126 | expected, 1127 | to_event(start_lang_server_msg, &buf_mapper).unwrap() 1128 | ); 1129 | } 1130 | 1131 | fn to_text_document(s: &str) -> Option { 1132 | let uri = Url::from_file_path(s).ok()?; 1133 | Some(TextDocumentIdentifier::new(uri)) 1134 | } 1135 | 1136 | #[test] 1137 | fn test_deserialize_inlay_hints_params() { 1138 | #[cfg(not(target_os = "windows"))] 1139 | let file_path = "/a/b/c/d"; 1140 | #[cfg(target_os = "windows")] 1141 | let file_path = r#"C:\\a\b\d"#; 1142 | 1143 | let inlay_hints_msg = NvimMessage::RpcNotification { 1144 | method: String::from("inlay_hints"), 1145 | params: Value::from(vec![Value::from(1), Value::from(file_path)]), 1146 | }; 1147 | let text_document = to_text_document(file_path).unwrap(); 1148 | let expected = Event::InlayHints { text_document }; 1149 | let buf_mapper = mock_buf_mapper(); 1150 | 1151 | assert_eq!(expected, to_event(inlay_hints_msg, &buf_mapper).unwrap()); 1152 | } 1153 | 1154 | #[test] 1155 | fn test_deserialize_buffer_handler() { 1156 | let v = Value::Ext(0, vec![13]); 1157 | let handle: NvimHandle = Deserialize::deserialize(v).unwrap(); 1158 | 1159 | assert_eq!(NvimHandle::Buffer(BufferHandler(13)), handle); 1160 | } 1161 | } 1162 | -------------------------------------------------------------------------------- /src/rpc.rs: -------------------------------------------------------------------------------- 1 | use log; 2 | use std::{ 3 | io::{BufRead, BufReader, Read, Write}, 4 | thread, 5 | }; 6 | 7 | use crossbeam::channel::{bounded, Receiver, Sender}; 8 | 9 | pub trait Message: Sized + Send + 'static { 10 | fn read(r: &mut impl BufRead) -> Result, RpcError>; 11 | fn write(self, w: &mut impl Write) -> Result<(), RpcError>; 12 | fn is_exit(&self) -> bool; 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum RpcError { 17 | Deserialize(String), 18 | Read(String), 19 | Write(String), 20 | Serialize(String), 21 | } 22 | 23 | impl std::fmt::Display for RpcError { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | match self { 26 | RpcError::Deserialize(e) => write!(f, "Deserialize Error: {}", e), 27 | RpcError::Serialize(e) => write!(f, "Serialize Error: {}", e), 28 | RpcError::Write(e) => write!(f, "Write Error: {}", e), 29 | RpcError::Read(e) => write!(f, "Read Error: {}", e), 30 | } 31 | } 32 | } 33 | 34 | impl std::convert::From for String { 35 | fn from(c: RpcError) -> Self { 36 | match c { 37 | RpcError::Deserialize(e) => format!("Deserialize Error: {}", e), 38 | RpcError::Serialize(e) => format!("Serialize Error: {}", e), 39 | RpcError::Write(e) => format!("Write Error: {}", e), 40 | RpcError::Read(e) => format!("Read Error: {}", e), 41 | } 42 | } 43 | } 44 | 45 | impl std::error::Error for RpcError {} 46 | 47 | #[derive(Debug)] 48 | pub struct Threads { 49 | reader: thread::JoinHandle>, 50 | writer: thread::JoinHandle>, 51 | } 52 | 53 | impl Threads { 54 | pub fn join(self) -> Result<(), String> { 55 | match self.reader.join() { 56 | Ok(r) => r?, 57 | Err(_) => Err("reader panicked")?, 58 | }; 59 | match self.writer.join() { 60 | Ok(r) => r?, 61 | Err(_) => Err("writer panicked")?, 62 | }; 63 | Ok(()) 64 | } 65 | } 66 | 67 | #[derive(Debug)] 68 | pub struct Client 69 | where 70 | M: Message, 71 | { 72 | pub sender: Sender, 73 | pub receiver: Receiver, 74 | threads: Threads, 75 | } 76 | 77 | impl Client { 78 | pub fn new(get_reader: RF, get_writer: WF) -> Self 79 | where 80 | RF: FnOnce() -> R, 81 | WF: FnOnce() -> W, 82 | R: Read + Sized, 83 | W: Write + Sized, 84 | RF: Send + 'static, 85 | WF: Send + 'static, 86 | { 87 | let (writer_sender, writer_receiver) = bounded::(16); 88 | let writer = thread::spawn(move || { 89 | let mut io_writer = get_writer(); 90 | writer_receiver.into_iter().for_each(|msg| { 91 | if let Err(e) = msg.write(&mut io_writer) { 92 | log::error!("Failed to write message {}", e); 93 | } 94 | }); 95 | Ok(()) 96 | }); 97 | 98 | let (reader_sender, reader_receiver) = bounded::(16); 99 | let reader = thread::spawn(move || { 100 | let io_reader = get_reader(); 101 | let mut buf_read = BufReader::new(io_reader); 102 | loop { 103 | match M::read(&mut buf_read) { 104 | Ok(Some(msg)) => { 105 | let is_exit = msg.is_exit(); 106 | 107 | reader_sender.send(msg).unwrap(); 108 | 109 | if is_exit { 110 | break; 111 | } 112 | } 113 | Ok(None) => continue, 114 | Err(e) => log::error!("Error reading message: {:?}", e), 115 | } 116 | } 117 | Ok(()) 118 | }); 119 | let threads = Threads { reader, writer }; 120 | 121 | Client { 122 | sender: writer_sender, 123 | receiver: reader_receiver, 124 | threads, 125 | } 126 | } 127 | 128 | fn close(self) -> Result<(), String> { 129 | self.threads.join() 130 | } 131 | } 132 | --------------------------------------------------------------------------------