├── .github └── workflows │ └── CI.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── pyproject.toml └── src ├── config.rs └── main.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | tags: 6 | - 'v*' 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | name: CI 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | test: 18 | name: Test 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os: 23 | - ubuntu-latest 24 | - macos-latest 25 | - windows-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: dtolnay/rust-toolchain@stable 29 | - uses: Swatinem/rust-cache@v2 30 | - run: cargo test 31 | 32 | build: 33 | name: Build on ${{ matrix.platform.os }} for ${{ matrix.platform.target }} 34 | runs-on: ${{ matrix.platform.os }} 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | platform: 39 | - os: ubuntu-latest 40 | target: x86_64 41 | maturin-args: --sdist 42 | - os: ubuntu-latest 43 | target: i686 44 | - os: ubuntu-latest 45 | target: aarch64 46 | - os: macos-latest 47 | target: x86_64 48 | - os: macos-latest 49 | target: aarch64 50 | - os: windows-latest 51 | target: x86_64 52 | steps: 53 | - uses: actions/checkout@v3 54 | - uses: dtolnay/rust-toolchain@stable 55 | - uses: PyO3/maturin-action@v1 56 | with: 57 | target: ${{ matrix.platform.target }} 58 | manylinux: auto 59 | args: -b bin --release -o dist ${{ matrix.platform.maturin-args || '' }} 60 | - name: Upload wheel artifacts 61 | uses: actions/upload-artifact@v3 62 | with: 63 | name: wheels 64 | path: dist 65 | 66 | release-pypi: 67 | permissions: 68 | # Used to sign the release's artifacts with sigstore-python. 69 | id-token: write 70 | # Used to upload release artifacts. 71 | contents: write 72 | name: Publish to PyPI 73 | runs-on: ubuntu-latest 74 | if: "startsWith(github.ref, 'refs/tags/')" 75 | needs: build 76 | steps: 77 | - uses: actions/download-artifact@v3 78 | with: 79 | name: wheels 80 | - uses: actions/setup-python@v4 81 | with: 82 | python-version: '3.11' 83 | - name: Publish 84 | env: 85 | MATURIN_PASSWORD: ${{ secrets.MATURIN_PASSWORD }} 86 | run: | 87 | set -ex 88 | pip install maturin 89 | maturin upload -u __token__ --skip-existing * 90 | - name: Sigstore Sign 91 | uses: sigstore/gh-action-sigstore-python@v0.0.11 92 | with: 93 | inputs: ./*.whl 94 | upload-signing-artifacts: true 95 | - name: Release signing artifacts 96 | uses: softprops/action-gh-release@v1 97 | with: 98 | files: | 99 | *.whl 100 | *.sig 101 | *.crt 102 | prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} 103 | generate_release_notes: true 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /dist 3 | /config.toml 4 | *.log -------------------------------------------------------------------------------- /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 = "anyhow" 7 | version = "1.0.68" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 10 | 11 | [[package]] 12 | name = "async-stream" 13 | version = "0.3.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" 16 | dependencies = [ 17 | "async-stream-impl", 18 | "futures-core", 19 | ] 20 | 21 | [[package]] 22 | name = "async-stream-impl" 23 | version = "0.3.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" 26 | dependencies = [ 27 | "proc-macro2", 28 | "quote", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "autocfg" 34 | version = "1.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 37 | 38 | [[package]] 39 | name = "bitflags" 40 | version = "1.3.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 43 | 44 | [[package]] 45 | name = "bytes" 46 | version = "1.3.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 49 | 50 | [[package]] 51 | name = "cc" 52 | version = "1.0.78" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 55 | 56 | [[package]] 57 | name = "cfg-if" 58 | version = "1.0.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 61 | 62 | [[package]] 63 | name = "clap" 64 | version = "4.1.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" 67 | dependencies = [ 68 | "bitflags", 69 | "clap_derive", 70 | "clap_lex", 71 | "is-terminal", 72 | "once_cell", 73 | "strsim", 74 | "termcolor", 75 | ] 76 | 77 | [[package]] 78 | name = "clap_derive" 79 | version = "4.1.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 82 | dependencies = [ 83 | "heck", 84 | "proc-macro-error", 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "clap_lex" 92 | version = "0.3.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 95 | dependencies = [ 96 | "os_str_bytes", 97 | ] 98 | 99 | [[package]] 100 | name = "crossbeam-channel" 101 | version = "0.5.6" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 104 | dependencies = [ 105 | "cfg-if", 106 | "crossbeam-utils", 107 | ] 108 | 109 | [[package]] 110 | name = "crossbeam-utils" 111 | version = "0.8.14" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 114 | dependencies = [ 115 | "cfg-if", 116 | ] 117 | 118 | [[package]] 119 | name = "either" 120 | version = "1.8.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 123 | 124 | [[package]] 125 | name = "errno" 126 | version = "0.2.8" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 129 | dependencies = [ 130 | "errno-dragonfly", 131 | "libc", 132 | "winapi", 133 | ] 134 | 135 | [[package]] 136 | name = "errno-dragonfly" 137 | version = "0.1.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 140 | dependencies = [ 141 | "cc", 142 | "libc", 143 | ] 144 | 145 | [[package]] 146 | name = "futures-core" 147 | version = "0.3.25" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 150 | 151 | [[package]] 152 | name = "hashbrown" 153 | version = "0.12.3" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 156 | 157 | [[package]] 158 | name = "heck" 159 | version = "0.4.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 162 | 163 | [[package]] 164 | name = "hermit-abi" 165 | version = "0.2.6" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 168 | dependencies = [ 169 | "libc", 170 | ] 171 | 172 | [[package]] 173 | name = "indexmap" 174 | version = "1.9.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 177 | dependencies = [ 178 | "autocfg", 179 | "hashbrown", 180 | ] 181 | 182 | [[package]] 183 | name = "io-lifetimes" 184 | version = "1.0.4" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" 187 | dependencies = [ 188 | "libc", 189 | "windows-sys", 190 | ] 191 | 192 | [[package]] 193 | name = "is-terminal" 194 | version = "0.4.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 197 | dependencies = [ 198 | "hermit-abi", 199 | "io-lifetimes", 200 | "rustix", 201 | "windows-sys", 202 | ] 203 | 204 | [[package]] 205 | name = "itertools" 206 | version = "0.10.5" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 209 | dependencies = [ 210 | "either", 211 | ] 212 | 213 | [[package]] 214 | name = "itoa" 215 | version = "1.0.5" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 218 | 219 | [[package]] 220 | name = "lazy_static" 221 | version = "1.4.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 224 | 225 | [[package]] 226 | name = "libc" 227 | version = "0.2.139" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 230 | 231 | [[package]] 232 | name = "linux-raw-sys" 233 | version = "0.1.4" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 236 | 237 | [[package]] 238 | name = "lock_api" 239 | version = "0.4.9" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 242 | dependencies = [ 243 | "autocfg", 244 | "scopeguard", 245 | ] 246 | 247 | [[package]] 248 | name = "log" 249 | version = "0.4.17" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 252 | dependencies = [ 253 | "cfg-if", 254 | ] 255 | 256 | [[package]] 257 | name = "matchers" 258 | version = "0.1.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 261 | dependencies = [ 262 | "regex-automata", 263 | ] 264 | 265 | [[package]] 266 | name = "memchr" 267 | version = "2.5.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 270 | 271 | [[package]] 272 | name = "mio" 273 | version = "0.8.5" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 276 | dependencies = [ 277 | "libc", 278 | "log", 279 | "wasi", 280 | "windows-sys", 281 | ] 282 | 283 | [[package]] 284 | name = "multi-lsp-proxy" 285 | version = "0.1.3" 286 | dependencies = [ 287 | "anyhow", 288 | "async-stream", 289 | "clap", 290 | "serde", 291 | "serde_json", 292 | "tokio", 293 | "tokio-stream", 294 | "toml_edit", 295 | "tracing", 296 | "tracing-appender", 297 | "tracing-subscriber", 298 | ] 299 | 300 | [[package]] 301 | name = "nom8" 302 | version = "0.2.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" 305 | dependencies = [ 306 | "memchr", 307 | ] 308 | 309 | [[package]] 310 | name = "nu-ansi-term" 311 | version = "0.46.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 314 | dependencies = [ 315 | "overload", 316 | "winapi", 317 | ] 318 | 319 | [[package]] 320 | name = "num_cpus" 321 | version = "1.15.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 324 | dependencies = [ 325 | "hermit-abi", 326 | "libc", 327 | ] 328 | 329 | [[package]] 330 | name = "once_cell" 331 | version = "1.17.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 334 | 335 | [[package]] 336 | name = "os_str_bytes" 337 | version = "6.4.1" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 340 | 341 | [[package]] 342 | name = "overload" 343 | version = "0.1.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 346 | 347 | [[package]] 348 | name = "parking_lot" 349 | version = "0.12.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 352 | dependencies = [ 353 | "lock_api", 354 | "parking_lot_core", 355 | ] 356 | 357 | [[package]] 358 | name = "parking_lot_core" 359 | version = "0.9.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" 362 | dependencies = [ 363 | "cfg-if", 364 | "libc", 365 | "redox_syscall", 366 | "smallvec", 367 | "windows-sys", 368 | ] 369 | 370 | [[package]] 371 | name = "pin-project-lite" 372 | version = "0.2.9" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 375 | 376 | [[package]] 377 | name = "proc-macro-error" 378 | version = "1.0.4" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 381 | dependencies = [ 382 | "proc-macro-error-attr", 383 | "proc-macro2", 384 | "quote", 385 | "syn", 386 | "version_check", 387 | ] 388 | 389 | [[package]] 390 | name = "proc-macro-error-attr" 391 | version = "1.0.4" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 394 | dependencies = [ 395 | "proc-macro2", 396 | "quote", 397 | "version_check", 398 | ] 399 | 400 | [[package]] 401 | name = "proc-macro2" 402 | version = "1.0.49" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 405 | dependencies = [ 406 | "unicode-ident", 407 | ] 408 | 409 | [[package]] 410 | name = "quote" 411 | version = "1.0.23" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 414 | dependencies = [ 415 | "proc-macro2", 416 | ] 417 | 418 | [[package]] 419 | name = "redox_syscall" 420 | version = "0.2.16" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 423 | dependencies = [ 424 | "bitflags", 425 | ] 426 | 427 | [[package]] 428 | name = "regex" 429 | version = "1.7.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 432 | dependencies = [ 433 | "regex-syntax", 434 | ] 435 | 436 | [[package]] 437 | name = "regex-automata" 438 | version = "0.1.10" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 441 | dependencies = [ 442 | "regex-syntax", 443 | ] 444 | 445 | [[package]] 446 | name = "regex-syntax" 447 | version = "0.6.28" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 450 | 451 | [[package]] 452 | name = "rustix" 453 | version = "0.36.6" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" 456 | dependencies = [ 457 | "bitflags", 458 | "errno", 459 | "io-lifetimes", 460 | "libc", 461 | "linux-raw-sys", 462 | "windows-sys", 463 | ] 464 | 465 | [[package]] 466 | name = "ryu" 467 | version = "1.0.12" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 470 | 471 | [[package]] 472 | name = "scopeguard" 473 | version = "1.1.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 476 | 477 | [[package]] 478 | name = "serde" 479 | version = "1.0.152" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 482 | dependencies = [ 483 | "serde_derive", 484 | ] 485 | 486 | [[package]] 487 | name = "serde_derive" 488 | version = "1.0.152" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 491 | dependencies = [ 492 | "proc-macro2", 493 | "quote", 494 | "syn", 495 | ] 496 | 497 | [[package]] 498 | name = "serde_json" 499 | version = "1.0.91" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 502 | dependencies = [ 503 | "itoa", 504 | "ryu", 505 | "serde", 506 | ] 507 | 508 | [[package]] 509 | name = "sharded-slab" 510 | version = "0.1.4" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 513 | dependencies = [ 514 | "lazy_static", 515 | ] 516 | 517 | [[package]] 518 | name = "signal-hook-registry" 519 | version = "1.4.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 522 | dependencies = [ 523 | "libc", 524 | ] 525 | 526 | [[package]] 527 | name = "smallvec" 528 | version = "1.10.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 531 | 532 | [[package]] 533 | name = "socket2" 534 | version = "0.4.7" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 537 | dependencies = [ 538 | "libc", 539 | "winapi", 540 | ] 541 | 542 | [[package]] 543 | name = "strsim" 544 | version = "0.10.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 547 | 548 | [[package]] 549 | name = "syn" 550 | version = "1.0.107" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 553 | dependencies = [ 554 | "proc-macro2", 555 | "quote", 556 | "unicode-ident", 557 | ] 558 | 559 | [[package]] 560 | name = "termcolor" 561 | version = "1.1.3" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 564 | dependencies = [ 565 | "winapi-util", 566 | ] 567 | 568 | [[package]] 569 | name = "thread_local" 570 | version = "1.1.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 573 | dependencies = [ 574 | "once_cell", 575 | ] 576 | 577 | [[package]] 578 | name = "time" 579 | version = "0.3.17" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 582 | dependencies = [ 583 | "itoa", 584 | "serde", 585 | "time-core", 586 | "time-macros", 587 | ] 588 | 589 | [[package]] 590 | name = "time-core" 591 | version = "0.1.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 594 | 595 | [[package]] 596 | name = "time-macros" 597 | version = "0.2.6" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 600 | dependencies = [ 601 | "time-core", 602 | ] 603 | 604 | [[package]] 605 | name = "tokio" 606 | version = "1.24.1" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" 609 | dependencies = [ 610 | "autocfg", 611 | "bytes", 612 | "libc", 613 | "memchr", 614 | "mio", 615 | "num_cpus", 616 | "parking_lot", 617 | "pin-project-lite", 618 | "signal-hook-registry", 619 | "socket2", 620 | "tokio-macros", 621 | "windows-sys", 622 | ] 623 | 624 | [[package]] 625 | name = "tokio-macros" 626 | version = "1.8.2" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 629 | dependencies = [ 630 | "proc-macro2", 631 | "quote", 632 | "syn", 633 | ] 634 | 635 | [[package]] 636 | name = "tokio-stream" 637 | version = "0.1.11" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" 640 | dependencies = [ 641 | "futures-core", 642 | "pin-project-lite", 643 | "tokio", 644 | ] 645 | 646 | [[package]] 647 | name = "toml_datetime" 648 | version = "0.5.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "808b51e57d0ef8f71115d8f3a01e7d3750d01c79cac4b3eda910f4389fdf92fd" 651 | dependencies = [ 652 | "serde", 653 | ] 654 | 655 | [[package]] 656 | name = "toml_edit" 657 | version = "0.17.1" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "a34cc558345efd7e88b9eda9626df2138b80bb46a7606f695e751c892bc7dac6" 660 | dependencies = [ 661 | "indexmap", 662 | "itertools", 663 | "nom8", 664 | "serde", 665 | "toml_datetime", 666 | ] 667 | 668 | [[package]] 669 | name = "tracing" 670 | version = "0.1.37" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 673 | dependencies = [ 674 | "cfg-if", 675 | "pin-project-lite", 676 | "tracing-attributes", 677 | "tracing-core", 678 | ] 679 | 680 | [[package]] 681 | name = "tracing-appender" 682 | version = "0.2.2" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" 685 | dependencies = [ 686 | "crossbeam-channel", 687 | "time", 688 | "tracing-subscriber", 689 | ] 690 | 691 | [[package]] 692 | name = "tracing-attributes" 693 | version = "0.1.23" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 696 | dependencies = [ 697 | "proc-macro2", 698 | "quote", 699 | "syn", 700 | ] 701 | 702 | [[package]] 703 | name = "tracing-core" 704 | version = "0.1.30" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 707 | dependencies = [ 708 | "once_cell", 709 | "valuable", 710 | ] 711 | 712 | [[package]] 713 | name = "tracing-log" 714 | version = "0.1.3" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 717 | dependencies = [ 718 | "lazy_static", 719 | "log", 720 | "tracing-core", 721 | ] 722 | 723 | [[package]] 724 | name = "tracing-subscriber" 725 | version = "0.3.16" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 728 | dependencies = [ 729 | "matchers", 730 | "nu-ansi-term", 731 | "once_cell", 732 | "regex", 733 | "sharded-slab", 734 | "smallvec", 735 | "thread_local", 736 | "tracing", 737 | "tracing-core", 738 | "tracing-log", 739 | ] 740 | 741 | [[package]] 742 | name = "unicode-ident" 743 | version = "1.0.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 746 | 747 | [[package]] 748 | name = "valuable" 749 | version = "0.1.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 752 | 753 | [[package]] 754 | name = "version_check" 755 | version = "0.9.4" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 758 | 759 | [[package]] 760 | name = "wasi" 761 | version = "0.11.0+wasi-snapshot-preview1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 764 | 765 | [[package]] 766 | name = "winapi" 767 | version = "0.3.9" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 770 | dependencies = [ 771 | "winapi-i686-pc-windows-gnu", 772 | "winapi-x86_64-pc-windows-gnu", 773 | ] 774 | 775 | [[package]] 776 | name = "winapi-i686-pc-windows-gnu" 777 | version = "0.4.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 780 | 781 | [[package]] 782 | name = "winapi-util" 783 | version = "0.1.5" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 786 | dependencies = [ 787 | "winapi", 788 | ] 789 | 790 | [[package]] 791 | name = "winapi-x86_64-pc-windows-gnu" 792 | version = "0.4.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 795 | 796 | [[package]] 797 | name = "windows-sys" 798 | version = "0.42.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 801 | dependencies = [ 802 | "windows_aarch64_gnullvm", 803 | "windows_aarch64_msvc", 804 | "windows_i686_gnu", 805 | "windows_i686_msvc", 806 | "windows_x86_64_gnu", 807 | "windows_x86_64_gnullvm", 808 | "windows_x86_64_msvc", 809 | ] 810 | 811 | [[package]] 812 | name = "windows_aarch64_gnullvm" 813 | version = "0.42.1" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 816 | 817 | [[package]] 818 | name = "windows_aarch64_msvc" 819 | version = "0.42.1" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 822 | 823 | [[package]] 824 | name = "windows_i686_gnu" 825 | version = "0.42.1" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 828 | 829 | [[package]] 830 | name = "windows_i686_msvc" 831 | version = "0.42.1" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 834 | 835 | [[package]] 836 | name = "windows_x86_64_gnu" 837 | version = "0.42.1" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 840 | 841 | [[package]] 842 | name = "windows_x86_64_gnullvm" 843 | version = "0.42.1" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 846 | 847 | [[package]] 848 | name = "windows_x86_64_msvc" 849 | version = "0.42.1" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 852 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multi-lsp-proxy" 3 | version = "0.1.3" 4 | edition = "2021" 5 | description = "A LSP Proxy to multiple language servers" 6 | license = "MIT" 7 | repository = "https://github.com/messense/multi-lsp-proxy.git" 8 | readme = "README.md" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1.0.68" 14 | async-stream = "0.3.3" 15 | clap = { version = "4.1.1", features = ["derive"] } 16 | serde = { version = "1.0.152", features = ["derive"] } 17 | serde_json = "1.0.91" 18 | tokio = { version = "1.24.1", features = ["full"] } 19 | tokio-stream = "0.1.11" 20 | toml_edit = { version = "0.17.1", features = ["easy", "serde"] } 21 | tracing = "0.1.37" 22 | tracing-appender = "0.2.2" 23 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-present Messense Lv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multi-lsp-proxy 2 | 3 | [![GitHub Actions](https://github.com/messense/multi-lsp-proxy/workflows/CI/badge.svg)](https://github.com/messense/multi-lsp-proxy/actions?query=workflow%3ACI) 4 | [![PyPI](https://img.shields.io/pypi/v/multi-lsp-proxy.svg)](https://pypi.org/project/multi-lsp-proxy) 5 | [![Crates.io](https://img.shields.io/crates/v/multi-lsp-proxy.svg?logo=rust)](https://crates.io/crates/multi-lsp-proxy) 6 | 7 | A **barely working** LSP Proxy to multiple language servers, to use multiple LSPs per language in 8 | editors that doesn't support multiple LSPs per language natively like Helix (version 22.12). 9 | 10 | ## Installation 11 | 12 | Install with [pipx](https://github.com/pypa/pipx/) is recommended: 13 | 14 | ```bash 15 | pipx install multi-lsp-proxy 16 | ``` 17 | 18 | Pip also works: 19 | 20 | ```bash 21 | pip install multi-lsp-proxy 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```bash 27 | Usage: multi-lsp-proxy [OPTIONS] --config 28 | 29 | Options: 30 | -c, --config Configuration file path 31 | -l, --language Select language servers by programming language name 32 | -h, --help Print help 33 | -V, --version Print version 34 | ``` 35 | 36 | To use with Helix, set the `language-server` option in `languages.toml`, 37 | below is an example for Python that enables both `pyright-langserver` and `ruff-lsp`: 38 | 39 | ```toml 40 | # Helix languages.toml file 41 | [[language]] 42 | name = "python" 43 | scope = "source.python" 44 | injection-regex = "python" 45 | file-types = ["py", "pyi"] 46 | shebangs = ["python"] 47 | roots = ["pyproject.toml", "setup.py", "Poetry.lock"] 48 | comment-token = "#" 49 | language-server = { command = "multi-lsp-proxy", args = ["--config", "/path/to/multi-lsp-config.toml"] } 50 | auto-format = false 51 | indent = { tab-width = 4, unit = " " } 52 | config = {} 53 | ``` 54 | 55 | and configure multi-lsp-proxy in `multi-lsp-proxy.toml` 56 | 57 | ```toml 58 | log-file = "/tmp/multi-lsp-proxy.log" 59 | 60 | [[language]] 61 | name = "python" 62 | command = "pyright-langserver" 63 | args = ["--stdio"] 64 | 65 | [[language]] 66 | name = "python" 67 | command = "ruff-lsp" 68 | ``` 69 | 70 | ## License 71 | 72 | This work is released under the MIT license. A copy of the license is provided in the [LICENSE](./LICENSE) file. 73 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=0.14,<0.15"] 3 | build-backend = "maturin" 4 | 5 | [tool.maturin] 6 | bindings = "bin" 7 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use serde::Deserialize; 4 | 5 | #[derive(Debug, Clone, Deserialize)] 6 | #[serde(rename_all = "kebab-case")] 7 | pub struct LspConfig { 8 | pub log_file: Option, 9 | #[serde(rename = "language")] 10 | pub languages: Vec, 11 | } 12 | 13 | #[derive(Debug, Clone, Deserialize)] 14 | #[serde(rename_all = "kebab-case")] 15 | pub struct Language { 16 | pub name: String, 17 | pub command: PathBuf, 18 | #[serde(default)] 19 | pub args: Vec, 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn test_parse_config() { 28 | let config = r#" 29 | [[language]] 30 | name = "python" 31 | command = "pylsp" 32 | 33 | [[language]] 34 | name = "python" 35 | command = "ruff-lsp" 36 | "#; 37 | 38 | let conf: LspConfig = toml_edit::easy::from_str(config).unwrap(); 39 | assert_eq!(conf.languages.len(), 2); 40 | assert_eq!(conf.languages[0].name, "python"); 41 | assert_eq!(conf.languages[0].command, PathBuf::from("pylsp")); 42 | assert_eq!(conf.languages[0].args.len(), 0); 43 | assert_eq!(conf.languages[1].name, "python"); 44 | assert_eq!(conf.languages[1].command, PathBuf::from("ruff-lsp")); 45 | assert_eq!(conf.languages[1].args.len(), 0); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | use std::pin::Pin; 4 | use std::process::Stdio; 5 | 6 | use anyhow::{bail, Context, Result}; 7 | use clap::Parser; 8 | use serde_json::Value; 9 | use tokio::{ 10 | io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, 11 | process::{ChildStdin, ChildStdout, Command}, 12 | sync::{broadcast, mpsc}, 13 | }; 14 | use tokio_stream::{Stream, StreamExt, StreamMap}; 15 | use tracing::{debug, info, trace}; 16 | use tracing_subscriber::filter::{EnvFilter, LevelFilter}; 17 | 18 | use self::config::LspConfig; 19 | 20 | mod config; 21 | 22 | async fn read_content_length(reader: &mut BufReader) -> Result 23 | where 24 | BufReader: AsyncBufReadExt, 25 | T: Unpin, 26 | { 27 | let mut content_length = 0; 28 | loop { 29 | let mut line = String::new(); 30 | reader.read_line(&mut line).await?; 31 | trace!("read line: {}", line); 32 | if let Some(content) = line.strip_prefix("Content-Length: ") { 33 | content_length = content 34 | .trim() 35 | .parse() 36 | .context("Failed to parse Content-Length")?; 37 | } else if line.strip_prefix("Content-Type: ").is_some() { 38 | // ignored. 39 | } else if line == "\r\n" { 40 | break; 41 | } else { 42 | bail!("Failed to get Content-Length from LSP data.") 43 | } 44 | } 45 | Ok(content_length) 46 | } 47 | 48 | async fn read_message(reader: &mut BufReader) -> Result 49 | where 50 | BufReader: AsyncBufReadExt, 51 | T: Unpin, 52 | { 53 | let content_length = read_content_length(reader).await?; 54 | let mut body = vec![0u8; content_length]; 55 | reader.read_exact(&mut body).await.unwrap(); 56 | trace!("read body: {}", String::from_utf8_lossy(&body)); 57 | serde_json::from_slice(&body).context("Failed to parse input as LSP data") 58 | } 59 | 60 | async fn proxy_stdin(mut stdin: ChildStdin, mut input: broadcast::Receiver) { 61 | while let Ok(message) = input.recv().await { 62 | stdin 63 | .write_all(format!("Content-Length: {}\r\n\r\n", message.len()).as_bytes()) 64 | .await 65 | .unwrap(); 66 | stdin.write_all(message.as_bytes()).await.unwrap(); 67 | } 68 | } 69 | 70 | async fn proxy_stdout(mut stdout: BufReader, tx: mpsc::Sender) { 71 | loop { 72 | let message = read_message(&mut stdout).await.unwrap(); 73 | tx.send(message).await.unwrap(); 74 | } 75 | } 76 | 77 | async fn run(config: LspConfig) -> Result<()> { 78 | // keep tracing_appender guard alive 79 | let mut _tracing_guard = None; 80 | if let Some(log_file) = config.log_file.as_ref() { 81 | // setup tracing 82 | let directory = log_file.parent().unwrap(); 83 | let file_name = log_file.file_name().unwrap(); 84 | let file_appender = tracing_appender::rolling::never(directory, file_name); 85 | let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); 86 | _tracing_guard = Some(guard); 87 | 88 | let env_filter = EnvFilter::builder() 89 | .with_default_directive(LevelFilter::DEBUG.into()) 90 | .from_env_lossy(); 91 | tracing_subscriber::fmt() 92 | .with_writer(non_blocking) 93 | .with_env_filter(env_filter) 94 | .init(); 95 | } 96 | 97 | let (tx, _rx) = broadcast::channel(100); 98 | let mut child_processes = Vec::new(); 99 | let mut child_rxs = Vec::with_capacity(config.languages.len()); 100 | for lang in &config.languages { 101 | // spawn LSP server command 102 | let mut cmd = Command::new(&lang.command); 103 | cmd.args(&lang.args) 104 | .stdin(Stdio::piped()) 105 | .stdout(Stdio::piped()); 106 | let mut child = cmd 107 | .spawn() 108 | .with_context(|| format!("Failed to spawn {} binary.", &lang.command.display()))?; 109 | info!("spawned {}", lang.command.display()); 110 | 111 | let child_stdin = child.stdin.take().unwrap(); 112 | let child_stdout = BufReader::new(child.stdout.take().unwrap()); 113 | 114 | let (child_tx, child_rx) = mpsc::channel(100); 115 | child_rxs.push(child_rx); 116 | 117 | let rx = tx.subscribe(); 118 | tokio::spawn(async move { 119 | proxy_stdin(child_stdin, rx).await; 120 | }); 121 | tokio::spawn(async move { proxy_stdout(child_stdout, child_tx).await }); 122 | 123 | // Keep child process alive 124 | child_processes.push(child); 125 | } 126 | 127 | // read messages from child LSPs 128 | // TODO: merge server capabilities? 129 | tokio::spawn(async move { 130 | let mut stdout = io::stdout(); 131 | let mut map = StreamMap::new(); 132 | for (key, mut rx) in child_rxs.into_iter().enumerate() { 133 | let stream = Box::pin(async_stream::stream! { 134 | while let Some(value) = rx.recv().await { 135 | yield value; 136 | } 137 | }) as Pin + Send>>; 138 | map.insert(key, stream); 139 | } 140 | while let Some((_, value)) = map.next().await { 141 | let message = serde_json::to_string(&value).unwrap(); 142 | debug!("received: {}", message); 143 | stdout 144 | .write_all(format!("Content-Length: {}\r\n\r\n", message.len()).as_bytes()) 145 | .await 146 | .unwrap(); 147 | stdout.write_all(message.as_bytes()).await.unwrap(); 148 | } 149 | }); 150 | 151 | // LSP server main loop 152 | // Read new command, send to all child LSP servers 153 | let mut stdin = BufReader::new(io::stdin()); 154 | loop { 155 | let content_length = read_content_length(&mut stdin).await?; 156 | let mut body = vec![0u8; content_length]; 157 | stdin.read_exact(&mut body).await.unwrap(); 158 | let raw = String::from_utf8(body)?; 159 | debug!(request = %raw, "incoming lsp request"); 160 | tx.send(raw.clone()).unwrap(); 161 | } 162 | } 163 | 164 | #[derive(Debug, Parser)] 165 | #[command(version)] 166 | struct Cli { 167 | /// Configuration file path 168 | #[arg(short = 'c', long)] 169 | config: PathBuf, 170 | /// Select language servers by programming language name 171 | #[arg(short = 'l', long)] 172 | language: Option, 173 | } 174 | 175 | #[tokio::main] 176 | async fn main() -> Result<()> { 177 | let cli = Cli::parse(); 178 | let config_content = fs::read_to_string(&cli.config)?; 179 | let mut lsp_config: LspConfig = toml_edit::easy::from_str(&config_content)?; 180 | if let Some(lang) = cli.language.as_deref() { 181 | lsp_config.languages.retain(|l| l.name == lang); 182 | } 183 | if lsp_config.languages.is_empty() { 184 | if let Some(lang) = cli.language.as_deref() { 185 | bail!("No language server found for {}.", lang); 186 | } 187 | bail!("No language server found."); 188 | } 189 | run(lsp_config).await?; 190 | Ok(()) 191 | } 192 | --------------------------------------------------------------------------------