├── .github └── workflows │ ├── ci.yaml │ └── future_proof.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench.rs ├── examples ├── client_builder.rs ├── client_trait.rs ├── inspector.rs ├── server_builder.rs └── server_trait.rs ├── generate_omni_trait.sh ├── src ├── client_monitor.rs ├── concurrency.rs ├── forward.rs ├── lib.rs ├── omni_trait.rs ├── omni_trait_generated.rs ├── panic.rs ├── router.rs ├── server.rs ├── stdio.rs └── tracing.rs └── tests ├── client_test_data ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── stdio.rs └── unit_test.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | schedule: 6 | - cron: '29 1 * * *' # *-*-* 01:29:00 UTC 7 | 8 | permissions: 9 | contents: read 10 | 11 | env: 12 | RUST_BACKTRACE: full 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | test: 17 | timeout-minutes: 45 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | rust: [nightly, beta, stable] 22 | os: [ubuntu-latest, macos-latest, windows-latest] 23 | name: Test ${{ matrix.rust }} on ${{ matrix.os }} 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Install Rust ${{ matrix.rust }} 30 | run: | 31 | rustup update --no-self-update beta 32 | rustup default ${{ matrix.rust }} 33 | rustup component add rust-analyzer 34 | 35 | - name: Cache dependencies 36 | uses: Swatinem/rust-cache@v2 37 | 38 | - uses: taiki-e/install-action@cargo-hack 39 | 40 | # `cargo build` doesn't pull in [dev-dependencies]. 41 | - name: Build 42 | run: cargo hack build --each-feature 43 | - name: Doc test 44 | run: cargo hack test --each-feature --doc 45 | - name: Test 46 | run: cargo hack test --each-feature --all-targets 47 | - name: Test examples 48 | run: cargo test --all-features --examples -- --ignored 49 | 50 | msrv: 51 | timeout-minutes: 15 52 | name: MSRV 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | # FIXME: MacOS' `sed` is incompatible with `gnused`. 57 | os: [ubuntu-latest, windows-latest] 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | 63 | # Sync with Cargo.toml! 64 | - name: Install Rust 65 | run: | 66 | rustup update --no-self-update 67 | rustup toolchain add nightly 68 | # Sync with Cargo.toml! 69 | rustup default 1.66 70 | 71 | - uses: taiki-e/install-action@cargo-minimal-versions 72 | - uses: taiki-e/install-action@cargo-hack 73 | 74 | - name: Build 75 | env: 76 | CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback 77 | run: cargo minimal-versions build --direct --all-features 78 | 79 | clippy: 80 | name: Clippy 81 | runs-on: ubuntu-latest 82 | timeout-minutes: 45 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v4 86 | 87 | - name: Install Rust 88 | run: | 89 | rustup update --no-self-update stable 90 | rustup default stable 91 | 92 | - run: | 93 | cargo clippy --all-targets --all-features -- -Dclippy::all 94 | 95 | docs: 96 | name: Docs 97 | runs-on: ubuntu-latest 98 | timeout-minutes: 15 99 | steps: 100 | - name: Checkout 101 | uses: actions/checkout@v4 102 | 103 | - name: Install Rust 104 | run: | 105 | rustup update --no-self-update stable 106 | rustup default stable 107 | 108 | - name: cargo doc 109 | env: 110 | RUSTDOCFLAGS: --cfg docsrs_ -Dwarnings 111 | run: cargo doc --all-features 112 | -------------------------------------------------------------------------------- /.github/workflows/future_proof.yaml: -------------------------------------------------------------------------------- 1 | name: Future proof tests 2 | on: 3 | schedule: 4 | - cron: '35 23 * * 0' # Sun *-*-* 23:35:00 UTC 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read 9 | 10 | env: 11 | RUST_BACKTRACE: full 12 | 13 | jobs: 14 | outdated: 15 | name: Outdated 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 45 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install cargo-outdated 23 | uses: dtolnay/install@cargo-outdated 24 | 25 | - name: cargo-outdated 26 | run: rm -f Cargo.lock && cargo outdated --workspace --exit-code 1 --root-deps-only --exclude=clap,anstyle,clap_lex,regex,lsp-types 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /metaModel.json 3 | /perf*.data* 4 | /flamegraph*.svg 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/). 7 | 8 | ## v0.2.2 9 | 10 | ### Changed 11 | 12 | - Bump to rustix 1 and waitpid-any 0.3. 13 | 14 | This makes it possible to read into uninitialized buffer in tokio AsyncRead 15 | impl. 16 | 17 | ### Others 18 | 19 | - Fix tests for nightly rust-analyzer. 20 | 21 | - Regenerate lockfile respecting MSRV. 22 | 23 | ## v0.2.1 24 | 25 | ### Changed 26 | 27 | - Use `workspace_folders` instead of deprecated `root_uri` in examples. 28 | - Update `thiserror` to 2. 29 | 30 | ### Others 31 | 32 | - Add backref from `AnyEvent` to `LspService::emit` in docs. 33 | 34 | ## v0.2.0 35 | 36 | ### Changed 37 | 38 | - Update `lsp-types` to 0.95.0 39 | 40 | ### Added 41 | 42 | - `lsp-types` dependency crate is now re-exported under the crate root. 43 | 44 | ## v0.1.0 45 | 46 | ### Changed 47 | 48 | - Updated `lsp-types` to 0.94.1 which comes with a few more LSP methods. 49 | They are added into `trait Language{Server,Client}` respectively 50 | (under `omni-trait` feature). 51 | 52 | - Updated many other dependencies. 53 | 54 | ### Others 55 | 56 | - Some random documentation tweak and fixes. 57 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-lsp" 3 | version = "0.2.2" 4 | edition = "2021" 5 | description = "Asynchronous Language Server Protocol (LSP) framework based on tower" 6 | keywords = ["lsp", "language-server", "tower"] 7 | categories = ["asynchronous"] 8 | license = "MIT OR Apache-2.0" 9 | repository = "https://github.com/oxalica/async-lsp" 10 | # Required features: 11 | # - std::hint::black_box 12 | rust-version = "1.66" # Sync with CI! 13 | include = [ 14 | "/Cargo.toml", 15 | "/LICENSE-APACHE", 16 | "/LICENSE-MIT", 17 | "/README.md", 18 | "/benches", 19 | "/examples", 20 | "/src", 21 | "/tests", 22 | ] 23 | 24 | [features] 25 | default = ["client-monitor", "omni-trait", "stdio", "tracing"] 26 | client-monitor = ["dep:waitpid-any", "dep:rustix"] 27 | omni-trait = [] 28 | stdio = ["dep:rustix", "rustix?/fs", "tokio?/net"] 29 | tracing = ["dep:tracing"] 30 | forward = [] 31 | 32 | [[example]] 33 | name = "client_builder" 34 | required-features = ["omni-trait", "tracing", "tokio"] 35 | 36 | [[example]] 37 | name = "client_trait" 38 | required-features = ["omni-trait", "tracing", "tokio"] 39 | 40 | [[example]] 41 | name = "server_builder" 42 | required-features = ["client-monitor", "omni-trait", "stdio", "tracing", "tokio"] 43 | 44 | [[example]] 45 | name = "server_trait" 46 | required-features = ["client-monitor", "omni-trait", "stdio", "tracing", "tokio"] 47 | 48 | [[example]] 49 | name = "inspector" 50 | required-features = ["forward", "tracing", "tokio"] 51 | 52 | [[test]] 53 | name = "unit_test" 54 | required-features = ["omni-trait", "tokio"] 55 | 56 | [[test]] 57 | name = "stdio" 58 | harness = false 59 | required-features = ["stdio", "tokio"] 60 | 61 | [[bench]] 62 | name = "bench" 63 | harness = false 64 | 65 | [dependencies] 66 | async-io = { version = "2", optional = true } 67 | futures = { version = "0.3.28", default-features = false, features = ["async-await", "std"] } 68 | # See: https://github.com/gluon-lang/lsp-types/issues/284 69 | lsp-types = "0.95.0" 70 | pin-project-lite = "0.2.9" 71 | rustix = { version = "1", optional = true } 72 | serde = { version = "1.0.159", features = ["derive"] } 73 | serde_json = "1.0.95" 74 | thiserror = "2" 75 | tokio = { version = "1.27.0", optional = true } 76 | tower-layer = "0.3.2" 77 | tower-service = "0.3.2" 78 | tracing = { version = "0.1.37", optional = true } 79 | waitpid-any = { version = "0.3", optional = true } 80 | 81 | [dev-dependencies] 82 | async-io = "2" 83 | async-process = "2" 84 | criterion = { version = "0.6", features = ["async_tokio"] } 85 | tokio = { version = "1.27.0", features = ["io-std", "io-util", "macros", "process", "rt", "time"] } 86 | tokio-util = { version = "0.7.8", features = ["compat"] } 87 | tower = "0.5" 88 | tracing-subscriber = "0.3.16" 89 | 90 | [profile.bench] 91 | debug = 1 92 | 93 | [package.metadata.docs.rs] 94 | all-features = true 95 | rustdoc-args = ["--cfg", "docsrs"] 96 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-lsp 2 | 3 | [![crates.io](https://img.shields.io/crates/v/async-lsp)](https://crates.io/crates/async-lsp) 4 | [![docs.rs](https://img.shields.io/docsrs/async-lsp)][docs] 5 | [![CI Status](https://github.com/oxalica/async-lsp/actions/workflows/ci.yaml/badge.svg)](https://github.com/oxalica/async-lsp/actions/workflows/ci.yaml) 6 | 7 | Asynchronous [Language Server Protocol (LSP)][lsp] framework based on [tower]. 8 | 9 | [docs]: https://docs.rs/async-lsp 10 | [lsp]: https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/ 11 | [tower]: https://github.com/tower-rs/tower 12 | 13 | ## Overview 14 | 15 | This crate is centered at `trait LspService` which mainly consists of a tower 16 | `Service` of LSP requests and a handler of LSP notifications. 17 | 18 | As protocol defines, requests can be processed concurrently (asynchronously), 19 | while notifications must be processed in order (synchronously), changing states 20 | and affecting semantics of later requests and/or notifications. 21 | 22 | Request handling is designed in a decoupled manner with 23 | [tower-layer](https://crates.io/crates/tower-layer), so you can chain multiple 24 | request processing layer (aka. middleware) to build complex a service. 25 | 26 | Despite the name of `LspService`, it can be used to build both Language Server 27 | and Language Client. They are logically symmetric and both using duplex 28 | channels. The only difference is the kind of requests and notifications they 29 | support. 30 | 31 | ## Usage 32 | 33 | See [examples](./examples). 34 | 35 | ## Similar projects 36 | 37 | ### [tower-lsp](https://crates.io/crates/tower-lsp) 38 | 39 | async-lsp is heavily inspired by tower-lsp, we are both built on tower but have 40 | major design differences. 41 | 42 | 1. tower-lsp is less flexible and hard to use with tower ecosystem. It doesn't 43 | support custom tower `Layer` since the `Service` interface is builtin. Both 44 | server lifecycle handling and concurrency logic is built-in and is hard to 45 | opt-opt or customize. 46 | 47 | async-lsp uses tower `Layer` to implement server lifecycle, concurrency, 48 | tracing and more. Users can select and compose layers, or creating custom 49 | ones. 50 | 51 | 1. tower-lsp handles notifications asynchronously, which is semantically 52 | incorrect and introduces 53 | [out-of-order issues](https://github.com/ebkalderon/tower-lsp/issues/284). 54 | 55 | async-lsp executes notification handlers synchronously, and allows it to 56 | control main loop when, it needs to exit or something goes wrong. 57 | 58 | 1. tower-lsp's `trait LanguageServer` accepts immutable state `&self` for 59 | concurrency. Thus state changing notifications like 60 | `textDocument/didChange` always requires asynchronous locks, regarding that 61 | the underlying communication channel is synchronous anyway. 62 | 63 | async-lsp accepts `&mut self` for requests and notifications, and the 64 | former returns a `Future` without borrowing `self`. Requests borrows only 65 | immutable states and can be run concurrently, while still being able to 66 | mutate state (like snapshotting) during preparation. 67 | 68 | 1. tower-lsp provides some higher level abstractions over LSP specification to 69 | make it more ergonomic, like generic `Client::show_message`, simplified 70 | `LanguageServer::shutdown`, or planned 71 | [`Progress`-API](https://github.com/ebkalderon/tower-lsp/issues/380). 72 | 73 | While this is not a goal of async-lsp. By default we doesn't do more than 74 | serialization, deserialization and request/response `id` handling. 75 | Parameters and interface follows the 76 | [`lsp-types`](https://crates.io/crates/lsp-types)' `Request` and 77 | `Notification` traits. But you are still free to implement your custom 78 | `Request`s for extension, or custom middlewares for higher level API. 79 | 80 | 1. tower-lsp is specialized for building Language Servers. 81 | 82 | async-lsp can be used for both Language Servers and Clients. 83 | 84 | ### [lsp-server](https://crates.io/crates/lsp-server) 85 | 86 | lsp-server is a simple and synchronous framework for only Language Server. You 87 | need spawning tasks and managing ongoing requests/responses manually. 88 | 89 | ## License 90 | 91 | async-lsp is distributed under the terms of either the MIT or the Apache 2.0 92 | license, at your option. See [LICENSE-MIT](./LICENSE-MIT) and 93 | [LICENSE-APACHE](./LICENSE-APACHE) for details. 94 | 95 | Unless you explicitly state otherwise, any contribution intentionally submitted 96 | for inclusion in the work by you, shall be dual licensed as above, without any 97 | additional terms or conditions. 98 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | use std::io; 3 | use std::ops::ControlFlow; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | use std::time::Instant; 7 | 8 | use async_lsp::{ 9 | AnyEvent, AnyNotification, AnyRequest, ClientSocket, ErrorCode, LspService, MainLoop, 10 | ResponseError, 11 | }; 12 | use criterion::{criterion_group, criterion_main, Criterion}; 13 | use futures::future::{ready, Ready}; 14 | use futures::{AsyncBufRead, AsyncRead, AsyncWrite}; 15 | use lsp_types::notification::{LogMessage, Notification}; 16 | use lsp_types::request::{Request, SemanticTokensFullRequest}; 17 | use lsp_types::{ 18 | LogMessageParams, MessageType, PartialResultParams, SemanticTokens, SemanticTokensParams, 19 | SemanticTokensResult, TextDocumentIdentifier, WorkDoneProgressParams, 20 | }; 21 | use serde_json::json; 22 | use tower_service::Service; 23 | 24 | criterion_group!(benches, bench); 25 | criterion_main!(benches); 26 | 27 | fn bench(c: &mut Criterion) { 28 | let rt = tokio::runtime::Builder::new_current_thread() 29 | .build() 30 | .unwrap(); 31 | 32 | let notification_frame = gen_input_frame(json!({ 33 | "jsonrpc": "2.0", 34 | "method": LogMessage::METHOD, 35 | "params": serde_json::to_value(LogMessageParams { 36 | typ: MessageType::LOG, 37 | message: "log".into(), 38 | }).unwrap(), 39 | })); 40 | 41 | c.bench_function("input-notification", |b| { 42 | b.to_async(&rt).iter_custom(|iters| { 43 | let mut input = RingReader::from(&*notification_frame); 44 | async move { 45 | let (mainloop, _client) = MainLoop::new_server(|_| TestService { 46 | count_notifications: iters, 47 | }); 48 | let fut = mainloop.run( 49 | black_box(Pin::new(&mut input) as Pin<&mut dyn AsyncBufRead>), 50 | futures::io::sink(), 51 | ); 52 | 53 | let inst = Instant::now(); 54 | fut.await.unwrap(); 55 | inst.elapsed() 56 | } 57 | }); 58 | }); 59 | 60 | c.bench_function("output-notification", |b| { 61 | b.to_async(&rt).iter_custom(|iters| async move { 62 | let (mainloop, client) = MainLoop::new_server(|_| TestService { 63 | count_notifications: iters, 64 | }); 65 | for _ in 0..iters { 66 | client 67 | .notify::(LogMessageParams { 68 | typ: MessageType::LOG, 69 | message: "log".into(), 70 | }) 71 | .unwrap(); 72 | } 73 | client.emit(()).unwrap(); 74 | 75 | let mut output = futures::io::sink(); 76 | let fut = mainloop.run( 77 | PendingReader, 78 | black_box(Pin::new(&mut output) as Pin<&mut dyn AsyncWrite>), 79 | ); 80 | 81 | let inst = Instant::now(); 82 | fut.await.unwrap(); 83 | inst.elapsed() 84 | }); 85 | }); 86 | 87 | let request_frame = gen_input_frame(json!({ 88 | "jsonrpc": "2.0", 89 | "method": SemanticTokensFullRequest::METHOD, 90 | "id": 42, 91 | "params": SemanticTokensParams { 92 | work_done_progress_params: WorkDoneProgressParams::default(), 93 | partial_result_params: PartialResultParams::default(), 94 | text_document: TextDocumentIdentifier::new("untitled:Untitled-1".parse().unwrap()), 95 | }, 96 | })); 97 | 98 | c.bench_function("request-throughput", |b| { 99 | b.to_async(&rt).iter_custom(|iters| { 100 | let mut input = RingReader::from(&*request_frame); 101 | async move { 102 | let (mainloop, client) = MainLoop::new_server(|_| TestService { 103 | count_notifications: 0, 104 | }); 105 | 106 | let mut output = CounterWriter { 107 | limit: iters, 108 | client, 109 | }; 110 | let fut = mainloop.run( 111 | black_box(Pin::new(&mut input) as Pin<&mut dyn AsyncBufRead>), 112 | black_box(Pin::new(&mut output) as Pin<&mut dyn AsyncWrite>), 113 | ); 114 | 115 | let inst = Instant::now(); 116 | fut.await.unwrap(); 117 | inst.elapsed() 118 | } 119 | }); 120 | }); 121 | } 122 | 123 | struct TestService { 124 | count_notifications: u64, 125 | } 126 | 127 | impl Service for TestService { 128 | type Response = serde_json::Value; 129 | type Error = ResponseError; 130 | type Future = Ready>; 131 | 132 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 133 | Poll::Ready(Ok(())) 134 | } 135 | 136 | fn call(&mut self, req: AnyRequest) -> Self::Future { 137 | let ret = if req.method == SemanticTokensFullRequest::METHOD { 138 | Ok( 139 | serde_json::to_value(SemanticTokensResult::Tokens(SemanticTokens { 140 | result_id: None, 141 | data: Vec::new(), 142 | })) 143 | .unwrap(), 144 | ) 145 | } else { 146 | Err(ResponseError::new( 147 | ErrorCode::METHOD_NOT_FOUND, 148 | "method not found", 149 | )) 150 | }; 151 | ready(ret) 152 | } 153 | } 154 | 155 | impl LspService for TestService { 156 | fn notify(&mut self, _notif: AnyNotification) -> ControlFlow> { 157 | if self.count_notifications == 0 { 158 | return ControlFlow::Break(Ok(())); 159 | } 160 | self.count_notifications -= 1; 161 | ControlFlow::Continue(()) 162 | } 163 | 164 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 165 | event.downcast::<()>().unwrap(); 166 | ControlFlow::Break(Ok(())) 167 | } 168 | } 169 | 170 | fn gen_input_frame(v: serde_json::Value) -> Vec { 171 | let data = serde_json::to_string(&v).unwrap(); 172 | format!( 173 | "\ 174 | Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\ 175 | Content-Length: {}\r\n\ 176 | \r\n\ 177 | {}\ 178 | ", 179 | data.len(), 180 | data 181 | ) 182 | .into_bytes() 183 | } 184 | 185 | struct PendingReader; 186 | 187 | impl AsyncRead for PendingReader { 188 | fn poll_read( 189 | self: Pin<&mut Self>, 190 | _cx: &mut Context<'_>, 191 | _buf: &mut [u8], 192 | ) -> Poll> { 193 | Poll::Pending 194 | } 195 | 196 | fn poll_read_vectored( 197 | self: Pin<&mut Self>, 198 | _cx: &mut Context<'_>, 199 | _bufs: &mut [io::IoSliceMut<'_>], 200 | ) -> Poll> { 201 | unreachable!() 202 | } 203 | } 204 | 205 | impl AsyncBufRead for PendingReader { 206 | fn poll_fill_buf(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 207 | Poll::Pending 208 | } 209 | 210 | fn consume(self: Pin<&mut Self>, _amt: usize) { 211 | unreachable!() 212 | } 213 | } 214 | 215 | #[derive(Clone)] 216 | struct RingReader<'a> { 217 | data: &'a [u8], 218 | pos: usize, 219 | } 220 | 221 | impl<'a> From<&'a [u8]> for RingReader<'a> { 222 | fn from(data: &'a [u8]) -> Self { 223 | Self { data, pos: 0 } 224 | } 225 | } 226 | 227 | impl AsyncRead for RingReader<'_> { 228 | fn poll_read( 229 | mut self: Pin<&mut Self>, 230 | _cx: &mut Context<'_>, 231 | buf: &mut [u8], 232 | ) -> Poll> { 233 | let end = self.pos + buf.len(); 234 | assert!(end <= self.data.len(), "should not read passing messages"); 235 | buf.copy_from_slice(&self.data[self.pos..end]); 236 | if end == self.data.len() { 237 | self.pos = 0; 238 | } 239 | Poll::Ready(Ok(buf.len())) 240 | } 241 | 242 | fn poll_read_vectored( 243 | self: Pin<&mut Self>, 244 | _cx: &mut Context<'_>, 245 | _bufs: &mut [io::IoSliceMut<'_>], 246 | ) -> Poll> { 247 | unreachable!() 248 | } 249 | } 250 | 251 | impl AsyncBufRead for RingReader<'_> { 252 | fn poll_fill_buf(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 253 | assert!(self.pos < self.data.len()); 254 | let pos = self.pos; 255 | Poll::Ready(Ok(&self.get_mut().data[pos..])) 256 | } 257 | 258 | fn consume(mut self: Pin<&mut Self>, amt: usize) { 259 | self.pos += amt; 260 | if self.data.len() == self.pos { 261 | self.pos = 0; 262 | } 263 | } 264 | } 265 | 266 | struct CounterWriter { 267 | limit: u64, 268 | client: ClientSocket, 269 | } 270 | 271 | impl AsyncWrite for CounterWriter { 272 | fn poll_write( 273 | mut self: Pin<&mut Self>, 274 | _cx: &mut Context<'_>, 275 | buf: &[u8], 276 | ) -> Poll> { 277 | if buf.starts_with(b"Content-Length: ") { 278 | self.limit -= 1; 279 | if self.limit == 0 { 280 | self.client.emit(()).unwrap(); 281 | } 282 | } 283 | Poll::Ready(Ok(buf.len())) 284 | } 285 | 286 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 287 | Poll::Ready(Ok(())) 288 | } 289 | 290 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 291 | Poll::Ready(Ok(())) 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /examples/client_builder.rs: -------------------------------------------------------------------------------- 1 | use std::ops::ControlFlow; 2 | use std::path::Path; 3 | use std::process::Stdio; 4 | 5 | use async_lsp::concurrency::ConcurrencyLayer; 6 | use async_lsp::panic::CatchUnwindLayer; 7 | use async_lsp::router::Router; 8 | use async_lsp::tracing::TracingLayer; 9 | use async_lsp::LanguageServer; 10 | use futures::channel::oneshot; 11 | use lsp_types::notification::{Progress, PublishDiagnostics, ShowMessage}; 12 | use lsp_types::{ 13 | ClientCapabilities, DidOpenTextDocumentParams, HoverContents, HoverParams, InitializeParams, 14 | InitializedParams, MarkupContent, NumberOrString, Position, ProgressParamsValue, 15 | TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url, 16 | WindowClientCapabilities, WorkDoneProgress, WorkDoneProgressParams, WorkspaceFolder, 17 | }; 18 | use tower::ServiceBuilder; 19 | use tracing::{info, Level}; 20 | 21 | const TEST_ROOT: &str = "tests/client_test_data"; 22 | // Old and new token names. 23 | const RA_INDEXING_TOKENS: &[&str] = &["rustAnalyzer/Indexing", "rustAnalyzer/cachePriming"]; 24 | 25 | struct ClientState { 26 | indexed_tx: Option>, 27 | } 28 | 29 | struct Stop; 30 | 31 | #[tokio::main(flavor = "current_thread")] 32 | async fn main() { 33 | let root_dir = Path::new(TEST_ROOT) 34 | .canonicalize() 35 | .expect("test root should be valid"); 36 | 37 | let (indexed_tx, indexed_rx) = oneshot::channel(); 38 | 39 | let (mainloop, mut server) = async_lsp::MainLoop::new_client(|_server| { 40 | let mut router = Router::new(ClientState { 41 | indexed_tx: Some(indexed_tx), 42 | }); 43 | router 44 | .notification::(|this, prog| { 45 | tracing::info!("{:?} {:?}", prog.token, prog.value); 46 | if matches!(prog.token, NumberOrString::String(s) if RA_INDEXING_TOKENS.contains(&&*s)) 47 | && matches!( 48 | prog.value, 49 | ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)) 50 | ) 51 | { 52 | // Sometimes rust-analyzer auto-index multiple times? 53 | if let Some(tx) = this.indexed_tx.take() { 54 | let _: Result<_, _> = tx.send(()); 55 | } 56 | } 57 | ControlFlow::Continue(()) 58 | }) 59 | .notification::(|_, _| ControlFlow::Continue(())) 60 | .notification::(|_, params| { 61 | tracing::info!("Message {:?}: {}", params.typ, params.message); 62 | ControlFlow::Continue(()) 63 | }) 64 | .event(|_, _: Stop| ControlFlow::Break(Ok(()))); 65 | 66 | ServiceBuilder::new() 67 | .layer(TracingLayer::default()) 68 | .layer(CatchUnwindLayer::default()) 69 | .layer(ConcurrencyLayer::default()) 70 | .service(router) 71 | }); 72 | 73 | tracing_subscriber::fmt() 74 | .with_max_level(Level::INFO) 75 | .with_ansi(false) 76 | .with_writer(std::io::stderr) 77 | .init(); 78 | 79 | let child = async_process::Command::new("rust-analyzer") 80 | .current_dir(&root_dir) 81 | .stdin(Stdio::piped()) 82 | .stdout(Stdio::piped()) 83 | .stderr(Stdio::inherit()) 84 | .kill_on_drop(true) 85 | .spawn() 86 | .expect("Failed run rust-analyzer"); 87 | let stdout = child.stdout.unwrap(); 88 | let stdin = child.stdin.unwrap(); 89 | 90 | let mainloop_fut = tokio::spawn(async move { 91 | mainloop.run_buffered(stdout, stdin).await.unwrap(); 92 | }); 93 | 94 | // Initialize. 95 | let init_ret = server 96 | .initialize(InitializeParams { 97 | workspace_folders: Some(vec![WorkspaceFolder { 98 | uri: Url::from_file_path(&root_dir).unwrap(), 99 | name: "root".into(), 100 | }]), 101 | capabilities: ClientCapabilities { 102 | window: Some(WindowClientCapabilities { 103 | work_done_progress: Some(true), 104 | ..WindowClientCapabilities::default() 105 | }), 106 | ..ClientCapabilities::default() 107 | }, 108 | ..InitializeParams::default() 109 | }) 110 | .await 111 | .unwrap(); 112 | info!("Initialized: {init_ret:?}"); 113 | server.initialized(InitializedParams {}).unwrap(); 114 | 115 | // Synchronize documents. 116 | let file_uri = Url::from_file_path(root_dir.join("src/lib.rs")).unwrap(); 117 | let text = "fn func() { let var = 1; }"; 118 | server 119 | .did_open(DidOpenTextDocumentParams { 120 | text_document: TextDocumentItem { 121 | uri: file_uri.clone(), 122 | language_id: "rust".into(), 123 | version: 0, 124 | text: text.into(), 125 | }, 126 | }) 127 | .unwrap(); 128 | 129 | // Wait until indexed. 130 | indexed_rx.await.unwrap(); 131 | 132 | // Query. 133 | let var_pos = text.find("var").unwrap(); 134 | let hover = server 135 | .hover(HoverParams { 136 | text_document_position_params: TextDocumentPositionParams { 137 | text_document: TextDocumentIdentifier { uri: file_uri }, 138 | position: Position::new(0, var_pos as _), 139 | }, 140 | work_done_progress_params: WorkDoneProgressParams::default(), 141 | }) 142 | .await 143 | .unwrap() 144 | .unwrap(); 145 | info!("Hover result: {hover:?}"); 146 | assert!( 147 | matches!( 148 | hover.contents, 149 | HoverContents::Markup(MarkupContent { value, .. }) 150 | if value.contains("let var: i32") 151 | ), 152 | "should show the type of `var`", 153 | ); 154 | 155 | // Shutdown. 156 | server.shutdown(()).await.unwrap(); 157 | server.exit(()).unwrap(); 158 | 159 | server.emit(Stop).unwrap(); 160 | mainloop_fut.await.unwrap(); 161 | } 162 | 163 | #[test] 164 | #[ignore = "invokes rust-analyzer"] 165 | fn rust_analyzer() { 166 | main() 167 | } 168 | -------------------------------------------------------------------------------- /examples/client_trait.rs: -------------------------------------------------------------------------------- 1 | use std::ops::ControlFlow; 2 | use std::path::Path; 3 | use std::process::Stdio; 4 | 5 | use async_lsp::concurrency::ConcurrencyLayer; 6 | use async_lsp::panic::CatchUnwindLayer; 7 | use async_lsp::router::Router; 8 | use async_lsp::tracing::TracingLayer; 9 | use async_lsp::{LanguageClient, LanguageServer, ResponseError}; 10 | use futures::channel::oneshot; 11 | use lsp_types::{ 12 | ClientCapabilities, DidOpenTextDocumentParams, HoverContents, HoverParams, InitializeParams, 13 | InitializedParams, MarkupContent, NumberOrString, Position, ProgressParams, 14 | ProgressParamsValue, PublishDiagnosticsParams, ShowMessageParams, TextDocumentIdentifier, 15 | TextDocumentItem, TextDocumentPositionParams, Url, WindowClientCapabilities, WorkDoneProgress, 16 | WorkDoneProgressParams, WorkspaceFolder, 17 | }; 18 | use tower::ServiceBuilder; 19 | use tracing::{info, Level}; 20 | 21 | const TEST_ROOT: &str = "tests/client_test_data"; 22 | // Old and new token names. 23 | const RA_INDEXING_TOKENS: &[&str] = &["rustAnalyzer/Indexing", "rustAnalyzer/cachePriming"]; 24 | 25 | struct ClientState { 26 | indexed_tx: Option>, 27 | } 28 | 29 | impl LanguageClient for ClientState { 30 | type Error = ResponseError; 31 | type NotifyResult = ControlFlow>; 32 | 33 | fn progress(&mut self, params: ProgressParams) -> Self::NotifyResult { 34 | tracing::info!("{:?} {:?}", params.token, params.value); 35 | if matches!(params.token, NumberOrString::String(s) if RA_INDEXING_TOKENS.contains(&&*s)) 36 | && matches!( 37 | params.value, 38 | ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)) 39 | ) 40 | { 41 | // Sometimes rust-analyzer auto-index multiple times? 42 | if let Some(tx) = self.indexed_tx.take() { 43 | let _: Result<_, _> = tx.send(()); 44 | } 45 | } 46 | ControlFlow::Continue(()) 47 | } 48 | 49 | fn publish_diagnostics(&mut self, _: PublishDiagnosticsParams) -> Self::NotifyResult { 50 | ControlFlow::Continue(()) 51 | } 52 | 53 | fn show_message(&mut self, params: ShowMessageParams) -> Self::NotifyResult { 54 | tracing::info!("Message {:?}: {}", params.typ, params.message); 55 | ControlFlow::Continue(()) 56 | } 57 | } 58 | 59 | impl ClientState { 60 | fn new_router(indexed_tx: oneshot::Sender<()>) -> Router { 61 | let mut router = Router::from_language_client(ClientState { 62 | indexed_tx: Some(indexed_tx), 63 | }); 64 | router.event(Self::on_stop); 65 | router 66 | } 67 | 68 | fn on_stop(&mut self, _: Stop) -> ControlFlow> { 69 | ControlFlow::Break(Ok(())) 70 | } 71 | } 72 | 73 | struct Stop; 74 | 75 | #[tokio::main(flavor = "current_thread")] 76 | async fn main() { 77 | let root_dir = Path::new(TEST_ROOT) 78 | .canonicalize() 79 | .expect("test root should be valid"); 80 | 81 | let (indexed_tx, indexed_rx) = oneshot::channel(); 82 | let (mainloop, mut server) = async_lsp::MainLoop::new_client(|_server| { 83 | ServiceBuilder::new() 84 | .layer(TracingLayer::default()) 85 | .layer(CatchUnwindLayer::default()) 86 | .layer(ConcurrencyLayer::default()) 87 | .service(ClientState::new_router(indexed_tx)) 88 | }); 89 | 90 | tracing_subscriber::fmt() 91 | .with_max_level(Level::INFO) 92 | .with_ansi(false) 93 | .with_writer(std::io::stderr) 94 | .init(); 95 | 96 | let child = async_process::Command::new("rust-analyzer") 97 | .current_dir(&root_dir) 98 | .stdin(Stdio::piped()) 99 | .stdout(Stdio::piped()) 100 | .stderr(Stdio::inherit()) 101 | .kill_on_drop(true) 102 | .spawn() 103 | .expect("Failed run rust-analyzer"); 104 | let stdout = child.stdout.unwrap(); 105 | let stdin = child.stdin.unwrap(); 106 | 107 | let mainloop_fut = tokio::spawn(async move { 108 | mainloop.run_buffered(stdout, stdin).await.unwrap(); 109 | }); 110 | 111 | // Initialize. 112 | let init_ret = server 113 | .initialize(InitializeParams { 114 | workspace_folders: Some(vec![WorkspaceFolder { 115 | uri: Url::from_file_path(&root_dir).unwrap(), 116 | name: "root".into(), 117 | }]), 118 | capabilities: ClientCapabilities { 119 | window: Some(WindowClientCapabilities { 120 | work_done_progress: Some(true), 121 | ..WindowClientCapabilities::default() 122 | }), 123 | ..ClientCapabilities::default() 124 | }, 125 | ..InitializeParams::default() 126 | }) 127 | .await 128 | .unwrap(); 129 | info!("Initialized: {init_ret:?}"); 130 | server.initialized(InitializedParams {}).unwrap(); 131 | 132 | // Synchronize documents. 133 | let file_uri = Url::from_file_path(root_dir.join("src/lib.rs")).unwrap(); 134 | let text = "#![no_std] fn func() { let var = 1; }"; 135 | server 136 | .did_open(DidOpenTextDocumentParams { 137 | text_document: TextDocumentItem { 138 | uri: file_uri.clone(), 139 | language_id: "rust".into(), 140 | version: 0, 141 | text: text.into(), 142 | }, 143 | }) 144 | .unwrap(); 145 | 146 | // Wait until indexed. 147 | indexed_rx.await.unwrap(); 148 | 149 | // Query. 150 | let var_pos = text.find("var").unwrap(); 151 | let hover = server 152 | .hover(HoverParams { 153 | text_document_position_params: TextDocumentPositionParams { 154 | text_document: TextDocumentIdentifier { uri: file_uri }, 155 | position: Position::new(0, var_pos as _), 156 | }, 157 | work_done_progress_params: WorkDoneProgressParams::default(), 158 | }) 159 | .await 160 | .unwrap() 161 | .unwrap(); 162 | info!("Hover result: {hover:?}"); 163 | assert!( 164 | matches!( 165 | hover.contents, 166 | HoverContents::Markup(MarkupContent { value, .. }) 167 | if value.contains("let var: i32") 168 | ), 169 | "should show the type of `var`", 170 | ); 171 | 172 | // Shutdown. 173 | server.shutdown(()).await.unwrap(); 174 | server.exit(()).unwrap(); 175 | 176 | server.emit(Stop).unwrap(); 177 | mainloop_fut.await.unwrap(); 178 | } 179 | 180 | #[test] 181 | #[ignore = "invokes rust-analyzer"] 182 | fn rust_analyzer() { 183 | main() 184 | } 185 | -------------------------------------------------------------------------------- /examples/inspector.rs: -------------------------------------------------------------------------------- 1 | use std::ops::ControlFlow; 2 | use std::pin::Pin; 3 | use std::process::Stdio; 4 | use std::task::{Context, Poll}; 5 | 6 | use async_lsp::{AnyEvent, AnyNotification, AnyRequest, LspService, MainLoop}; 7 | use async_process::Command; 8 | use futures::Future; 9 | use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; 10 | use tower_service::Service; 11 | use tracing::Level; 12 | 13 | struct Forward(Option); 14 | 15 | impl Service for Forward { 16 | type Response = S::Response; 17 | type Error = S::Error; 18 | type Future = S::Future; 19 | 20 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 21 | self.0.as_mut().unwrap().poll_ready(cx) 22 | } 23 | 24 | fn call(&mut self, req: AnyRequest) -> Self::Future { 25 | self.0.as_mut().unwrap().call(req) 26 | } 27 | } 28 | 29 | impl LspService for Forward { 30 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 31 | self.0.as_mut().unwrap().notify(notif) 32 | } 33 | 34 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 35 | self.0.as_mut().unwrap().emit(event) 36 | } 37 | } 38 | 39 | struct Inspect { 40 | service: S, 41 | incoming: &'static str, 42 | outgoing: &'static str, 43 | } 44 | 45 | impl Service for Inspect 46 | where 47 | S::Future: Send + 'static, 48 | { 49 | type Response = S::Response; 50 | type Error = S::Error; 51 | type Future = Pin> + Send>>; 52 | 53 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 54 | self.service.poll_ready(cx) 55 | } 56 | 57 | fn call(&mut self, req: AnyRequest) -> Self::Future { 58 | tracing::info!("{} request {}", self.incoming, req.method); 59 | let method = req.method.clone(); 60 | let fut = self.service.call(req); 61 | let outgoing = self.outgoing; 62 | Box::pin(async move { 63 | let resp_ret = fut.await; 64 | tracing::info!( 65 | "{} response {}: {}", 66 | outgoing, 67 | method, 68 | if resp_ret.is_ok() { "ok" } else { "err" }, 69 | ); 70 | resp_ret 71 | }) 72 | } 73 | } 74 | 75 | impl LspService for Inspect 76 | where 77 | S::Future: Send + 'static, 78 | { 79 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 80 | tracing::info!("{} notification {}", self.incoming, notif.method); 81 | self.service.notify(notif) 82 | } 83 | 84 | fn emit(&mut self, _event: AnyEvent) -> ControlFlow> { 85 | unreachable!() 86 | } 87 | } 88 | 89 | #[tokio::main(flavor = "current_thread")] 90 | async fn main() { 91 | tracing_subscriber::fmt() 92 | .with_max_level(Level::INFO) 93 | .with_ansi(false) 94 | .with_writer(std::io::stderr) 95 | .init(); 96 | 97 | let server_path = std::env::args() 98 | .nth(1) 99 | .expect("expect argument to the forwarded LSP server"); 100 | let mut child = Command::new(server_path) 101 | .stdin(Stdio::piped()) 102 | .stdout(Stdio::piped()) 103 | .stderr(Stdio::inherit()) 104 | .spawn() 105 | .expect("failed to spawn"); 106 | 107 | // Mock client to communicate with the server. Incoming messages are forwarded to stdin/out. 108 | let (mut mock_client, server_socket) = MainLoop::new_client(|_| Inspect { 109 | service: Forward(None), 110 | incoming: "<", 111 | outgoing: ">", 112 | }); 113 | 114 | // Mock server to communicate with the client. Incoming messages are forwarded to child LSP. 115 | let (mock_server, client_socket) = MainLoop::new_server(|_| Inspect { 116 | service: server_socket, 117 | incoming: ">", 118 | outgoing: "<", 119 | }); 120 | 121 | // Link to form a bidirectional connection. 122 | mock_client.get_mut().service.0 = Some(client_socket); 123 | 124 | let child_stdin = child.stdin.take().unwrap(); 125 | let child_stdout = child.stdout.take().unwrap(); 126 | let main1 = tokio::spawn(mock_client.run_buffered(child_stdout, child_stdin)); 127 | 128 | let stdin = tokio::io::stdin().compat(); 129 | let stdout = tokio::io::stdout().compat_write(); 130 | let main2 = tokio::spawn(mock_server.run_buffered(stdin, stdout)); 131 | 132 | let ret = tokio::select! { 133 | ret = main1 => ret, 134 | ret = main2 => ret, 135 | }; 136 | ret.expect("join error").unwrap(); 137 | } 138 | -------------------------------------------------------------------------------- /examples/server_builder.rs: -------------------------------------------------------------------------------- 1 | use std::ops::ControlFlow; 2 | use std::time::Duration; 3 | 4 | use async_lsp::client_monitor::ClientProcessMonitorLayer; 5 | use async_lsp::concurrency::ConcurrencyLayer; 6 | use async_lsp::panic::CatchUnwindLayer; 7 | use async_lsp::router::Router; 8 | use async_lsp::server::LifecycleLayer; 9 | use async_lsp::tracing::TracingLayer; 10 | use async_lsp::ClientSocket; 11 | use lsp_types::{ 12 | notification, request, Hover, HoverContents, HoverProviderCapability, InitializeResult, 13 | MarkedString, MessageType, OneOf, ServerCapabilities, ShowMessageParams, 14 | }; 15 | use tower::ServiceBuilder; 16 | use tracing::{info, Level}; 17 | 18 | struct ServerState { 19 | client: ClientSocket, 20 | counter: i32, 21 | } 22 | 23 | struct TickEvent; 24 | 25 | #[tokio::main(flavor = "current_thread")] 26 | async fn main() { 27 | let (server, _) = async_lsp::MainLoop::new_server(|client| { 28 | tokio::spawn({ 29 | let client = client.clone(); 30 | async move { 31 | let mut interval = tokio::time::interval(Duration::from_secs(1)); 32 | loop { 33 | interval.tick().await; 34 | if client.emit(TickEvent).is_err() { 35 | break; 36 | } 37 | } 38 | } 39 | }); 40 | 41 | let mut router = Router::new(ServerState { 42 | client: client.clone(), 43 | counter: 0, 44 | }); 45 | router 46 | .request::(|_, params| async move { 47 | eprintln!("Initialize with {params:?}"); 48 | Ok(InitializeResult { 49 | capabilities: ServerCapabilities { 50 | hover_provider: Some(HoverProviderCapability::Simple(true)), 51 | definition_provider: Some(OneOf::Left(true)), 52 | ..ServerCapabilities::default() 53 | }, 54 | server_info: None, 55 | }) 56 | }) 57 | .request::(|st, _| { 58 | let client = st.client.clone(); 59 | let counter = st.counter; 60 | async move { 61 | tokio::time::sleep(Duration::from_secs(1)).await; 62 | client 63 | .notify::(ShowMessageParams { 64 | typ: MessageType::INFO, 65 | message: "Hello LSP".into(), 66 | }) 67 | .unwrap(); 68 | Ok(Some(Hover { 69 | contents: HoverContents::Scalar(MarkedString::String(format!( 70 | "I am a hover text {counter}!" 71 | ))), 72 | range: None, 73 | })) 74 | } 75 | }) 76 | .request::(|_, _| async move { 77 | unimplemented!("Not yet implemented!") 78 | }) 79 | .notification::(|_, _| ControlFlow::Continue(())) 80 | .notification::(|_, _| ControlFlow::Continue(())) 81 | .notification::(|_, _| ControlFlow::Continue(())) 82 | .notification::(|_, _| ControlFlow::Continue(())) 83 | .notification::(|_, _| ControlFlow::Continue(())) 84 | .event::(|st, _| { 85 | info!("tick"); 86 | st.counter += 1; 87 | ControlFlow::Continue(()) 88 | }); 89 | 90 | ServiceBuilder::new() 91 | .layer(TracingLayer::default()) 92 | .layer(LifecycleLayer::default()) 93 | .layer(CatchUnwindLayer::default()) 94 | .layer(ConcurrencyLayer::default()) 95 | .layer(ClientProcessMonitorLayer::new(client)) 96 | .service(router) 97 | }); 98 | 99 | tracing_subscriber::fmt() 100 | .with_max_level(Level::INFO) 101 | .with_ansi(false) 102 | .with_writer(std::io::stderr) 103 | .init(); 104 | 105 | // Prefer truly asynchronous piped stdin/stdout without blocking tasks. 106 | #[cfg(unix)] 107 | let (stdin, stdout) = ( 108 | async_lsp::stdio::PipeStdin::lock_tokio().unwrap(), 109 | async_lsp::stdio::PipeStdout::lock_tokio().unwrap(), 110 | ); 111 | // Fallback to spawn blocking read/write otherwise. 112 | #[cfg(not(unix))] 113 | let (stdin, stdout) = ( 114 | tokio_util::compat::TokioAsyncReadCompatExt::compat(tokio::io::stdin()), 115 | tokio_util::compat::TokioAsyncWriteCompatExt::compat_write(tokio::io::stdout()), 116 | ); 117 | 118 | server.run_buffered(stdin, stdout).await.unwrap(); 119 | } 120 | -------------------------------------------------------------------------------- /examples/server_trait.rs: -------------------------------------------------------------------------------- 1 | use std::ops::ControlFlow; 2 | use std::time::Duration; 3 | 4 | use async_lsp::client_monitor::ClientProcessMonitorLayer; 5 | use async_lsp::concurrency::ConcurrencyLayer; 6 | use async_lsp::panic::CatchUnwindLayer; 7 | use async_lsp::router::Router; 8 | use async_lsp::server::LifecycleLayer; 9 | use async_lsp::tracing::TracingLayer; 10 | use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError}; 11 | use futures::future::BoxFuture; 12 | use lsp_types::{ 13 | DidChangeConfigurationParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, 14 | HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, 15 | MarkedString, MessageType, OneOf, ServerCapabilities, ShowMessageParams, 16 | }; 17 | use tower::ServiceBuilder; 18 | use tracing::{info, Level}; 19 | 20 | struct ServerState { 21 | client: ClientSocket, 22 | counter: i32, 23 | } 24 | 25 | impl LanguageServer for ServerState { 26 | type Error = ResponseError; 27 | type NotifyResult = ControlFlow>; 28 | 29 | fn initialize( 30 | &mut self, 31 | params: InitializeParams, 32 | ) -> BoxFuture<'static, Result> { 33 | eprintln!("Initialize with {params:?}"); 34 | Box::pin(async move { 35 | Ok(InitializeResult { 36 | capabilities: ServerCapabilities { 37 | hover_provider: Some(HoverProviderCapability::Simple(true)), 38 | definition_provider: Some(OneOf::Left(true)), 39 | ..ServerCapabilities::default() 40 | }, 41 | server_info: None, 42 | }) 43 | }) 44 | } 45 | 46 | fn hover(&mut self, _: HoverParams) -> BoxFuture<'static, Result, Self::Error>> { 47 | let mut client = self.client.clone(); 48 | let counter = self.counter; 49 | Box::pin(async move { 50 | tokio::time::sleep(Duration::from_secs(1)).await; 51 | client 52 | .show_message(ShowMessageParams { 53 | typ: MessageType::INFO, 54 | message: "Hello LSP".into(), 55 | }) 56 | .unwrap(); 57 | Ok(Some(Hover { 58 | contents: HoverContents::Scalar(MarkedString::String(format!( 59 | "I am a hover text {counter}!" 60 | ))), 61 | range: None, 62 | })) 63 | }) 64 | } 65 | 66 | fn definition( 67 | &mut self, 68 | _: GotoDefinitionParams, 69 | ) -> BoxFuture<'static, Result, ResponseError>> { 70 | unimplemented!("Not yet implemented!"); 71 | } 72 | 73 | fn did_change_configuration( 74 | &mut self, 75 | _: DidChangeConfigurationParams, 76 | ) -> ControlFlow> { 77 | ControlFlow::Continue(()) 78 | } 79 | } 80 | 81 | struct TickEvent; 82 | 83 | impl ServerState { 84 | fn new_router(client: ClientSocket) -> Router { 85 | let mut router = Router::from_language_server(Self { client, counter: 0 }); 86 | router.event(Self::on_tick); 87 | router 88 | } 89 | 90 | fn on_tick(&mut self, _: TickEvent) -> ControlFlow> { 91 | info!("tick"); 92 | self.counter += 1; 93 | ControlFlow::Continue(()) 94 | } 95 | } 96 | 97 | #[tokio::main(flavor = "current_thread")] 98 | async fn main() { 99 | let (server, _) = async_lsp::MainLoop::new_server(|client| { 100 | tokio::spawn({ 101 | let client = client.clone(); 102 | async move { 103 | let mut interval = tokio::time::interval(Duration::from_secs(1)); 104 | loop { 105 | interval.tick().await; 106 | if client.emit(TickEvent).is_err() { 107 | break; 108 | } 109 | } 110 | } 111 | }); 112 | 113 | ServiceBuilder::new() 114 | .layer(TracingLayer::default()) 115 | .layer(LifecycleLayer::default()) 116 | .layer(CatchUnwindLayer::default()) 117 | .layer(ConcurrencyLayer::default()) 118 | .layer(ClientProcessMonitorLayer::new(client.clone())) 119 | .service(ServerState::new_router(client)) 120 | }); 121 | 122 | tracing_subscriber::fmt() 123 | .with_max_level(Level::INFO) 124 | .with_ansi(false) 125 | .with_writer(std::io::stderr) 126 | .init(); 127 | 128 | // Prefer truly asynchronous piped stdin/stdout without blocking tasks. 129 | #[cfg(unix)] 130 | let (stdin, stdout) = ( 131 | async_lsp::stdio::PipeStdin::lock_tokio().unwrap(), 132 | async_lsp::stdio::PipeStdout::lock_tokio().unwrap(), 133 | ); 134 | // Fallback to spawn blocking read/write otherwise. 135 | #[cfg(not(unix))] 136 | let (stdin, stdout) = ( 137 | tokio_util::compat::TokioAsyncReadCompatExt::compat(tokio::io::stdin()), 138 | tokio_util::compat::TokioAsyncWriteCompatExt::compat_write(tokio::io::stdout()), 139 | ); 140 | 141 | server.run_buffered(stdin, stdout).await.unwrap(); 142 | } 143 | -------------------------------------------------------------------------------- /generate_omni_trait.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | if [[ -n "$1" ]]; then 4 | cat "$1" 5 | else 6 | curl --fail -L 'https://github.com/microsoft/language-server-protocol/raw/38c3746bc86c8ae7bc74f49afbd398b132bc627e/_specifications/lsp/3.17/metaModel/metaModel.json' | 7 | tee ./metaModel.json 8 | fi | jq -r ' 9 | def is_excluded: 10 | .method | 11 | # Notebook protocols are not supported. 12 | test("^notebookDocument/") 13 | # Lifecycle methods are handled specially outside. 14 | or test("^(initialized?|shutdown|exit)$"); 15 | 16 | def to_snake: 17 | # Keep the prefix for `*/diagnostic` since both for workspace and document exist. 18 | sub("textDocument/diagnostic"; "document_diagnostic") | 19 | sub("workspace/diagnostic"; "workspace_diagnostic") | 20 | sub("^(workspace|textDocument|callHierarchy|typeHierarchy|window|client|\\$)/"; "") | 21 | sub("/"; "_"; "g") | sub("(?[A-Z])"; "_\(.x | ascii_downcase)"; "g"); 22 | 23 | def extract(dir_pat): 24 | .[] | 25 | select((.messageDirection | test(dir_pat)) and (is_excluded | not)) | 26 | " \"\(.method)\", \(.method | to_snake);"; 27 | 28 | "// This file is automatically @generated by update_omni_trait.sh", 29 | "// It is not intended for manual editing.", 30 | "#[rustfmt::ignore]", 31 | "define! {", 32 | " // Client -> Server requests.", 33 | " {", 34 | (.requests | extract("clientToServer|both")), 35 | " }", 36 | " // Client -> Server notifications.", 37 | " {", 38 | (.notifications | extract("clientToServer|both")), 39 | " }", 40 | " // Server -> Client requests.", 41 | " {", 42 | (.requests | extract("serverToClient|both")), 43 | " }", 44 | " // Server -> Client notifications.", 45 | " {", 46 | (.notifications | extract("serverToClient|both")), 47 | " }", 48 | "}" 49 | ' >./src/omni_trait_generated.rs 50 | -------------------------------------------------------------------------------- /src/client_monitor.rs: -------------------------------------------------------------------------------- 1 | //! Stop the main loop when the Language Client process aborted unexpectedly. 2 | //! 3 | //! *Only applies to Language Servers.* 4 | //! 5 | //! Typically, the Language Client should send 6 | //! [`exit` notification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#exit) 7 | //! to inform the Language Server to exit. While in case of it unexpected aborted without the 8 | //! notification, and the communicate channel is left open somehow (eg. an FIFO or UNIX domain 9 | //! socket), the Language Server would wait indefinitely, wasting system resources. 10 | //! 11 | //! It is encouraged by 12 | //! [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initializeParams) 13 | //! that, 14 | //! > If the parent process (`processId`) is not alive then the server should exit (see exit 15 | //! > notification) its process. 16 | //! 17 | //! And this middleware does exactly this monitor mechanism. 18 | //! 19 | //! Implementation: See crate [`waitpid_any`]. 20 | use std::ops::ControlFlow; 21 | use std::task::{Context, Poll}; 22 | 23 | use lsp_types::request::{self, Request}; 24 | use tower_layer::Layer; 25 | use tower_service::Service; 26 | 27 | use crate::{AnyEvent, AnyNotification, AnyRequest, ClientSocket, Error, LspService, Result}; 28 | 29 | struct ClientProcessExited; 30 | 31 | /// The middleware stopping the main loop when the Language Client process aborted unexpectedly. 32 | /// 33 | /// See [module level documentations](self) for details. 34 | pub struct ClientProcessMonitor { 35 | service: S, 36 | client: ClientSocket, 37 | } 38 | 39 | impl Service for ClientProcessMonitor { 40 | type Response = S::Response; 41 | type Error = S::Error; 42 | type Future = S::Future; 43 | 44 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 45 | self.service.poll_ready(cx) 46 | } 47 | 48 | fn call(&mut self, req: AnyRequest) -> Self::Future { 49 | if let Some(pid) = (|| -> Option { 50 | (req.method == request::Initialize::METHOD) 51 | .then_some(&req.params)? 52 | .as_object()? 53 | .get("processId")? 54 | .as_i64()? 55 | .try_into() 56 | .ok() 57 | })() { 58 | match waitpid_any::WaitHandle::open(pid) { 59 | Ok(mut handle) => { 60 | let client = self.client.clone(); 61 | let spawn_ret = std::thread::Builder::new() 62 | .name("client-process-monitor".into()) 63 | .spawn(move || { 64 | match handle.wait() { 65 | Ok(()) => { 66 | // Ignore channel close. 67 | let _: Result<_, _> = client.emit(ClientProcessExited); 68 | } 69 | #[allow(unused_variables)] 70 | Err(err) => { 71 | #[cfg(feature = "tracing")] 72 | ::tracing::error!( 73 | "Failed to monitor peer process ({pid}): {err:#}" 74 | ); 75 | } 76 | } 77 | }); 78 | #[allow(unused_variables)] 79 | if let Err(err) = spawn_ret { 80 | #[cfg(feature = "tracing")] 81 | ::tracing::error!("Failed to spawn client process monitor thread: {err:#}"); 82 | } 83 | } 84 | // Already exited. 85 | #[cfg(unix)] 86 | Err(err) if err.raw_os_error() == Some(rustix::io::Errno::SRCH.raw_os_error()) => { 87 | // Ignore channel close. 88 | let _: Result<_, _> = self.client.emit(ClientProcessExited); 89 | } 90 | #[allow(unused_variables)] 91 | Err(err) => { 92 | #[cfg(feature = "tracing")] 93 | ::tracing::error!("Failed to monitor peer process {pid}: {err:#}"); 94 | } 95 | } 96 | } 97 | 98 | self.service.call(req) 99 | } 100 | } 101 | 102 | impl LspService for ClientProcessMonitor { 103 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 104 | self.service.notify(notif) 105 | } 106 | 107 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 108 | match event.downcast::() { 109 | Ok(ClientProcessExited) => { 110 | ControlFlow::Break(Err(Error::Protocol("Client process exited".into()))) 111 | } 112 | Err(event) => self.service.emit(event), 113 | } 114 | } 115 | } 116 | 117 | /// The builder of [`ClientProcessMonitor`] middleware. 118 | #[must_use] 119 | pub struct ClientProcessMonitorBuilder { 120 | client: ClientSocket, 121 | } 122 | 123 | impl ClientProcessMonitorBuilder { 124 | /// Create the middleware builder with a given [`ClientSocket`] to inject exit events. 125 | pub fn new(client: ClientSocket) -> Self { 126 | Self { client } 127 | } 128 | } 129 | 130 | /// A type alias of [`ClientProcessMonitorBuilder`] conforming to the naming convention of 131 | /// [`tower_layer`]. 132 | pub type ClientProcessMonitorLayer = ClientProcessMonitorBuilder; 133 | 134 | impl Layer for ClientProcessMonitorBuilder { 135 | type Service = ClientProcessMonitor; 136 | 137 | fn layer(&self, inner: S) -> Self::Service { 138 | ClientProcessMonitor { 139 | service: inner, 140 | client: self.client.clone(), 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/concurrency.rs: -------------------------------------------------------------------------------- 1 | //! Incoming request multiplexing limits and cancellation. 2 | //! 3 | //! *Applies to both Language Servers and Language Clients.* 4 | //! 5 | //! Note that the [`crate::MainLoop`] can poll multiple ongoing requests 6 | //! out-of-box, while this middleware is to provides these additional features: 7 | //! 1. Limit concurrent incoming requests to at most `max_concurrency`. 8 | //! 2. Cancellation of incoming requests via client notification `$/cancelRequest`. 9 | use std::collections::HashMap; 10 | use std::future::Future; 11 | use std::num::NonZeroUsize; 12 | use std::ops::ControlFlow; 13 | use std::pin::Pin; 14 | use std::sync::{Arc, Weak}; 15 | use std::task::{Context, Poll}; 16 | use std::thread::available_parallelism; 17 | 18 | use futures::stream::{AbortHandle, Abortable}; 19 | use futures::task::AtomicWaker; 20 | use lsp_types::notification::{self, Notification}; 21 | use pin_project_lite::pin_project; 22 | use tower_layer::Layer; 23 | use tower_service::Service; 24 | 25 | use crate::{ 26 | AnyEvent, AnyNotification, AnyRequest, ErrorCode, LspService, RequestId, ResponseError, Result, 27 | }; 28 | 29 | /// The middleware for incoming request multiplexing limits and cancellation. 30 | /// 31 | /// See [module level documentations](self) for details. 32 | pub struct Concurrency { 33 | service: S, 34 | max_concurrency: NonZeroUsize, 35 | /// A specialized single-acquire-multiple-release semaphore, using `Arc::weak_count` as tokens. 36 | semaphore: Arc, 37 | ongoing: HashMap, 38 | } 39 | 40 | define_getters!(impl[S] Concurrency, service: S); 41 | 42 | impl Service for Concurrency 43 | where 44 | S::Error: From, 45 | { 46 | type Response = S::Response; 47 | type Error = S::Error; 48 | type Future = ResponseFuture; 49 | 50 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 51 | if Arc::weak_count(&self.semaphore) >= self.max_concurrency.get() { 52 | // Implicit `Acquire`. 53 | self.semaphore.register(cx.waker()); 54 | // No guards dropped between the check and register? 55 | if Arc::weak_count(&self.semaphore) >= self.max_concurrency.get() { 56 | return Poll::Pending; 57 | } 58 | } 59 | 60 | // Here we have `weak_count < max_concurrency`. The service is ready for new calls. 61 | Poll::Ready(Ok(())) 62 | } 63 | 64 | fn call(&mut self, req: AnyRequest) -> Self::Future { 65 | let guard = SemaphoreGuard(Arc::downgrade(&self.semaphore)); 66 | debug_assert!( 67 | Arc::weak_count(&self.semaphore) <= self.max_concurrency.get(), 68 | "`poll_ready` is not called before `call`", 69 | ); 70 | 71 | let (handle, registration) = AbortHandle::new_pair(); 72 | 73 | // Regularly purge completed or dead tasks. See also `AbortOnDrop` below. 74 | // This costs 2*N time to remove at least N tasks, results in amortized O(1) time cost 75 | // for each spawned task. 76 | if self.ongoing.len() >= self.max_concurrency.get() * 2 { 77 | self.ongoing.retain(|_, handle| !handle.is_aborted()); 78 | } 79 | self.ongoing.insert(req.id.clone(), handle.clone()); 80 | 81 | let fut = self.service.call(req); 82 | let fut = Abortable::new(fut, registration); 83 | ResponseFuture { 84 | fut, 85 | _abort_on_drop: AbortOnDrop(handle), 86 | _guard: guard, 87 | } 88 | } 89 | } 90 | 91 | struct SemaphoreGuard(Weak); 92 | 93 | impl Drop for SemaphoreGuard { 94 | fn drop(&mut self) { 95 | if let Some(sema) = self.0.upgrade() { 96 | if let Some(waker) = sema.take() { 97 | // Return the token first. 98 | drop(sema); 99 | // Wake up `poll_ready`. Implicit "Release". 100 | waker.wake(); 101 | } 102 | } 103 | } 104 | } 105 | 106 | /// By default, the `AbortHandle` only transfers information from it to `Abortable<_>`, not in 107 | /// reverse. But we want to set the flag on drop (either success or failure), so that the `ongoing` 108 | /// map can be purged regularly without bloating indefinitely. 109 | struct AbortOnDrop(AbortHandle); 110 | 111 | impl Drop for AbortOnDrop { 112 | fn drop(&mut self) { 113 | self.0.abort(); 114 | } 115 | } 116 | 117 | pin_project! { 118 | /// The [`Future`] type used by the [`Concurrency`] middleware. 119 | pub struct ResponseFuture { 120 | #[pin] 121 | fut: Abortable, 122 | // NB. Comes before `SemaphoreGuard`. So that when the guard wake up the caller, it is able 123 | // to purge the current future from `ongoing` map immediately. 124 | _abort_on_drop: AbortOnDrop, 125 | _guard: SemaphoreGuard, 126 | } 127 | } 128 | 129 | impl Future for ResponseFuture 130 | where 131 | Fut: Future>, 132 | Error: From, 133 | { 134 | type Output = Fut::Output; 135 | 136 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 137 | match self.project().fut.poll(cx) { 138 | Poll::Pending => Poll::Pending, 139 | Poll::Ready(Ok(inner_ret)) => Poll::Ready(inner_ret), 140 | Poll::Ready(Err(_aborted)) => Poll::Ready(Err(ResponseError { 141 | code: ErrorCode::REQUEST_CANCELLED, 142 | message: "Client cancelled the request".into(), 143 | data: None, 144 | } 145 | .into())), 146 | } 147 | } 148 | } 149 | 150 | impl LspService for Concurrency 151 | where 152 | S::Error: From, 153 | { 154 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 155 | if notif.method == notification::Cancel::METHOD { 156 | if let Ok(params) = serde_json::from_value::(notif.params) { 157 | self.ongoing.remove(¶ms.id); 158 | } 159 | return ControlFlow::Continue(()); 160 | } 161 | self.service.notify(notif) 162 | } 163 | 164 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 165 | self.service.emit(event) 166 | } 167 | } 168 | 169 | /// The builder of [`Concurrency`] middleware. 170 | /// 171 | /// It's [`Default`] configuration has `max_concurrency` of the result of 172 | /// [`std::thread::available_parallelism`], fallback to `1` if it fails. 173 | /// 174 | /// See [module level documentations](self) for details. 175 | #[derive(Clone, Debug)] 176 | #[must_use] 177 | pub struct ConcurrencyBuilder { 178 | max_concurrency: NonZeroUsize, 179 | } 180 | 181 | impl Default for ConcurrencyBuilder { 182 | fn default() -> Self { 183 | Self::new(available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap())) 184 | } 185 | } 186 | 187 | impl ConcurrencyBuilder { 188 | /// Create the middleware with concurrency limit `max_concurrency`. 189 | pub fn new(max_concurrency: NonZeroUsize) -> Self { 190 | Self { max_concurrency } 191 | } 192 | } 193 | 194 | /// A type alias of [`ConcurrencyBuilder`] conforming to the naming convention of [`tower_layer`]. 195 | pub type ConcurrencyLayer = ConcurrencyBuilder; 196 | 197 | impl Layer for ConcurrencyBuilder { 198 | type Service = Concurrency; 199 | 200 | fn layer(&self, inner: S) -> Self::Service { 201 | Concurrency { 202 | service: inner, 203 | max_concurrency: self.max_concurrency, 204 | semaphore: Arc::new(AtomicWaker::new()), 205 | // See `Concurrency::call` for why the factor 2. 206 | ongoing: HashMap::with_capacity( 207 | self.max_concurrency 208 | .get() 209 | .checked_mul(2) 210 | .expect("max_concurrency overflow"), 211 | ), 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/forward.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::ops::ControlFlow; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use futures::channel::oneshot; 7 | use futures::FutureExt; 8 | use tower_service::Service; 9 | 10 | use crate::{ 11 | AnyEvent, AnyNotification, AnyRequest, AnyResponse, ClientSocket, ErrorCode, LspService, 12 | MainLoopEvent, Message, PeerSocket, ResponseError, Result, ServerSocket, 13 | }; 14 | 15 | pub struct PeerSocketResponseFuture { 16 | rx: oneshot::Receiver, 17 | } 18 | 19 | impl Future for PeerSocketResponseFuture { 20 | type Output = Result; 21 | 22 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 23 | match self.rx.poll_unpin(cx) { 24 | Poll::Ready(Ok(resp)) => Poll::Ready(match resp.error { 25 | None => Ok(resp.result.unwrap_or_default()), 26 | Some(resp_err) => Err(resp_err), 27 | }), 28 | Poll::Ready(Err(_closed)) => Poll::Ready(Err(ResponseError::new( 29 | ErrorCode::INTERNAL_ERROR, 30 | "forwarding service stopped", 31 | ))), 32 | Poll::Pending => Poll::Pending, 33 | } 34 | } 35 | } 36 | 37 | impl PeerSocket { 38 | fn on_call(&mut self, req: AnyRequest) -> PeerSocketResponseFuture { 39 | let (tx, rx) = oneshot::channel(); 40 | let _: Result<_, _> = self.send(MainLoopEvent::OutgoingRequest(req, tx)); 41 | PeerSocketResponseFuture { rx } 42 | } 43 | 44 | fn on_notify(&mut self, notif: AnyNotification) -> ControlFlow> { 45 | match self.send(MainLoopEvent::Outgoing(Message::Notification(notif))) { 46 | Ok(()) => ControlFlow::Continue(()), 47 | Err(err) => ControlFlow::Break(Err(err)), 48 | } 49 | } 50 | 51 | fn on_emit(&mut self, event: AnyEvent) -> ControlFlow> { 52 | match self.send(MainLoopEvent::Any(event)) { 53 | Ok(()) => ControlFlow::Continue(()), 54 | Err(err) => ControlFlow::Break(Err(err)), 55 | } 56 | } 57 | } 58 | 59 | macro_rules! define_socket_wrappers { 60 | ($($ty:ty),*) => { 61 | $( 62 | impl Service for $ty { 63 | type Response = serde_json::Value; 64 | type Error = ResponseError; 65 | type Future = PeerSocketResponseFuture; 66 | 67 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 68 | // `*Socket` has an unbounded buffer, thus is always ready. 69 | Poll::Ready(Ok(())) 70 | } 71 | 72 | fn call(&mut self, req: AnyRequest) -> Self::Future { 73 | self.0.on_call(req) 74 | } 75 | } 76 | 77 | impl LspService for $ty { 78 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 79 | self.0.on_notify(notif) 80 | } 81 | 82 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 83 | self.0.on_emit(event) 84 | } 85 | } 86 | )* 87 | }; 88 | } 89 | 90 | define_socket_wrappers!(ClientSocket, ServerSocket); 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous [Language Server Protocol (LSP)][lsp] framework based on [tower]. 2 | //! 3 | //! See project [README] for a general overview. 4 | //! 5 | //! [README]: https://github.com/oxalica/async-lsp#readme 6 | //! [lsp]: https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/ 7 | //! [tower]: https://github.com/tower-rs/tower 8 | //! 9 | //! This project is centered at a core service trait [`LspService`] for either Language Servers or 10 | //! Language Clients. The main loop driver [`MainLoop`] executes the service. The additional 11 | //! features, called middleware, are pluggable can be layered using the [`tower_layer`] 12 | //! abstraction. This crate defines several common middlewares for various mandatory or optional 13 | //! LSP functionalities, see their documentations for details. 14 | //! - [`concurrency::Concurrency`]: Incoming request multiplexing and cancellation. 15 | //! - [`panic::CatchUnwind`]: Turn panics into errors. 16 | //! - [`tracing::Tracing`]: Logger spans with methods instrumenting handlers. 17 | //! - [`server::Lifecycle`]: Server initialization, shutting down, and exit handling. 18 | //! - [`client_monitor::ClientProcessMonitor`]: Client process monitor. 19 | //! - [`router::Router`]: "Root" service to dispatch requests, notifications and events. 20 | //! 21 | //! Users are free to select and layer middlewares to run a Language Server or Language Client. 22 | //! They can also implement their own middlewares for like timeout, metering, request 23 | //! transformation and etc. 24 | //! 25 | //! ## Usages 26 | //! 27 | //! There are two main ways to define a [`Router`](router::Router) root service: one is via its 28 | //! builder API, and the other is to construct via implementing the omnitrait [`LanguageServer`] or 29 | //! [`LanguageClient`] for a state struct. The former is more flexible, while the latter has a 30 | //! more similar API as [`tower-lsp`](https://crates.io/crates/tower-lsp). 31 | //! 32 | //! The examples for both builder-API and omnitrait, cross Language Server and Language Client, can 33 | //! be seen under 34 | #![doc = concat!("[`examples`](https://github.com/oxalica/async-lsp/tree/v", env!("CARGO_PKG_VERSION") , "/examples)")] 35 | //! directory. 36 | //! 37 | //! ## Cargo features 38 | //! 39 | //! - `client-monitor`: Client process monitor middleware [`client_monitor`]. 40 | //! *Enabled by default.* 41 | //! - `omni-trait`: Mega traits of all standard requests and notifications, namely 42 | //! [`LanguageServer`] and [`LanguageClient`]. 43 | //! *Enabled by default.* 44 | //! - `stdio`: Utilities to deal with pipe-like stdin/stdout communication channel for Language 45 | //! Servers. 46 | //! *Enabled by default.* 47 | //! - `tracing`: Integration with crate [`tracing`][::tracing] and the [`tracing`] middleware. 48 | //! *Enabled by default.* 49 | //! - `forward`: Impl [`LspService`] for `{Client,Server}Socket`. This collides some method names 50 | //! but allows easy service forwarding. See `examples/inspector.rs` for a possible use case. 51 | //! *Disabled by default.* 52 | //! - `tokio`: Enable compatible methods for [`tokio`](https://crates.io/crates/tokio) runtime. 53 | //! *Disabled by default.* 54 | #![cfg_attr(docsrs, feature(doc_cfg))] 55 | #![warn(missing_docs)] 56 | use std::any::{type_name, Any, TypeId}; 57 | use std::collections::HashMap; 58 | use std::future::{poll_fn, Future}; 59 | use std::marker::PhantomData; 60 | use std::ops::ControlFlow; 61 | use std::pin::Pin; 62 | use std::task::{ready, Context, Poll}; 63 | use std::{fmt, io}; 64 | 65 | use futures::channel::{mpsc, oneshot}; 66 | use futures::io::BufReader; 67 | use futures::stream::FuturesUnordered; 68 | use futures::{ 69 | pin_mut, select_biased, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, 70 | AsyncWriteExt, FutureExt, SinkExt, StreamExt, 71 | }; 72 | use lsp_types::notification::Notification; 73 | use lsp_types::request::Request; 74 | use lsp_types::NumberOrString; 75 | use pin_project_lite::pin_project; 76 | use serde::de::DeserializeOwned; 77 | use serde::{Deserialize, Serialize}; 78 | use serde_json::Value as JsonValue; 79 | use thiserror::Error; 80 | use tower_service::Service; 81 | 82 | /// Re-export of the [`lsp_types`] dependency of this crate. 83 | pub use lsp_types; 84 | 85 | macro_rules! define_getters { 86 | (impl[$($generic:tt)*] $ty:ty, $field:ident : $field_ty:ty) => { 87 | impl<$($generic)*> $ty { 88 | /// Get a reference to the inner service. 89 | #[must_use] 90 | pub fn get_ref(&self) -> &$field_ty { 91 | &self.$field 92 | } 93 | 94 | /// Get a mutable reference to the inner service. 95 | #[must_use] 96 | pub fn get_mut(&mut self) -> &mut $field_ty { 97 | &mut self.$field 98 | } 99 | 100 | /// Consume self, returning the inner service. 101 | #[must_use] 102 | pub fn into_inner(self) -> $field_ty { 103 | self.$field 104 | } 105 | } 106 | }; 107 | } 108 | 109 | pub mod concurrency; 110 | pub mod panic; 111 | pub mod router; 112 | pub mod server; 113 | 114 | #[cfg(feature = "forward")] 115 | #[cfg_attr(docsrs, doc(cfg(feature = "forward")))] 116 | mod forward; 117 | 118 | #[cfg(feature = "client-monitor")] 119 | #[cfg_attr(docsrs, doc(cfg(feature = "client-monitor")))] 120 | pub mod client_monitor; 121 | 122 | #[cfg(all(feature = "stdio", unix))] 123 | #[cfg_attr(docsrs, doc(cfg(all(feature = "stdio", unix))))] 124 | pub mod stdio; 125 | 126 | #[cfg(feature = "tracing")] 127 | #[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] 128 | pub mod tracing; 129 | 130 | #[cfg(feature = "omni-trait")] 131 | mod omni_trait; 132 | #[cfg(feature = "omni-trait")] 133 | #[cfg_attr(docsrs, doc(cfg(feature = "omni-trait")))] 134 | pub use omni_trait::{LanguageClient, LanguageServer}; 135 | 136 | /// A convenient type alias for `Result` with `E` = [`enum@crate::Error`]. 137 | pub type Result = std::result::Result; 138 | 139 | /// Possible errors. 140 | #[derive(Debug, thiserror::Error)] 141 | #[non_exhaustive] 142 | pub enum Error { 143 | /// The service main loop stopped. 144 | #[error("service stopped")] 145 | ServiceStopped, 146 | /// The peer replies undecodable or invalid responses. 147 | #[error("deserialization failed: {0}")] 148 | Deserialize(#[from] serde_json::Error), 149 | /// The peer replies an error. 150 | #[error("{0}")] 151 | Response(#[from] ResponseError), 152 | /// The peer violates the Language Server Protocol. 153 | #[error("protocol error: {0}")] 154 | Protocol(String), 155 | /// Input/output errors from the underlying channels. 156 | #[error("{0}")] 157 | Io(#[from] io::Error), 158 | /// The underlying channel reached EOF (end of file). 159 | #[error("the underlying channel reached EOF")] 160 | Eof, 161 | /// No handlers for events or mandatory notifications (not starting with `$/`). 162 | /// 163 | /// Will not occur when catch-all handlers ([`router::Router::unhandled_event`] and 164 | /// [`router::Router::unhandled_notification`]) are installed. 165 | #[error("{0}")] 166 | Routing(String), 167 | } 168 | 169 | /// The core service abstraction, representing either a Language Server or Language Client. 170 | pub trait LspService: Service { 171 | /// The handler of [LSP notifications](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage). 172 | /// 173 | /// Notifications are delivered in order and synchronously. This is mandatory since they can 174 | /// change the interpretation of later notifications or requests. 175 | /// 176 | /// # Return 177 | /// 178 | /// The return value decides the action to either break or continue the main loop. 179 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow>; 180 | 181 | /// The handler of an arbitrary [`AnyEvent`]. 182 | /// 183 | /// Events are emitted by users or middlewares via [`ClientSocket::emit`] or 184 | /// [`ServerSocket::emit`], for user-defined purposes. Events are delivered in order and 185 | /// synchronously. 186 | /// 187 | /// # Return 188 | /// 189 | /// The return value decides the action to either break or continue the main loop. 190 | fn emit(&mut self, event: AnyEvent) -> ControlFlow>; 191 | } 192 | 193 | /// A JSON-RPC error code. 194 | /// 195 | /// Codes defined and/or used by LSP are defined as associated constants, eg. 196 | /// [`ErrorCode::REQUEST_FAILED`]. 197 | /// 198 | /// See: 199 | /// 200 | #[derive( 201 | Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Error, 202 | )] 203 | #[error("jsonrpc error {0}")] 204 | pub struct ErrorCode(pub i32); 205 | 206 | impl From for ErrorCode { 207 | fn from(i: i32) -> Self { 208 | Self(i) 209 | } 210 | } 211 | 212 | impl ErrorCode { 213 | /// Invalid JSON was received by the server. An error occurred on the server while parsing the 214 | /// JSON text. 215 | /// 216 | /// Defined by [JSON-RPC](https://www.jsonrpc.org/specification#error_object). 217 | pub const PARSE_ERROR: Self = Self(-32700); 218 | 219 | /// The JSON sent is not a valid Request object. 220 | /// 221 | /// Defined by [JSON-RPC](https://www.jsonrpc.org/specification#error_object). 222 | pub const INVALID_REQUEST: Self = Self(-32600); 223 | 224 | /// The method does not exist / is not available. 225 | /// 226 | /// Defined by [JSON-RPC](https://www.jsonrpc.org/specification#error_object). 227 | pub const METHOD_NOT_FOUND: Self = Self(-32601); 228 | 229 | /// Invalid method parameter(s). 230 | /// 231 | /// Defined by [JSON-RPC](https://www.jsonrpc.org/specification#error_object). 232 | pub const INVALID_PARAMS: Self = Self(-32602); 233 | 234 | /// Internal JSON-RPC error. 235 | /// 236 | /// Defined by [JSON-RPC](https://www.jsonrpc.org/specification#error_object). 237 | pub const INTERNAL_ERROR: Self = Self(-32603); 238 | 239 | /// This is the start range of JSON-RPC reserved error codes. 240 | /// It doesn't denote a real error code. No LSP error codes should 241 | /// be defined between the start and end range. For backwards 242 | /// compatibility the `ServerNotInitialized` and the `UnknownErrorCode` 243 | /// are left in the range. 244 | /// 245 | /// @since 3.16.0 246 | pub const JSONRPC_RESERVED_ERROR_RANGE_START: Self = Self(-32099); 247 | 248 | /// Error code indicating that a server received a notification or 249 | /// request before the server has received the `initialize` request. 250 | pub const SERVER_NOT_INITIALIZED: Self = Self(-32002); 251 | 252 | /// (Defined by LSP specification without description) 253 | pub const UNKNOWN_ERROR_CODE: Self = Self(-32001); 254 | 255 | /// This is the end range of JSON-RPC reserved error codes. 256 | /// It doesn't denote a real error code. 257 | /// 258 | /// @since 3.16.0 259 | pub const JSONRPC_RESERVED_ERROR_RANGE_END: Self = Self(-32000); 260 | 261 | /// This is the start range of LSP reserved error codes. 262 | /// It doesn't denote a real error code. 263 | /// 264 | /// @since 3.16.0 265 | pub const LSP_RESERVED_ERROR_RANGE_START: Self = Self(-32899); 266 | 267 | /// A request failed but it was syntactically correct, e.g the 268 | /// method name was known and the parameters were valid. The error 269 | /// message should contain human readable information about why 270 | /// the request failed. 271 | /// 272 | /// @since 3.17.0 273 | pub const REQUEST_FAILED: Self = Self(-32803); 274 | 275 | /// The server cancelled the request. This error code should 276 | /// only be used for requests that explicitly support being 277 | /// server cancellable. 278 | /// 279 | /// @since 3.17.0 280 | pub const SERVER_CANCELLED: Self = Self(-32802); 281 | 282 | /// The server detected that the content of a document got 283 | /// modified outside normal conditions. A server should 284 | /// NOT send this error code if it detects a content change 285 | /// in it unprocessed messages. The result even computed 286 | /// on an older state might still be useful for the client. 287 | /// 288 | /// If a client decides that a result is not of any use anymore 289 | /// the client should cancel the request. 290 | pub const CONTENT_MODIFIED: Self = Self(-32801); 291 | 292 | /// The client has canceled a request and a server as detected 293 | /// the cancel. 294 | pub const REQUEST_CANCELLED: Self = Self(-32800); 295 | 296 | /// This is the end range of LSP reserved error codes. 297 | /// It doesn't denote a real error code. 298 | /// 299 | /// @since 3.16.0 300 | pub const LSP_RESERVED_ERROR_RANGE_END: Self = Self(-32800); 301 | } 302 | 303 | /// The identifier of requests and responses. 304 | /// 305 | /// Though `null` is technically a valid id for responses, we reject it since it hardly makes sense 306 | /// for valid communication. 307 | pub type RequestId = NumberOrString; 308 | 309 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 310 | struct RawMessage { 311 | jsonrpc: RpcVersion, 312 | #[serde(flatten)] 313 | inner: T, 314 | } 315 | 316 | impl RawMessage { 317 | fn new(inner: T) -> Self { 318 | Self { 319 | jsonrpc: RpcVersion::V2, 320 | inner, 321 | } 322 | } 323 | } 324 | 325 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 326 | enum RpcVersion { 327 | #[serde(rename = "2.0")] 328 | V2, 329 | } 330 | 331 | #[derive(Debug, Clone, Serialize, Deserialize)] 332 | #[serde(untagged)] 333 | enum Message { 334 | Request(AnyRequest), 335 | Response(AnyResponse), 336 | Notification(AnyNotification), 337 | } 338 | 339 | /// A dynamic runtime [LSP request](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage). 340 | #[derive(Debug, Clone, Serialize, Deserialize)] 341 | #[non_exhaustive] 342 | pub struct AnyRequest { 343 | /// The request id. 344 | pub id: RequestId, 345 | /// The method to be invoked. 346 | pub method: String, 347 | /// The method's params. 348 | #[serde(default)] 349 | #[serde(skip_serializing_if = "serde_json::Value::is_null")] 350 | pub params: serde_json::Value, 351 | } 352 | 353 | /// A dynamic runtime [LSP notification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage). 354 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 355 | #[non_exhaustive] 356 | pub struct AnyNotification { 357 | /// The method to be invoked. 358 | pub method: String, 359 | /// The notification's params. 360 | #[serde(default)] 361 | #[serde(skip_serializing_if = "serde_json::Value::is_null")] 362 | pub params: JsonValue, 363 | } 364 | 365 | /// A dynamic runtime response. 366 | #[derive(Debug, Clone, Serialize, Deserialize)] 367 | #[non_exhaustive] 368 | struct AnyResponse { 369 | id: RequestId, 370 | #[serde(skip_serializing_if = "Option::is_none")] 371 | result: Option, 372 | #[serde(skip_serializing_if = "Option::is_none")] 373 | error: Option, 374 | } 375 | 376 | /// The error object in case a request fails. 377 | /// 378 | /// See: 379 | /// 380 | #[derive(Debug, Clone, Serialize, Deserialize, Error)] 381 | #[non_exhaustive] 382 | #[error("{message} ({code})")] 383 | pub struct ResponseError { 384 | /// A number indicating the error type that occurred. 385 | pub code: ErrorCode, 386 | /// A string providing a short description of the error. 387 | pub message: String, 388 | /// A primitive or structured value that contains additional 389 | /// information about the error. Can be omitted. 390 | pub data: Option, 391 | } 392 | 393 | impl ResponseError { 394 | /// Create a new error object with a JSON-RPC error code and a message. 395 | #[must_use] 396 | pub fn new(code: ErrorCode, message: impl fmt::Display) -> Self { 397 | Self { 398 | code, 399 | message: message.to_string(), 400 | data: None, 401 | } 402 | } 403 | 404 | /// Create a new error object with a JSON-RPC error code, a message, and any additional data. 405 | #[must_use] 406 | pub fn new_with_data(code: ErrorCode, message: impl fmt::Display, data: JsonValue) -> Self { 407 | Self { 408 | code, 409 | message: message.to_string(), 410 | data: Some(data), 411 | } 412 | } 413 | } 414 | 415 | impl Message { 416 | const CONTENT_LENGTH: &'static str = "Content-Length"; 417 | 418 | async fn read(mut reader: impl AsyncBufRead + Unpin) -> Result { 419 | let mut line = String::new(); 420 | let mut content_len = None; 421 | loop { 422 | line.clear(); 423 | reader.read_line(&mut line).await?; 424 | if line.is_empty() { 425 | return Err(Error::Eof); 426 | } 427 | if line == "\r\n" { 428 | break; 429 | } 430 | // NB. LSP spec is stricter than HTTP spec, the spaces here is required and it's not 431 | // explicitly permitted to include extra spaces. We reject them here. 432 | let (name, value) = line 433 | .strip_suffix("\r\n") 434 | .and_then(|line| line.split_once(": ")) 435 | .ok_or_else(|| Error::Protocol(format!("Invalid header: {line:?}")))?; 436 | if name.eq_ignore_ascii_case(Self::CONTENT_LENGTH) { 437 | let value = value 438 | .parse::() 439 | .map_err(|_| Error::Protocol(format!("Invalid content-length: {value}")))?; 440 | content_len = Some(value); 441 | } 442 | } 443 | let content_len = 444 | content_len.ok_or_else(|| Error::Protocol("Missing content-length".into()))?; 445 | let mut buf = vec![0u8; content_len]; 446 | reader.read_exact(&mut buf).await?; 447 | #[cfg(feature = "tracing")] 448 | ::tracing::trace!(msg = %String::from_utf8_lossy(&buf), "incoming"); 449 | let msg = serde_json::from_slice::>(&buf)?; 450 | Ok(msg.inner) 451 | } 452 | 453 | async fn write(&self, mut writer: impl AsyncWrite + Unpin) -> Result<()> { 454 | let buf = serde_json::to_string(&RawMessage::new(self))?; 455 | #[cfg(feature = "tracing")] 456 | ::tracing::trace!(msg = %buf, "outgoing"); 457 | writer 458 | .write_all(format!("{}: {}\r\n\r\n", Self::CONTENT_LENGTH, buf.len()).as_bytes()) 459 | .await?; 460 | writer.write_all(buf.as_bytes()).await?; 461 | writer.flush().await?; 462 | Ok(()) 463 | } 464 | } 465 | 466 | /// Service main loop driver for either Language Servers or Language Clients. 467 | pub struct MainLoop { 468 | service: S, 469 | rx: mpsc::UnboundedReceiver, 470 | outgoing_id: i32, 471 | outgoing: HashMap>, 472 | tasks: FuturesUnordered>, 473 | } 474 | 475 | enum MainLoopEvent { 476 | Outgoing(Message), 477 | OutgoingRequest(AnyRequest, oneshot::Sender), 478 | Any(AnyEvent), 479 | } 480 | 481 | define_getters!(impl[S: LspService] MainLoop, service: S); 482 | 483 | impl MainLoop 484 | where 485 | S: LspService, 486 | ResponseError: From, 487 | { 488 | /// Create a Language Server main loop. 489 | #[must_use] 490 | pub fn new_server(builder: impl FnOnce(ClientSocket) -> S) -> (Self, ClientSocket) { 491 | let (this, socket) = Self::new(|socket| builder(ClientSocket(socket))); 492 | (this, ClientSocket(socket)) 493 | } 494 | 495 | /// Create a Language Client main loop. 496 | #[must_use] 497 | pub fn new_client(builder: impl FnOnce(ServerSocket) -> S) -> (Self, ServerSocket) { 498 | let (this, socket) = Self::new(|socket| builder(ServerSocket(socket))); 499 | (this, ServerSocket(socket)) 500 | } 501 | 502 | fn new(builder: impl FnOnce(PeerSocket) -> S) -> (Self, PeerSocket) { 503 | let (tx, rx) = mpsc::unbounded(); 504 | let socket = PeerSocket { tx }; 505 | let this = Self { 506 | service: builder(socket.clone()), 507 | rx, 508 | outgoing_id: 0, 509 | outgoing: HashMap::new(), 510 | tasks: FuturesUnordered::new(), 511 | }; 512 | (this, socket) 513 | } 514 | 515 | /// Drive the service main loop to provide the service. 516 | /// 517 | /// Shortcut to [`MainLoop::run`] that accept an `impl AsyncRead` and implicit wrap it in a 518 | /// [`BufReader`]. 519 | // Documented in `Self::run`. 520 | #[allow(clippy::missing_errors_doc)] 521 | pub async fn run_buffered(self, input: impl AsyncRead, output: impl AsyncWrite) -> Result<()> { 522 | self.run(BufReader::new(input), output).await 523 | } 524 | 525 | /// Drive the service main loop to provide the service. 526 | /// 527 | /// # Errors 528 | /// 529 | /// - `Error::Io` when the underlying `input` or `output` raises an error. 530 | /// - `Error::Deserialize` when the peer sends undecodable or invalid message. 531 | /// - `Error::Protocol` when the peer violates Language Server Protocol. 532 | /// - Other errors raised from service handlers. 533 | pub async fn run(mut self, input: impl AsyncBufRead, output: impl AsyncWrite) -> Result<()> { 534 | pin_mut!(input, output); 535 | let incoming = futures::stream::unfold(input, |mut input| async move { 536 | Some((Message::read(&mut input).await, input)) 537 | }); 538 | let outgoing = futures::sink::unfold(output, |mut output, msg| async move { 539 | Message::write(&msg, &mut output).await.map(|()| output) 540 | }); 541 | pin_mut!(incoming, outgoing); 542 | 543 | let mut flush_fut = futures::future::Fuse::terminated(); 544 | let ret = loop { 545 | // Outgoing > internal > incoming. 546 | // Preference on outgoing data provides back pressure in case of 547 | // flooding incoming requests. 548 | let ctl = select_biased! { 549 | // Concurrently flush out the previous message. 550 | ret = flush_fut => { ret?; continue; } 551 | 552 | resp = self.tasks.select_next_some() => ControlFlow::Continue(Some(Message::Response(resp))), 553 | event = self.rx.next() => self.dispatch_event(event.expect("Sender is alive")), 554 | msg = incoming.next() => { 555 | let dispatch_fut = self.dispatch_message(msg.expect("Never ends")?).fuse(); 556 | pin_mut!(dispatch_fut); 557 | // NB. Concurrently wait for `poll_ready`, and write out the last message. 558 | // If the service is waiting for client's response of the last request, while 559 | // the last message is not delivered on the first write, it can deadlock. 560 | loop { 561 | select_biased! { 562 | // Dispatch first. It usually succeeds immediately for non-requests, 563 | // and the service is hardly busy. 564 | ctl = dispatch_fut => break ctl, 565 | ret = flush_fut => { ret?; continue } 566 | } 567 | } 568 | } 569 | }; 570 | let msg = match ctl { 571 | ControlFlow::Continue(Some(msg)) => msg, 572 | ControlFlow::Continue(None) => continue, 573 | ControlFlow::Break(ret) => break ret, 574 | }; 575 | // Flush the previous one and load a new message to send. 576 | outgoing.feed(msg).await?; 577 | flush_fut = outgoing.flush().fuse(); 578 | }; 579 | 580 | // Flush the last message. It is enqueued before the event returning `ControlFlow::Break`. 581 | // To preserve the order at best effort, we send it before exiting the main loop. 582 | // But the more significant `ControlFlow::Break` error will override the flushing error, 583 | // if there is any. 584 | let flush_ret = outgoing.close().await; 585 | ret.and(flush_ret) 586 | } 587 | 588 | async fn dispatch_message(&mut self, msg: Message) -> ControlFlow, Option> { 589 | match msg { 590 | Message::Request(req) => { 591 | if let Err(err) = poll_fn(|cx| self.service.poll_ready(cx)).await { 592 | let resp = AnyResponse { 593 | id: req.id, 594 | result: None, 595 | error: Some(err.into()), 596 | }; 597 | return ControlFlow::Continue(Some(Message::Response(resp))); 598 | } 599 | let id = req.id.clone(); 600 | let fut = self.service.call(req); 601 | self.tasks.push(RequestFuture { fut, id: Some(id) }); 602 | } 603 | Message::Response(resp) => { 604 | if let Some(resp_tx) = self.outgoing.remove(&resp.id) { 605 | // The result may be ignored. 606 | let _: Result<_, _> = resp_tx.send(resp); 607 | } 608 | } 609 | Message::Notification(notif) => { 610 | self.service.notify(notif)?; 611 | } 612 | } 613 | ControlFlow::Continue(None) 614 | } 615 | 616 | fn dispatch_event(&mut self, event: MainLoopEvent) -> ControlFlow, Option> { 617 | match event { 618 | MainLoopEvent::OutgoingRequest(mut req, resp_tx) => { 619 | req.id = RequestId::Number(self.outgoing_id); 620 | assert!(self.outgoing.insert(req.id.clone(), resp_tx).is_none()); 621 | self.outgoing_id += 1; 622 | ControlFlow::Continue(Some(Message::Request(req))) 623 | } 624 | MainLoopEvent::Outgoing(msg) => ControlFlow::Continue(Some(msg)), 625 | MainLoopEvent::Any(event) => { 626 | self.service.emit(event)?; 627 | ControlFlow::Continue(None) 628 | } 629 | } 630 | } 631 | } 632 | 633 | pin_project! { 634 | struct RequestFuture { 635 | #[pin] 636 | fut: Fut, 637 | id: Option, 638 | } 639 | } 640 | 641 | impl Future for RequestFuture 642 | where 643 | Fut: Future>, 644 | ResponseError: From, 645 | { 646 | type Output = AnyResponse; 647 | 648 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 649 | let this = self.project(); 650 | let (mut result, mut error) = (None, None); 651 | match ready!(this.fut.poll(cx)) { 652 | Ok(v) => result = Some(v), 653 | Err(err) => error = Some(err.into()), 654 | } 655 | Poll::Ready(AnyResponse { 656 | id: this.id.take().expect("Future is consumed"), 657 | result, 658 | error, 659 | }) 660 | } 661 | } 662 | 663 | macro_rules! impl_socket_wrapper { 664 | ($name:ident) => { 665 | impl $name { 666 | /// Create a closed socket outside a main loop. Any interaction will immediately return 667 | /// an error of [`Error::ServiceStopped`]. 668 | /// 669 | /// This works as a placeholder where a socket is required but actually unused. 670 | /// 671 | /// # Note 672 | /// 673 | /// To prevent accidental misusages, this method is NOT implemented as 674 | /// [`Default::default`] intentionally. 675 | #[must_use] 676 | pub fn new_closed() -> Self { 677 | Self(PeerSocket::new_closed()) 678 | } 679 | 680 | /// Send a request to the peer and wait for its response. 681 | /// 682 | /// # Errors 683 | /// - [`Error::ServiceStopped`] when the service main loop stopped. 684 | /// - [`Error::Response`] when the peer replies an error. 685 | pub async fn request(&self, params: R::Params) -> Result { 686 | self.0.request::(params).await 687 | } 688 | 689 | /// Send a notification to the peer and wait for its response. 690 | /// 691 | /// This is done asynchronously. An `Ok` result indicates the message is successfully 692 | /// queued, but may not be sent to the peer yet. 693 | /// 694 | /// # Errors 695 | /// - [`Error::ServiceStopped`] when the service main loop stopped. 696 | pub fn notify(&self, params: N::Params) -> Result<()> { 697 | self.0.notify::(params) 698 | } 699 | 700 | /// Emit an arbitrary loopback event object to the service handler. 701 | /// 702 | /// This is done asynchronously. An `Ok` result indicates the message is successfully 703 | /// queued, but may not be processed yet. 704 | /// 705 | /// # Errors 706 | /// - [`Error::ServiceStopped`] when the service main loop stopped. 707 | pub fn emit(&self, event: E) -> Result<()> { 708 | self.0.emit::(event) 709 | } 710 | } 711 | }; 712 | } 713 | 714 | /// The socket for Language Server to communicate with the Language Client peer. 715 | #[derive(Debug, Clone)] 716 | pub struct ClientSocket(PeerSocket); 717 | impl_socket_wrapper!(ClientSocket); 718 | 719 | /// The socket for Language Client to communicate with the Language Server peer. 720 | #[derive(Debug, Clone)] 721 | pub struct ServerSocket(PeerSocket); 722 | impl_socket_wrapper!(ServerSocket); 723 | 724 | #[derive(Debug, Clone)] 725 | struct PeerSocket { 726 | tx: mpsc::UnboundedSender, 727 | } 728 | 729 | impl PeerSocket { 730 | fn new_closed() -> Self { 731 | let (tx, _rx) = mpsc::unbounded(); 732 | Self { tx } 733 | } 734 | 735 | fn send(&self, v: MainLoopEvent) -> Result<()> { 736 | self.tx.unbounded_send(v).map_err(|_| Error::ServiceStopped) 737 | } 738 | 739 | fn request(&self, params: R::Params) -> PeerSocketRequestFuture { 740 | let req = AnyRequest { 741 | id: RequestId::Number(0), 742 | method: R::METHOD.into(), 743 | params: serde_json::to_value(params).expect("Failed to serialize"), 744 | }; 745 | let (tx, rx) = oneshot::channel(); 746 | // If this fails, the oneshot channel will also be closed, and it is handled by 747 | // `PeerSocketRequestFuture`. 748 | let _: Result<_, _> = self.send(MainLoopEvent::OutgoingRequest(req, tx)); 749 | PeerSocketRequestFuture { 750 | rx, 751 | _marker: PhantomData, 752 | } 753 | } 754 | 755 | fn notify(&self, params: N::Params) -> Result<()> { 756 | let notif = AnyNotification { 757 | method: N::METHOD.into(), 758 | params: serde_json::to_value(params).expect("Failed to serialize"), 759 | }; 760 | self.send(MainLoopEvent::Outgoing(Message::Notification(notif))) 761 | } 762 | 763 | pub fn emit(&self, event: E) -> Result<()> { 764 | self.send(MainLoopEvent::Any(AnyEvent::new(event))) 765 | } 766 | } 767 | 768 | struct PeerSocketRequestFuture { 769 | rx: oneshot::Receiver, 770 | _marker: PhantomData T>, 771 | } 772 | 773 | impl Future for PeerSocketRequestFuture { 774 | type Output = Result; 775 | 776 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 777 | let resp = ready!(Pin::new(&mut self.rx) 778 | .poll(cx) 779 | .map_err(|_| Error::ServiceStopped))?; 780 | Poll::Ready(match resp.error { 781 | None => Ok(serde_json::from_value(resp.result.unwrap_or_default())?), 782 | Some(err) => Err(Error::Response(err)), 783 | }) 784 | } 785 | } 786 | 787 | /// A dynamic runtime event. 788 | /// 789 | /// This is a wrapper of `Box`, but saves the underlying type name for better 790 | /// `Debug` impl. 791 | /// 792 | /// See [`LspService::emit`] for usages of this type. 793 | pub struct AnyEvent { 794 | inner: Box, 795 | type_name: &'static str, 796 | } 797 | 798 | impl fmt::Debug for AnyEvent { 799 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 800 | f.debug_struct("AnyEvent") 801 | .field("type_name", &self.type_name) 802 | .finish_non_exhaustive() 803 | } 804 | } 805 | 806 | impl AnyEvent { 807 | #[must_use] 808 | fn new(v: T) -> Self { 809 | AnyEvent { 810 | inner: Box::new(v), 811 | type_name: type_name::(), 812 | } 813 | } 814 | 815 | #[must_use] 816 | fn inner_type_id(&self) -> TypeId { 817 | // Call `type_id` on the inner `dyn Any`, not `Box<_> as Any` or `&Box<_> as Any`. 818 | Any::type_id(&*self.inner) 819 | } 820 | 821 | /// Get the underlying type name for debugging purpose. 822 | /// 823 | /// The result string is only meant for debugging. It is not stable and cannot be trusted. 824 | #[must_use] 825 | pub fn type_name(&self) -> &'static str { 826 | self.type_name 827 | } 828 | 829 | /// Returns `true` if the inner type is the same as `T`. 830 | #[must_use] 831 | pub fn is(&self) -> bool { 832 | self.inner.is::() 833 | } 834 | 835 | /// Returns some reference to the inner value if it is of type `T`, or `None` if it isn't. 836 | #[must_use] 837 | pub fn downcast_ref(&self) -> Option<&T> { 838 | self.inner.downcast_ref::() 839 | } 840 | 841 | /// Returns some mutable reference to the inner value if it is of type `T`, or `None` if it 842 | /// isn't. 843 | #[must_use] 844 | pub fn downcast_mut(&mut self) -> Option<&mut T> { 845 | self.inner.downcast_mut::() 846 | } 847 | 848 | /// Attempt to downcast it to a concrete type. 849 | /// 850 | /// # Errors 851 | /// 852 | /// Returns `self` if the type mismatches. 853 | pub fn downcast(self) -> Result { 854 | match self.inner.downcast::() { 855 | Ok(v) => Ok(*v), 856 | Err(inner) => Err(Self { 857 | inner, 858 | type_name: self.type_name, 859 | }), 860 | } 861 | } 862 | } 863 | 864 | #[cfg(test)] 865 | mod tests { 866 | use super::*; 867 | 868 | fn _main_loop_future_is_send( 869 | f: MainLoop, 870 | input: impl AsyncBufRead + Send, 871 | output: impl AsyncWrite + Send, 872 | ) -> impl Send 873 | where 874 | S: LspService + Send, 875 | S::Future: Send, 876 | S::Error: From + Send, 877 | ResponseError: From, 878 | { 879 | f.run(input, output) 880 | } 881 | 882 | #[tokio::test] 883 | async fn closed_client_socket() { 884 | let socket = ClientSocket::new_closed(); 885 | assert!(matches!( 886 | socket.notify::(()), 887 | Err(Error::ServiceStopped) 888 | )); 889 | assert!(matches!( 890 | socket.request::(()).await, 891 | Err(Error::ServiceStopped) 892 | )); 893 | assert!(matches!(socket.emit(42i32), Err(Error::ServiceStopped))); 894 | } 895 | 896 | #[tokio::test] 897 | async fn closed_server_socket() { 898 | let socket = ServerSocket::new_closed(); 899 | assert!(matches!( 900 | socket.notify::(()), 901 | Err(Error::ServiceStopped) 902 | )); 903 | assert!(matches!( 904 | socket.request::(()).await, 905 | Err(Error::ServiceStopped) 906 | )); 907 | assert!(matches!(socket.emit(42i32), Err(Error::ServiceStopped))); 908 | } 909 | 910 | #[test] 911 | fn any_event() { 912 | #[derive(Debug, Clone, PartialEq, Eq)] 913 | struct MyEvent(T); 914 | 915 | let event = MyEvent("hello".to_owned()); 916 | let mut any_event = AnyEvent::new(event.clone()); 917 | assert!(any_event.type_name().contains("MyEvent")); 918 | 919 | assert!(!any_event.is::()); 920 | assert!(!any_event.is::>()); 921 | assert!(any_event.is::>()); 922 | 923 | assert_eq!(any_event.downcast_ref::(), None); 924 | assert_eq!(any_event.downcast_ref::>(), Some(&event)); 925 | 926 | assert_eq!(any_event.downcast_mut::>(), None); 927 | any_event.downcast_mut::>().unwrap().0 += " world"; 928 | 929 | let any_event = any_event.downcast::<()>().unwrap_err(); 930 | let inner = any_event.downcast::>().unwrap(); 931 | assert_eq!(inner.0, "hello world"); 932 | } 933 | } 934 | -------------------------------------------------------------------------------- /src/omni_trait.rs: -------------------------------------------------------------------------------- 1 | use std::future::ready; 2 | use std::ops::ControlFlow; 3 | 4 | use futures::future::BoxFuture; 5 | use lsp_types::notification::{self, Notification}; 6 | use lsp_types::request::{self, Request}; 7 | use lsp_types::{lsp_notification, lsp_request}; 8 | 9 | use crate::router::Router; 10 | use crate::{ClientSocket, ErrorCode, ResponseError, Result, ServerSocket}; 11 | 12 | use self::sealed::NotifyResult; 13 | 14 | mod sealed { 15 | use super::*; 16 | 17 | pub trait NotifyResult { 18 | fn fallback() -> Self; 19 | } 20 | 21 | impl NotifyResult for ControlFlow> { 22 | fn fallback() -> Self { 23 | if N::METHOD.starts_with("$/") 24 | || N::METHOD == notification::Exit::METHOD 25 | || N::METHOD == notification::Initialized::METHOD 26 | { 27 | ControlFlow::Continue(()) 28 | } else { 29 | ControlFlow::Break(Err(crate::Error::Routing(format!( 30 | "Unhandled notification: {}", 31 | N::METHOD, 32 | )))) 33 | } 34 | } 35 | } 36 | 37 | impl NotifyResult for crate::Result<()> { 38 | fn fallback() -> Self { 39 | unreachable!() 40 | } 41 | } 42 | } 43 | 44 | type ResponseFuture = BoxFuture<'static, Result<::Result, E>>; 45 | 46 | fn method_not_found() -> ResponseFuture 47 | where 48 | R: Request, 49 | R::Result: Send + 'static, 50 | E: From + Send + 'static, 51 | { 52 | Box::pin(ready(Err(ResponseError { 53 | code: ErrorCode::METHOD_NOT_FOUND, 54 | message: format!("No such method: {}", R::METHOD), 55 | data: None, 56 | } 57 | .into()))) 58 | } 59 | 60 | macro_rules! define { 61 | ( 62 | { $($req_server:tt, $req_server_snake:ident;)* } 63 | { $($notif_server:tt, $notif_server_snake:ident;)* } 64 | { $($req_client:tt, $req_client_snake:ident;)* } 65 | { $($notif_client:tt, $notif_client_snake:ident;)* } 66 | ) => { 67 | define_server! { 68 | { $($req_server_snake, lsp_request!($req_server);)* } 69 | { $($notif_server_snake, lsp_notification!($notif_server);)* } 70 | } 71 | define_client! { 72 | { $($req_client_snake, lsp_request!($req_client);)* } 73 | { $($notif_client_snake, lsp_notification!($notif_client);)* } 74 | } 75 | }; 76 | } 77 | 78 | macro_rules! define_server { 79 | ( 80 | { $($req_snake:ident, $req:ty;)* } 81 | { $($notif_snake:ident, $notif:ty;)* } 82 | ) => { 83 | /// The omnitrait defining all standard LSP requests and notifications supported by 84 | /// [`lsp_types`] for a Language Server. 85 | #[allow(missing_docs)] 86 | pub trait LanguageServer { 87 | /// Should always be defined to [`ResponseError`] for user implementations. 88 | type Error: From + Send + 'static; 89 | /// Should always be defined to `ControlFlow>` for user implementations. 90 | type NotifyResult: NotifyResult; 91 | 92 | // Requests. 93 | 94 | #[must_use] 95 | fn initialize( 96 | &mut self, 97 | params: ::Params, 98 | ) -> ResponseFuture; 99 | 100 | #[must_use] 101 | fn shutdown( 102 | &mut self, 103 | (): ::Params, 104 | ) -> ResponseFuture { 105 | Box::pin(ready(Ok(()))) 106 | } 107 | 108 | $( 109 | #[must_use] 110 | fn $req_snake( 111 | &mut self, 112 | params: <$req as Request>::Params, 113 | ) -> ResponseFuture<$req, Self::Error> { 114 | let _ = params; 115 | method_not_found::<$req, _>() 116 | } 117 | )* 118 | 119 | // Notifications. 120 | 121 | #[must_use] 122 | fn initialized( 123 | &mut self, 124 | params: ::Params, 125 | ) -> Self::NotifyResult { 126 | let _ = params; 127 | Self::NotifyResult::fallback::() 128 | } 129 | 130 | #[must_use] 131 | fn exit( 132 | &mut self, 133 | (): ::Params, 134 | ) -> Self::NotifyResult { 135 | Self::NotifyResult::fallback::() 136 | } 137 | 138 | $( 139 | #[must_use] 140 | fn $notif_snake( 141 | &mut self, 142 | params: <$notif as Notification>::Params, 143 | ) -> Self::NotifyResult { 144 | let _ = params; 145 | Self::NotifyResult::fallback::<$notif>() 146 | } 147 | )* 148 | } 149 | 150 | macro_rules! impl_server_socket { 151 | ($ty:ty) => { 152 | impl LanguageServer for $ty { 153 | type Error = crate::Error; 154 | type NotifyResult = crate::Result<()>; 155 | 156 | // Requests. 157 | 158 | fn initialize( 159 | &mut self, 160 | params: ::Params, 161 | ) -> ResponseFuture { 162 | Box::pin(self.0.request::(params)) 163 | } 164 | 165 | fn shutdown( 166 | &mut self, 167 | (): ::Params, 168 | ) -> ResponseFuture { 169 | Box::pin(self.0.request::(())) 170 | } 171 | 172 | $( 173 | fn $req_snake( 174 | &mut self, 175 | params: <$req as Request>::Params, 176 | ) -> ResponseFuture<$req, Self::Error> { 177 | Box::pin(self.0.request::<$req>(params)) 178 | } 179 | )* 180 | 181 | // Notifications. 182 | 183 | fn initialized( 184 | &mut self, 185 | params: ::Params, 186 | ) -> Self::NotifyResult { 187 | self.notify::(params) 188 | } 189 | 190 | fn exit( 191 | &mut self, 192 | (): ::Params, 193 | ) -> Self::NotifyResult { 194 | self.notify::(()) 195 | } 196 | 197 | $( 198 | fn $notif_snake( 199 | &mut self, 200 | params: <$notif as Notification>::Params, 201 | ) -> Self::NotifyResult { 202 | self.notify::<$notif>(params) 203 | } 204 | )* 205 | } 206 | }; 207 | } 208 | 209 | impl_server_socket!(ServerSocket); 210 | impl_server_socket!(&'_ ServerSocket); 211 | 212 | impl Router 213 | where 214 | S: LanguageServer>>, 215 | ResponseError: From, 216 | { 217 | /// Create a [`Router`] using its implementation of [`LanguageServer`] as handlers. 218 | #[must_use] 219 | pub fn from_language_server(state: S) -> Self { 220 | let mut this = Self::new(state); 221 | this.request::(|state, params| { 222 | let fut = state.initialize(params); 223 | async move { fut.await.map_err(Into::into) } 224 | }); 225 | this.request::(|state, params| { 226 | let fut = state.shutdown(params); 227 | async move { fut.await.map_err(Into::into) } 228 | }); 229 | $(this.request::<$req, _>(|state, params| { 230 | let fut = state.$req_snake(params); 231 | async move { fut.await.map_err(Into::into) } 232 | });)* 233 | this.notification::(|state, params| state.initialized(params)); 234 | this.notification::(|state, params| state.exit(params)); 235 | $(this.notification::<$notif>(|state, params| state.$notif_snake(params));)* 236 | this 237 | } 238 | } 239 | }; 240 | } 241 | 242 | macro_rules! define_client { 243 | ( 244 | { $($req_snake:ident, $req:ty;)* } 245 | { $($notif_snake:ident, $notif:ty;)* } 246 | ) => { 247 | /// The omnitrait defining all standard LSP requests and notifications supported by 248 | /// [`lsp_types`] for a Language Client. 249 | #[allow(missing_docs)] 250 | pub trait LanguageClient { 251 | /// Should always be defined to [`ResponseError`] for user implementations. 252 | type Error: From + Send + 'static; 253 | /// Should always be defined to `ControlFlow>` for user implementations. 254 | type NotifyResult: NotifyResult; 255 | 256 | // Requests. 257 | $( 258 | #[must_use] 259 | fn $req_snake( 260 | &mut self, 261 | params: <$req as Request>::Params, 262 | ) -> ResponseFuture<$req, Self::Error> { 263 | let _ = params; 264 | method_not_found::<$req, _>() 265 | } 266 | )* 267 | 268 | // Notifications. 269 | $( 270 | #[must_use] 271 | fn $notif_snake( 272 | &mut self, 273 | params: <$notif as Notification>::Params, 274 | ) -> Self::NotifyResult { 275 | let _ = params; 276 | Self::NotifyResult::fallback::<$notif>() 277 | } 278 | )* 279 | } 280 | 281 | macro_rules! impl_client_socket { 282 | ($ty:ty) => { 283 | impl LanguageClient for $ty { 284 | type Error = crate::Error; 285 | type NotifyResult = crate::Result<()>; 286 | 287 | // Requests. 288 | $( 289 | fn $req_snake( 290 | &mut self, 291 | params: <$req as Request>::Params, 292 | ) -> ResponseFuture<$req, Self::Error> { 293 | Box::pin(self.0.request::<$req>(params)) 294 | } 295 | )* 296 | 297 | // Notifications. 298 | $( 299 | fn $notif_snake( 300 | &mut self, 301 | params: <$notif as Notification>::Params, 302 | ) -> Self::NotifyResult { 303 | self.notify::<$notif>(params) 304 | } 305 | )* 306 | } 307 | }; 308 | } 309 | 310 | impl_client_socket!(ClientSocket); 311 | impl_client_socket!(&'_ ClientSocket); 312 | 313 | impl Router 314 | where 315 | S: LanguageClient>>, 316 | ResponseError: From, 317 | { 318 | /// Create a [`Router`] using its implementation of [`LanguageClient`] as handlers. 319 | #[must_use] 320 | pub fn from_language_client(state: S) -> Self { 321 | let mut this = Self::new(state); 322 | $(this.request::<$req, _>(|state, params| { 323 | let fut = state.$req_snake(params); 324 | async move { fut.await.map_err(Into::into) } 325 | });)* 326 | $(this.notification::<$notif>(|state, params| state.$notif_snake(params));)* 327 | this 328 | } 329 | } 330 | }; 331 | } 332 | 333 | include!("./omni_trait_generated.rs"); 334 | -------------------------------------------------------------------------------- /src/omni_trait_generated.rs: -------------------------------------------------------------------------------- 1 | // This file is automatically @generated by update_omni_trait.sh 2 | // It is not intended for manual editing. 3 | #[rustfmt::ignore] 4 | define! { 5 | // Client -> Server requests. 6 | { 7 | "textDocument/implementation", implementation; 8 | "textDocument/typeDefinition", type_definition; 9 | "textDocument/documentColor", document_color; 10 | "textDocument/colorPresentation", color_presentation; 11 | "textDocument/foldingRange", folding_range; 12 | "textDocument/declaration", declaration; 13 | "textDocument/selectionRange", selection_range; 14 | "textDocument/prepareCallHierarchy", prepare_call_hierarchy; 15 | "callHierarchy/incomingCalls", incoming_calls; 16 | "callHierarchy/outgoingCalls", outgoing_calls; 17 | "textDocument/semanticTokens/full", semantic_tokens_full; 18 | "textDocument/semanticTokens/full/delta", semantic_tokens_full_delta; 19 | "textDocument/semanticTokens/range", semantic_tokens_range; 20 | "textDocument/linkedEditingRange", linked_editing_range; 21 | "workspace/willCreateFiles", will_create_files; 22 | "workspace/willRenameFiles", will_rename_files; 23 | "workspace/willDeleteFiles", will_delete_files; 24 | "textDocument/moniker", moniker; 25 | "textDocument/prepareTypeHierarchy", prepare_type_hierarchy; 26 | "typeHierarchy/supertypes", supertypes; 27 | "typeHierarchy/subtypes", subtypes; 28 | "textDocument/inlineValue", inline_value; 29 | "textDocument/inlayHint", inlay_hint; 30 | "inlayHint/resolve", inlay_hint_resolve; 31 | "textDocument/diagnostic", document_diagnostic; 32 | "workspace/diagnostic", workspace_diagnostic; 33 | "textDocument/willSaveWaitUntil", will_save_wait_until; 34 | "textDocument/completion", completion; 35 | "completionItem/resolve", completion_item_resolve; 36 | "textDocument/hover", hover; 37 | "textDocument/signatureHelp", signature_help; 38 | "textDocument/definition", definition; 39 | "textDocument/references", references; 40 | "textDocument/documentHighlight", document_highlight; 41 | "textDocument/documentSymbol", document_symbol; 42 | "textDocument/codeAction", code_action; 43 | "codeAction/resolve", code_action_resolve; 44 | "workspace/symbol", symbol; 45 | "workspaceSymbol/resolve", workspace_symbol_resolve; 46 | "textDocument/codeLens", code_lens; 47 | "codeLens/resolve", code_lens_resolve; 48 | "textDocument/documentLink", document_link; 49 | "documentLink/resolve", document_link_resolve; 50 | "textDocument/formatting", formatting; 51 | "textDocument/rangeFormatting", range_formatting; 52 | "textDocument/onTypeFormatting", on_type_formatting; 53 | "textDocument/rename", rename; 54 | "textDocument/prepareRename", prepare_rename; 55 | "workspace/executeCommand", execute_command; 56 | } 57 | // Client -> Server notifications. 58 | { 59 | "workspace/didChangeWorkspaceFolders", did_change_workspace_folders; 60 | "window/workDoneProgress/cancel", work_done_progress_cancel; 61 | "workspace/didCreateFiles", did_create_files; 62 | "workspace/didRenameFiles", did_rename_files; 63 | "workspace/didDeleteFiles", did_delete_files; 64 | "workspace/didChangeConfiguration", did_change_configuration; 65 | "textDocument/didOpen", did_open; 66 | "textDocument/didChange", did_change; 67 | "textDocument/didClose", did_close; 68 | "textDocument/didSave", did_save; 69 | "textDocument/willSave", will_save; 70 | "workspace/didChangeWatchedFiles", did_change_watched_files; 71 | "$/setTrace", set_trace; 72 | "$/cancelRequest", cancel_request; 73 | "$/progress", progress; 74 | } 75 | // Server -> Client requests. 76 | { 77 | "workspace/workspaceFolders", workspace_folders; 78 | "workspace/configuration", configuration; 79 | "window/workDoneProgress/create", work_done_progress_create; 80 | "workspace/semanticTokens/refresh", semantic_tokens_refresh; 81 | "window/showDocument", show_document; 82 | "workspace/inlineValue/refresh", inline_value_refresh; 83 | "workspace/inlayHint/refresh", inlay_hint_refresh; 84 | "workspace/diagnostic/refresh", workspace_diagnostic_refresh; 85 | "client/registerCapability", register_capability; 86 | "client/unregisterCapability", unregister_capability; 87 | "window/showMessageRequest", show_message_request; 88 | "workspace/codeLens/refresh", code_lens_refresh; 89 | "workspace/applyEdit", apply_edit; 90 | } 91 | // Server -> Client notifications. 92 | { 93 | "window/showMessage", show_message; 94 | "window/logMessage", log_message; 95 | "telemetry/event", telemetry_event; 96 | "textDocument/publishDiagnostics", publish_diagnostics; 97 | "$/logTrace", log_trace; 98 | "$/cancelRequest", cancel_request; 99 | "$/progress", progress; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | //! Catch panics of underlying handlers and turn them into error responses. 2 | //! 3 | //! *Applies to both Language Servers and Language Clients.* 4 | use std::any::Any; 5 | use std::future::Future; 6 | use std::ops::ControlFlow; 7 | use std::panic::{catch_unwind, AssertUnwindSafe}; 8 | use std::pin::Pin; 9 | use std::task::{Context, Poll}; 10 | 11 | use pin_project_lite::pin_project; 12 | use tower_layer::Layer; 13 | use tower_service::Service; 14 | 15 | use crate::{AnyEvent, AnyNotification, AnyRequest, ErrorCode, LspService, ResponseError, Result}; 16 | 17 | /// The middleware catching panics of underlying handlers and turn them into error responses. 18 | /// 19 | /// See [module level documentations](self) for details. 20 | pub struct CatchUnwind { 21 | service: S, 22 | handler: Handler, 23 | } 24 | 25 | define_getters!(impl[S: LspService] CatchUnwind, service: S); 26 | 27 | type Handler = fn(method: &str, payload: Box) -> E; 28 | 29 | fn default_handler(method: &str, payload: Box) -> ResponseError { 30 | let msg = match payload.downcast::() { 31 | Ok(msg) => *msg, 32 | Err(payload) => match payload.downcast::<&'static str>() { 33 | Ok(msg) => (*msg).into(), 34 | Err(_payload) => "unknown".into(), 35 | }, 36 | }; 37 | ResponseError { 38 | code: ErrorCode::INTERNAL_ERROR, 39 | message: format!("Request handler of {method} panicked: {msg}"), 40 | data: None, 41 | } 42 | } 43 | 44 | impl Service for CatchUnwind { 45 | type Response = S::Response; 46 | type Error = S::Error; 47 | type Future = ResponseFuture; 48 | 49 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 50 | self.service.poll_ready(cx) 51 | } 52 | 53 | fn call(&mut self, req: AnyRequest) -> Self::Future { 54 | let method = req.method.clone(); 55 | // FIXME: Clarify conditions of UnwindSafe. 56 | match catch_unwind(AssertUnwindSafe(|| self.service.call(req))) 57 | .map_err(|err| (self.handler)(&method, err)) 58 | { 59 | Ok(fut) => ResponseFuture { 60 | inner: ResponseFutureInner::Future { 61 | fut, 62 | method, 63 | handler: self.handler, 64 | }, 65 | }, 66 | Err(err) => ResponseFuture { 67 | inner: ResponseFutureInner::Ready { err: Some(err) }, 68 | }, 69 | } 70 | } 71 | } 72 | 73 | pin_project! { 74 | /// The [`Future`] type used by the [`CatchUnwind`] middleware. 75 | pub struct ResponseFuture { 76 | #[pin] 77 | inner: ResponseFutureInner, 78 | } 79 | } 80 | 81 | pin_project! { 82 | #[project = ResponseFutureProj] 83 | enum ResponseFutureInner { 84 | Future { 85 | #[pin] 86 | fut: Fut, 87 | method: String, 88 | handler: Handler, 89 | }, 90 | Ready { 91 | err: Option, 92 | }, 93 | } 94 | } 95 | 96 | impl Future for ResponseFuture 97 | where 98 | Fut: Future>, 99 | { 100 | type Output = Fut::Output; 101 | 102 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 103 | match self.project().inner.project() { 104 | ResponseFutureProj::Future { 105 | fut, 106 | method, 107 | handler, 108 | } => { 109 | // FIXME: Clarify conditions of UnwindSafe. 110 | match catch_unwind(AssertUnwindSafe(|| fut.poll(cx))) { 111 | Ok(poll) => poll, 112 | Err(payload) => Poll::Ready(Err(handler(method, payload))), 113 | } 114 | } 115 | ResponseFutureProj::Ready { err } => Poll::Ready(Err(err.take().expect("Completed"))), 116 | } 117 | } 118 | } 119 | 120 | impl LspService for CatchUnwind { 121 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 122 | self.service.notify(notif) 123 | } 124 | 125 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 126 | self.service.emit(event) 127 | } 128 | } 129 | 130 | /// The builder of [`CatchUnwind`] middleware. 131 | /// 132 | /// It's [`Default`] configuration tries to downcast the panic payload into `String` or `&str`, and 133 | /// fallback to format it via [`std::fmt::Display`], as the error message. 134 | /// The error code is set to [`ErrorCode::INTERNAL_ERROR`]. 135 | #[derive(Clone)] 136 | #[must_use] 137 | pub struct CatchUnwindBuilder { 138 | handler: Handler, 139 | } 140 | 141 | impl Default for CatchUnwindBuilder { 142 | fn default() -> Self { 143 | Self::new_with_handler(default_handler) 144 | } 145 | } 146 | 147 | impl CatchUnwindBuilder { 148 | /// Create the builder of [`CatchUnwind`] middleware with a custom handler converting panic 149 | /// payloads into [`ResponseError`]. 150 | pub fn new_with_handler(handler: Handler) -> Self { 151 | Self { handler } 152 | } 153 | } 154 | 155 | /// A type alias of [`CatchUnwindBuilder`] conforming to the naming convention of [`tower_layer`]. 156 | pub type CatchUnwindLayer = CatchUnwindBuilder; 157 | 158 | impl Layer for CatchUnwindBuilder { 159 | type Service = CatchUnwind; 160 | 161 | fn layer(&self, inner: S) -> Self::Service { 162 | CatchUnwind { 163 | service: inner, 164 | handler: self.handler, 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | //! Dispatch requests and notifications to individual handlers. 2 | use std::any::TypeId; 3 | use std::collections::HashMap; 4 | use std::future::{ready, Future}; 5 | use std::ops::ControlFlow; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | 9 | use lsp_types::notification::Notification; 10 | use lsp_types::request::Request; 11 | use tower_service::Service; 12 | 13 | use crate::{ 14 | AnyEvent, AnyNotification, AnyRequest, ErrorCode, JsonValue, LspService, ResponseError, Result, 15 | }; 16 | 17 | /// A router dispatching requests and notifications to individual handlers. 18 | pub struct Router { 19 | state: St, 20 | req_handlers: HashMap<&'static str, BoxReqHandler>, 21 | notif_handlers: HashMap<&'static str, BoxNotifHandler>, 22 | event_handlers: HashMap>, 23 | unhandled_req: BoxReqHandler, 24 | unhandled_notif: BoxNotifHandler, 25 | unhandled_event: BoxEventHandler, 26 | } 27 | 28 | type BoxReqFuture = Pin> + Send>>; 29 | type BoxReqHandler = Box BoxReqFuture + Send>; 30 | type BoxNotifHandler = Box ControlFlow> + Send>; 31 | type BoxEventHandler = Box ControlFlow> + Send>; 32 | 33 | impl Default for Router 34 | where 35 | St: Default, 36 | Error: From + Send + 'static, 37 | { 38 | fn default() -> Self { 39 | Self::new(St::default()) 40 | } 41 | } 42 | 43 | // TODO: Make it possible to construct with arbitrary `Error`, with no default handlers. 44 | impl Router 45 | where 46 | Error: From + Send + 'static, 47 | { 48 | /// Create a empty `Router`. 49 | #[must_use] 50 | pub fn new(state: St) -> Self { 51 | Self { 52 | state, 53 | req_handlers: HashMap::new(), 54 | notif_handlers: HashMap::new(), 55 | event_handlers: HashMap::new(), 56 | unhandled_req: Box::new(|_, req| { 57 | Box::pin(ready(Err(ResponseError { 58 | code: ErrorCode::METHOD_NOT_FOUND, 59 | message: format!("No such method {}", req.method), 60 | data: None, 61 | } 62 | .into()))) 63 | }), 64 | unhandled_notif: Box::new(|_, notif| { 65 | if notif.method.starts_with("$/") { 66 | ControlFlow::Continue(()) 67 | } else { 68 | ControlFlow::Break(Err(crate::Error::Routing(format!( 69 | "Unhandled notification: {}", 70 | notif.method, 71 | )))) 72 | } 73 | }), 74 | unhandled_event: Box::new(|_, event| { 75 | ControlFlow::Break(Err(crate::Error::Routing(format!( 76 | "Unhandled event: {event:?}" 77 | )))) 78 | }), 79 | } 80 | } 81 | 82 | /// Add an asynchronous request handler for a specific LSP request `R`. 83 | /// 84 | /// If handler for the method already exists, it replaces the old one. 85 | pub fn request( 86 | &mut self, 87 | handler: impl Fn(&mut St, R::Params) -> Fut + Send + 'static, 88 | ) -> &mut Self 89 | where 90 | Fut: Future> + Send + 'static, 91 | { 92 | self.req_handlers.insert( 93 | R::METHOD, 94 | Box::new( 95 | move |state, req| match serde_json::from_value::(req.params) { 96 | Ok(params) => { 97 | let fut = handler(state, params); 98 | Box::pin(async move { 99 | Ok(serde_json::to_value(fut.await?).expect("Serialization failed")) 100 | }) 101 | } 102 | Err(err) => Box::pin(ready(Err(ResponseError { 103 | code: ErrorCode::INVALID_PARAMS, 104 | message: format!("Failed to deserialize parameters: {err}"), 105 | data: None, 106 | } 107 | .into()))), 108 | }, 109 | ), 110 | ); 111 | self 112 | } 113 | 114 | /// Add a synchronous request handler for a specific LSP notification `N`. 115 | /// 116 | /// If handler for the method already exists, it replaces the old one. 117 | pub fn notification( 118 | &mut self, 119 | handler: impl Fn(&mut St, N::Params) -> ControlFlow> + Send + 'static, 120 | ) -> &mut Self { 121 | self.notif_handlers.insert( 122 | N::METHOD, 123 | Box::new( 124 | move |state, notif| match serde_json::from_value::(notif.params) { 125 | Ok(params) => handler(state, params), 126 | Err(err) => ControlFlow::Break(Err(err.into())), 127 | }, 128 | ), 129 | ); 130 | self 131 | } 132 | 133 | /// Add a synchronous event handler for event type `E`. 134 | /// 135 | /// If handler for the method already exists, it replaces the old one. 136 | pub fn event( 137 | &mut self, 138 | handler: impl Fn(&mut St, E) -> ControlFlow> + Send + 'static, 139 | ) -> &mut Self { 140 | self.event_handlers.insert( 141 | TypeId::of::(), 142 | Box::new(move |state, event| { 143 | let event = event.downcast::().expect("Checked TypeId"); 144 | handler(state, event) 145 | }), 146 | ); 147 | self 148 | } 149 | 150 | /// Set an asynchronous catch-all request handler for any requests with no corresponding handler 151 | /// for its `method`. 152 | /// 153 | /// There can only be a single catch-all request handler. New ones replace old ones. 154 | /// 155 | /// The default handler is to respond a error response with code 156 | /// [`ErrorCode::METHOD_NOT_FOUND`]. 157 | pub fn unhandled_request( 158 | &mut self, 159 | handler: impl Fn(&mut St, AnyRequest) -> Fut + Send + 'static, 160 | ) -> &mut Self 161 | where 162 | Fut: Future> + Send + 'static, 163 | { 164 | self.unhandled_req = Box::new(move |state, req| Box::pin(handler(state, req))); 165 | self 166 | } 167 | 168 | /// Set a synchronous catch-all notification handler for any notifications with no 169 | /// corresponding handler for its `method`. 170 | /// 171 | /// There can only be a single catch-all notification handler. New ones replace old ones. 172 | /// 173 | /// The default handler is to do nothing for methods starting with `$/`, and break the main 174 | /// loop with [`Error::Routing`][crate::Error::Routing] for other methods. Typically 175 | /// notifications are critical and 176 | /// losing them can break state synchronization, easily leading to catastrophic failures after 177 | /// incorrect incremental changes. 178 | pub fn unhandled_notification( 179 | &mut self, 180 | handler: impl Fn(&mut St, AnyNotification) -> ControlFlow> + Send + 'static, 181 | ) -> &mut Self { 182 | self.unhandled_notif = Box::new(handler); 183 | self 184 | } 185 | 186 | /// Set a synchronous catch-all event handler for any notifications with no 187 | /// corresponding handler for its type. 188 | /// 189 | /// There can only be a single catch-all event handler. New ones replace old ones. 190 | /// 191 | /// The default handler is to break the main loop with 192 | /// [`Error::Routing`][crate::Error::Routing]. Since events are 193 | /// emitted internally, mishandling are typically logic errors. 194 | pub fn unhandled_event( 195 | &mut self, 196 | handler: impl Fn(&mut St, AnyEvent) -> ControlFlow> + Send + 'static, 197 | ) -> &mut Self { 198 | self.unhandled_event = Box::new(handler); 199 | self 200 | } 201 | } 202 | 203 | impl Service for Router { 204 | type Response = JsonValue; 205 | type Error = Error; 206 | type Future = BoxReqFuture; 207 | 208 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 209 | Poll::Ready(Ok(())) 210 | } 211 | 212 | fn call(&mut self, req: AnyRequest) -> Self::Future { 213 | let h = self 214 | .req_handlers 215 | .get(&*req.method) 216 | .unwrap_or(&self.unhandled_req); 217 | h(&mut self.state, req) 218 | } 219 | } 220 | 221 | impl LspService for Router { 222 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 223 | let h = self 224 | .notif_handlers 225 | .get(&*notif.method) 226 | .unwrap_or(&self.unhandled_notif); 227 | h(&mut self.state, notif) 228 | } 229 | 230 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 231 | let h = self 232 | .event_handlers 233 | .get(&event.inner_type_id()) 234 | .unwrap_or(&self.unhandled_event); 235 | h(&mut self.state, event) 236 | } 237 | } 238 | 239 | #[cfg(test)] 240 | mod tests { 241 | use super::*; 242 | 243 | fn _assert_send(router: Router) -> impl Send { 244 | router 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! Language Server lifecycle. 2 | //! 3 | //! *Only applies to Language Servers.* 4 | //! 5 | //! This middleware handles 6 | //! [the lifecycle of Language Servers](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#lifeCycleMessages), 7 | //! specifically: 8 | //! - Exit the main loop with `ControlFlow::Break(Ok(()))` on `exit` notification. 9 | //! - Responds unrelated requests with errors and ignore unrelated notifications during 10 | //! initialization and shutting down. 11 | use std::future::{ready, Future, Ready}; 12 | use std::ops::ControlFlow; 13 | use std::pin::Pin; 14 | use std::task::{Context, Poll}; 15 | 16 | use futures::future::Either; 17 | use lsp_types::notification::{self, Notification}; 18 | use lsp_types::request::{self, Request}; 19 | use pin_project_lite::pin_project; 20 | use tower_layer::Layer; 21 | use tower_service::Service; 22 | 23 | use crate::{ 24 | AnyEvent, AnyNotification, AnyRequest, Error, ErrorCode, LspService, ResponseError, Result, 25 | }; 26 | 27 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] 28 | enum State { 29 | #[default] 30 | Uninitialized, 31 | Initializing, 32 | Ready, 33 | ShuttingDown, 34 | } 35 | 36 | /// The middleware handling Language Server lifecycle. 37 | /// 38 | /// See [module level documentations](self) for details. 39 | #[derive(Debug, Default)] 40 | pub struct Lifecycle { 41 | service: S, 42 | state: State, 43 | } 44 | 45 | define_getters!(impl[S] Lifecycle, service: S); 46 | 47 | impl Lifecycle { 48 | /// Creating the `Lifecycle` middleware in uninitialized state. 49 | #[must_use] 50 | pub fn new(service: S) -> Self { 51 | Self { 52 | service, 53 | state: State::Uninitialized, 54 | } 55 | } 56 | } 57 | 58 | impl Service for Lifecycle 59 | where 60 | S::Error: From, 61 | { 62 | type Response = S::Response; 63 | type Error = S::Error; 64 | type Future = ResponseFuture; 65 | 66 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 67 | self.service.poll_ready(cx) 68 | } 69 | 70 | fn call(&mut self, req: AnyRequest) -> Self::Future { 71 | let inner = match (self.state, &*req.method) { 72 | (State::Uninitialized, request::Initialize::METHOD) => { 73 | self.state = State::Initializing; 74 | Either::Left(self.service.call(req)) 75 | } 76 | (State::Uninitialized | State::Initializing, _) => { 77 | Either::Right(ready(Err(ResponseError { 78 | code: ErrorCode::SERVER_NOT_INITIALIZED, 79 | message: "Server is not initialized yet".into(), 80 | data: None, 81 | } 82 | .into()))) 83 | } 84 | (_, request::Initialize::METHOD) => Either::Right(ready(Err(ResponseError { 85 | code: ErrorCode::INVALID_REQUEST, 86 | message: "Server is already initialized".into(), 87 | data: None, 88 | } 89 | .into()))), 90 | (State::Ready, _) => { 91 | if req.method == request::Shutdown::METHOD { 92 | self.state = State::ShuttingDown; 93 | } 94 | Either::Left(self.service.call(req)) 95 | } 96 | (State::ShuttingDown, _) => Either::Right(ready(Err(ResponseError { 97 | code: ErrorCode::INVALID_REQUEST, 98 | message: "Server is shutting down".into(), 99 | data: None, 100 | } 101 | .into()))), 102 | }; 103 | ResponseFuture { inner } 104 | } 105 | } 106 | 107 | impl LspService for Lifecycle 108 | where 109 | S::Error: From, 110 | { 111 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 112 | match &*notif.method { 113 | notification::Exit::METHOD => { 114 | self.service.notify(notif)?; 115 | ControlFlow::Break(Ok(())) 116 | } 117 | notification::Initialized::METHOD => { 118 | if self.state != State::Initializing { 119 | return ControlFlow::Break(Err(Error::Protocol(format!( 120 | "Unexpected initialized notification on state {:?}", 121 | self.state 122 | )))); 123 | } 124 | self.state = State::Ready; 125 | self.service.notify(notif)?; 126 | ControlFlow::Continue(()) 127 | } 128 | _ => self.service.notify(notif), 129 | } 130 | } 131 | 132 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 133 | self.service.emit(event) 134 | } 135 | } 136 | 137 | pin_project! { 138 | /// The [`Future`] type used by the [`Lifecycle`] middleware. 139 | pub struct ResponseFuture { 140 | #[pin] 141 | inner: Either>, 142 | } 143 | } 144 | 145 | impl Future for ResponseFuture { 146 | type Output = Fut::Output; 147 | 148 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 149 | self.project().inner.poll(cx) 150 | } 151 | } 152 | 153 | /// A [`tower_layer::Layer`] which builds [`Lifecycle`]. 154 | #[must_use] 155 | #[derive(Clone, Default)] 156 | pub struct LifecycleLayer { 157 | _private: (), 158 | } 159 | 160 | impl Layer for LifecycleLayer { 161 | type Service = Lifecycle; 162 | 163 | fn layer(&self, inner: S) -> Self::Service { 164 | Lifecycle::new(inner) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/stdio.rs: -------------------------------------------------------------------------------- 1 | //! Utilities to deal with stdin/stdout communication channel for Language Servers. 2 | //! 3 | //! Typically Language Servers serves on stdin/stdout by default. But generally they cannot be read 4 | //! or written asynchronously usually, due to technical reasons. 5 | //! (Eg. [`tokio::io::stdin`](https://docs.rs/tokio/1.27.0/tokio/io/fn.stdin.html) delegates reads 6 | //! to blocking threads.) 7 | //! 8 | //! This mod defines [`PipeStdin`] and [`PipeStdout`] for only stdin/stdout with pipe-like 9 | //! backends, which actually supports asynchronous reads and writes. This currently means one of: 10 | //! - FIFO pipes. Eg. named pipes [mkfifo(3)] and unnamed pipes [pipe(2)]. 11 | //! - Sockets. Eg. TCP connections and UNIX domain sockets [unix(7)]. 12 | //! - Character devices. Eg. [tty(4)] or [pty(7)]. 13 | //! 14 | //! [mkfifo(3)]: https://man7.org/linux/man-pages/man3/mkfifo.3.html 15 | //! [pipe(2)]: https://man7.org/linux/man-pages/man2/pipe.2.html 16 | //! [unix(7)]: https://man7.org/linux/man-pages/man7/unix.7.html 17 | //! [tty(4)]: https://man7.org/linux/man-pages/man4/tty.4.html 18 | //! [pty(7)]: https://man7.org/linux/man-pages/man7/pty.7.html 19 | //! 20 | //! When calling [`PipeStdin::lock`], it locks the stdin using [`std::io::stdin`], set its mode to 21 | //! asynchronous, and exposes an a raw [`Read`] interface bypassing the std's buffer. 22 | //! 23 | //! # Caveats 24 | //! 25 | //! Since `PipeStd{in,out}` bypass the std's internal buffer. You should not leave any data in that 26 | //! buffer (via [`std::io::stdin`], [`print!`]-like macros and etc.). Otherwise they will be 27 | //! ignored during `PipeStd{in,out}` operations, which is typically a logic error. 28 | //! 29 | //! # Asynchrous I/O drivers 30 | //! 31 | //! ## `async-io` 32 | //! 33 | //! Wrapping `PipeStd{in,out}` inside `async_io::Async` works. This should also work for other 34 | //! drivers with a similar generic asynchronous adapter interface. 35 | //! 36 | //! For `async-io` >= 2, feature `async-io` should be enabled to let `Async` 37 | //! implements `Async{Read,Write}`. `async-io` < 2 does not require it to work. 38 | //! See more details in: 39 | //! 40 | //! ``` 41 | //! # async fn work() -> std::io::Result<()> { 42 | //! use futures::AsyncWriteExt; 43 | //! 44 | //! let mut stdout = async_io::Async::new(async_lsp::stdio::PipeStdout::lock()?)?; 45 | //! // For `async-io` >= 2, this requires `async-io` feature to work. 46 | #![cfg_attr(not(feature = "async-io"), doc = "# #[cfg(never)]")] 47 | //! stdout.write_all(b"never spawns blocking tasks").await?; 48 | //! // This always work. 49 | //! (&stdout).write_all(b"never spawns blocking tasks").await?; 50 | //! # Ok(()) 51 | //! # } 52 | //! ``` 53 | //! 54 | //! ## `tokio` 55 | //! 56 | //! There are methods `PipeStd{in,out}::{lock,try_into}_tokio` gated under feature `tokio` to 57 | //! work with `tokio` runtime. The returned type implements corresponding 58 | //! `tokio::io::Async{Read,Write}` interface. 59 | //! 60 | //! ``` 61 | //! # #[cfg(feature = "tokio")] 62 | //! # async fn work() -> std::io::Result<()> { 63 | //! use tokio::io::AsyncWriteExt; 64 | //! 65 | //! let mut stdout = async_lsp::stdio::PipeStdout::lock_tokio()?; 66 | //! stdout.write_all(b"never spawns blocking tasks").await?; 67 | //! # Ok(()) 68 | //! # } 69 | //! ``` 70 | use std::io::{self, Error, ErrorKind, IoSlice, Read, Result, StdinLock, StdoutLock, Write}; 71 | use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; 72 | 73 | use rustix::fs::{fcntl_getfl, fcntl_setfl, fstat, FileType, OFlags}; 74 | 75 | #[derive(Debug)] 76 | struct NonBlocking { 77 | inner: T, 78 | prev_flags: OFlags, 79 | } 80 | 81 | impl NonBlocking { 82 | fn new(inner: T) -> Result { 83 | let ft = FileType::from_raw_mode(fstat(&inner)?.st_mode); 84 | if !matches!( 85 | ft, 86 | FileType::Fifo | FileType::Socket | FileType::CharacterDevice 87 | ) { 88 | return Err(Error::new( 89 | ErrorKind::Other, 90 | format!("File type {ft:?} is not pipe-like"), 91 | )); 92 | } 93 | 94 | let prev_flags = fcntl_getfl(&inner)?; 95 | fcntl_setfl(&inner, prev_flags | OFlags::NONBLOCK)?; 96 | Ok(Self { inner, prev_flags }) 97 | } 98 | } 99 | 100 | impl Drop for NonBlocking { 101 | fn drop(&mut self) { 102 | let _: std::result::Result<_, _> = fcntl_setfl(&self.inner, self.prev_flags); 103 | } 104 | } 105 | 106 | /// Locked stdin for asynchronous read. 107 | #[derive(Debug)] 108 | pub struct PipeStdin { 109 | inner: NonBlocking>, 110 | } 111 | 112 | impl PipeStdin { 113 | /// Lock stdin with pipe-like backend and set it to asynchronous mode. 114 | /// 115 | /// # Errors 116 | /// 117 | /// Fails if the underlying FD is not pipe-like, or error occurs when setting mode. 118 | /// See [module level documentation](index.html) for more details. 119 | pub fn lock() -> Result { 120 | let inner = NonBlocking::new(io::stdin().lock())?; 121 | Ok(Self { inner }) 122 | } 123 | } 124 | 125 | impl AsFd for PipeStdin { 126 | fn as_fd(&self) -> BorrowedFd<'_> { 127 | self.inner.inner.as_fd() 128 | } 129 | } 130 | 131 | impl AsRawFd for PipeStdin { 132 | fn as_raw_fd(&self) -> RawFd { 133 | self.inner.inner.as_raw_fd() 134 | } 135 | } 136 | 137 | // Invariant: `AsFd::as_fd` never changes through its lifetime. 138 | #[cfg(feature = "async-io")] 139 | unsafe impl async_io::IoSafe for PipeStdin {} 140 | 141 | // NB. Bypass the internal buffer of `StdinLock` here to keep this in sync with the readiness of 142 | // the underlying FD (which is relied by the I/O re/actor). 143 | impl Read for &'_ PipeStdin { 144 | fn read(&mut self, buf: &mut [u8]) -> Result { 145 | rustix::io::read(self, buf).map_err(Into::into) 146 | } 147 | 148 | fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> Result { 149 | rustix::io::readv(self, bufs).map_err(Into::into) 150 | } 151 | } 152 | 153 | impl Read for PipeStdin { 154 | fn read(&mut self, buf: &mut [u8]) -> Result { 155 | <&PipeStdin>::read(&mut &*self, buf) 156 | } 157 | 158 | fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> Result { 159 | <&PipeStdin>::read_vectored(&mut &*self, bufs) 160 | } 161 | } 162 | 163 | /// Locked stdout for asynchronous read. 164 | #[derive(Debug)] 165 | pub struct PipeStdout { 166 | inner: NonBlocking>, 167 | } 168 | 169 | impl PipeStdout { 170 | /// Lock stdout with pipe-like backend and set it to asynchronous mode. 171 | /// 172 | /// # Errors 173 | /// Fails if the underlying FD is not pipe-like, or error occurs when setting mode. 174 | /// See [module level documentation](index.html) for more details. 175 | pub fn lock() -> Result { 176 | let inner = NonBlocking::new(io::stdout().lock())?; 177 | Ok(Self { inner }) 178 | } 179 | } 180 | 181 | impl AsFd for PipeStdout { 182 | fn as_fd(&self) -> BorrowedFd<'_> { 183 | self.inner.inner.as_fd() 184 | } 185 | } 186 | 187 | impl AsRawFd for PipeStdout { 188 | fn as_raw_fd(&self) -> RawFd { 189 | self.inner.inner.as_raw_fd() 190 | } 191 | } 192 | 193 | // Invariant: `AsFd::as_fd` never changes through its lifetime. 194 | #[cfg(feature = "async-io")] 195 | unsafe impl async_io::IoSafe for PipeStdout {} 196 | 197 | // NB. See `Read` impl. 198 | impl Write for &'_ PipeStdout { 199 | fn write(&mut self, buf: &[u8]) -> Result { 200 | rustix::io::write(self, buf).map_err(Into::into) 201 | } 202 | 203 | fn flush(&mut self) -> Result<()> { 204 | Ok(()) 205 | } 206 | 207 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 208 | rustix::io::writev(self, bufs).map_err(Into::into) 209 | } 210 | } 211 | 212 | impl Write for PipeStdout { 213 | fn write(&mut self, buf: &[u8]) -> Result { 214 | <&PipeStdout>::write(&mut &*self, buf) 215 | } 216 | 217 | fn flush(&mut self) -> Result<()> { 218 | <&PipeStdout>::flush(&mut &*self) 219 | } 220 | 221 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 222 | <&PipeStdout>::write_vectored(&mut &*self, bufs) 223 | } 224 | } 225 | 226 | // Tokio compatibility. 227 | // We can simplify these if we have https://github.com/tokio-rs/tokio/issues/5785 228 | #[cfg(feature = "tokio")] 229 | #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] 230 | mod tokio_impl { 231 | use std::pin::Pin; 232 | use std::task::{Context, Poll}; 233 | 234 | use futures::ready; 235 | use tokio::io::unix::AsyncFd; 236 | use tokio::io::{Interest, ReadBuf}; 237 | 238 | use super::*; 239 | 240 | pub struct TokioPipeStdin { 241 | inner: AsyncFd, 242 | } 243 | 244 | impl futures::AsyncRead for TokioPipeStdin { 245 | fn poll_read( 246 | self: Pin<&mut Self>, 247 | cx: &mut Context<'_>, 248 | buf: &mut [u8], 249 | ) -> Poll> { 250 | loop { 251 | let mut guard = ready!(self.inner.poll_read_ready(cx))?; 252 | match guard.try_io(|inner| inner.get_ref().read(buf)) { 253 | Ok(ret) => return Poll::Ready(ret), 254 | Err(_would_block) => continue, 255 | } 256 | } 257 | } 258 | 259 | fn poll_read_vectored( 260 | self: Pin<&mut Self>, 261 | cx: &mut Context<'_>, 262 | bufs: &mut [io::IoSliceMut<'_>], 263 | ) -> Poll> { 264 | loop { 265 | let mut guard = ready!(self.inner.poll_read_ready(cx))?; 266 | match guard.try_io(|inner| inner.get_ref().read_vectored(bufs)) { 267 | Ok(ret) => return Poll::Ready(ret), 268 | Err(_would_block) => continue, 269 | } 270 | } 271 | } 272 | } 273 | 274 | impl tokio::io::AsyncRead for TokioPipeStdin { 275 | fn poll_read( 276 | self: Pin<&mut Self>, 277 | cx: &mut Context<'_>, 278 | buf: &mut ReadBuf<'_>, 279 | ) -> Poll> { 280 | let len = loop { 281 | let mut guard = ready!(self.inner.poll_read_ready(cx))?; 282 | match guard.try_io(|inner| { 283 | // SAFETY: `read()` does not de-initialize any byte. 284 | let (written, _) = rustix::io::read(inner, unsafe { buf.unfilled_mut() })?; 285 | Ok(written.len()) 286 | }) { 287 | Ok(ret) => break ret?, 288 | Err(_would_block) => continue, 289 | } 290 | }; 291 | buf.advance(len); 292 | Poll::Ready(Ok(())) 293 | } 294 | } 295 | 296 | impl PipeStdin { 297 | /// Shortcut to [`PipeStdin::lock`] and then [`PipeStdin::try_into_tokio`]. 298 | /// 299 | /// # Errors 300 | /// 301 | /// Fails if cannot create [`AsyncFd`]. 302 | pub fn lock_tokio() -> Result { 303 | Self::lock()?.try_into_tokio() 304 | } 305 | 306 | /// Register the FD to the tokio runtime and return a tokio compatible reader. 307 | /// 308 | /// # Errors 309 | /// 310 | /// Fails if cannot create [`AsyncFd`]. 311 | pub fn try_into_tokio(self) -> Result { 312 | let inner = AsyncFd::with_interest(self, Interest::READABLE)?; 313 | Ok(TokioPipeStdin { inner }) 314 | } 315 | } 316 | 317 | pub struct TokioPipeStdout { 318 | inner: AsyncFd, 319 | } 320 | 321 | impl futures::AsyncWrite for TokioPipeStdout { 322 | fn poll_write( 323 | self: Pin<&mut Self>, 324 | cx: &mut Context<'_>, 325 | buf: &[u8], 326 | ) -> Poll> { 327 | loop { 328 | let mut guard = ready!(self.inner.poll_write_ready(cx))?; 329 | match guard.try_io(|inner| inner.get_ref().write(buf)) { 330 | Ok(result) => return Poll::Ready(result), 331 | Err(_would_block) => continue, 332 | } 333 | } 334 | } 335 | 336 | fn poll_write_vectored( 337 | self: Pin<&mut Self>, 338 | cx: &mut Context<'_>, 339 | bufs: &[IoSlice<'_>], 340 | ) -> Poll> { 341 | loop { 342 | let mut guard = ready!(self.inner.poll_write_ready(cx))?; 343 | match guard.try_io(|inner| inner.get_ref().write_vectored(bufs)) { 344 | Ok(result) => return Poll::Ready(result), 345 | Err(_would_block) => continue, 346 | } 347 | } 348 | } 349 | 350 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 351 | Poll::Ready(Ok(())) 352 | } 353 | 354 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 355 | Poll::Ready(Ok(())) 356 | } 357 | } 358 | 359 | impl tokio::io::AsyncWrite for TokioPipeStdout { 360 | fn poll_write( 361 | self: Pin<&mut Self>, 362 | cx: &mut Context<'_>, 363 | buf: &[u8], 364 | ) -> Poll> { 365 | ::poll_write(self, cx, buf) 366 | } 367 | 368 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 369 | Poll::Ready(Ok(())) 370 | } 371 | 372 | fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 373 | Poll::Ready(Ok(())) 374 | } 375 | } 376 | 377 | impl PipeStdout { 378 | /// Shortcut to [`PipeStdout::lock`] and then [`PipeStdout::try_into_tokio`]. 379 | /// 380 | /// # Errors 381 | /// 382 | /// Fails if cannot create [`AsyncFd`]. 383 | pub fn lock_tokio() -> Result { 384 | Self::lock()?.try_into_tokio() 385 | } 386 | 387 | /// Register the FD to the tokio runtime and return a tokio compatible writer. 388 | /// 389 | /// # Errors 390 | /// 391 | /// Fails if cannot create [`AsyncFd`]. 392 | pub fn try_into_tokio(self) -> Result { 393 | let inner = AsyncFd::with_interest(self, Interest::WRITABLE)?; 394 | Ok(TokioPipeStdout { inner }) 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/tracing.rs: -------------------------------------------------------------------------------- 1 | //! Attach [`tracing::Span`]s over underlying handlers. 2 | //! 3 | //! *Applies to both Language Servers and Language Clients.* 4 | //! 5 | //! This middleware attaches spans to logs in underlying implementations, with optional method 6 | //! strings of current processing requests/notifications. 7 | //! All of these methods are instrumented by the [`Default`] configuration: 8 | //! - [`Service::poll_ready`]. 9 | //! - [`Future::poll`] of returned `Future` from [`Service::call`]. 10 | //! - [`LspService::notify`]. 11 | //! - [`LspService::emit`]. 12 | use std::future::Future; 13 | use std::ops::ControlFlow; 14 | use std::pin::Pin; 15 | use std::task::{Context, Poll}; 16 | 17 | use pin_project_lite::pin_project; 18 | use tower_layer::Layer; 19 | use tower_service::Service; 20 | use tracing::{info_span, Span}; 21 | 22 | use crate::{AnyEvent, AnyNotification, AnyRequest, LspService, Result}; 23 | 24 | /// The middleware attaching [`tracing::Span`]s over underlying handlers. 25 | /// 26 | /// See [module level documentations](self) for details. 27 | #[derive(Default)] 28 | pub struct Tracing { 29 | service: S, 30 | spans: TracingBuilder, 31 | } 32 | 33 | define_getters!(impl[S] Tracing, service: S); 34 | 35 | impl Service for Tracing { 36 | type Response = S::Response; 37 | type Error = S::Error; 38 | type Future = ResponseFuture; 39 | 40 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 41 | let _guard = self.spans.service_ready.map(|f| f().entered()); 42 | self.service.poll_ready(cx) 43 | } 44 | 45 | fn call(&mut self, req: AnyRequest) -> Self::Future { 46 | ResponseFuture { 47 | span: self.spans.request.map(|f| f(&req)), 48 | fut: self.service.call(req), 49 | } 50 | } 51 | } 52 | 53 | pin_project! { 54 | /// The [`Future`] type used by the [`Tracing`] middleware. 55 | pub struct ResponseFuture { 56 | span: Option, 57 | #[pin] 58 | fut: Fut, 59 | } 60 | } 61 | 62 | impl Future for ResponseFuture { 63 | type Output = Fut::Output; 64 | 65 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 66 | let this = self.project(); 67 | let _guard = this.span.as_mut().map(|span| span.enter()); 68 | this.fut.poll(cx) 69 | } 70 | } 71 | 72 | impl LspService for Tracing { 73 | fn notify(&mut self, notif: AnyNotification) -> ControlFlow> { 74 | let _guard = self.spans.notification.map(|f| f(¬if).entered()); 75 | self.service.notify(notif) 76 | } 77 | 78 | fn emit(&mut self, event: AnyEvent) -> ControlFlow> { 79 | let _guard = self.spans.event.map(|f| f(&event).entered()); 80 | self.service.emit(event) 81 | } 82 | } 83 | 84 | /// The builder of [`Tracing`] middleware. 85 | /// 86 | /// See [module level documentations](self) for details. 87 | #[derive(Clone)] 88 | #[must_use] 89 | pub struct TracingBuilder { 90 | service_ready: Option Span>, 91 | request: Option Span>, 92 | notification: Option Span>, 93 | event: Option Span>, 94 | } 95 | 96 | impl Default for TracingBuilder { 97 | fn default() -> Self { 98 | Self { 99 | service_ready: Some(|| info_span!("service_ready")), 100 | request: Some(|req| info_span!("request", method = req.method)), 101 | notification: Some(|notif| info_span!("notification", method = notif.method)), 102 | event: Some(|event| info_span!("event", type_name = event.type_name())), 103 | } 104 | } 105 | } 106 | 107 | impl TracingBuilder { 108 | /// Creating the builder with no spans configured. 109 | /// 110 | /// NB. This is **NOT** the same as [`TracingBuilder::default`] which configures ALL default 111 | /// spans. 112 | pub fn new() -> Self { 113 | Self { 114 | service_ready: None, 115 | request: None, 116 | notification: None, 117 | event: None, 118 | } 119 | } 120 | 121 | /// Set a [`tracing::Span`] builder to instrument [`Service::poll_ready`] method. 122 | pub fn service_ready(mut self, f: fn() -> Span) -> Self { 123 | self.service_ready = Some(f); 124 | self 125 | } 126 | 127 | /// Set a [`tracing::Span`] builder to instrument [`Future::poll`] of the `Future` returned by 128 | /// [`Service::call`]. 129 | pub fn request(mut self, f: fn(&AnyRequest) -> Span) -> Self { 130 | self.request = Some(f); 131 | self 132 | } 133 | 134 | /// Set a [`tracing::Span`] builder to instrument [`LspService::notify`]. 135 | pub fn notification(mut self, f: fn(&AnyNotification) -> Span) -> Self { 136 | self.notification = Some(f); 137 | self 138 | } 139 | 140 | /// Set a [`tracing::Span`] builder to instrument [`LspService::emit`]. 141 | pub fn event(mut self, f: fn(&AnyEvent) -> Span) -> Self { 142 | self.event = Some(f); 143 | self 144 | } 145 | 146 | /// Build the middleware with the current configuration. 147 | pub fn build(&self, service: S) -> Tracing { 148 | Tracing { 149 | service, 150 | spans: self.clone(), 151 | } 152 | } 153 | } 154 | 155 | /// A type alias of [`TracingLayer`] conforming to the naming convention of [`tower_layer`]. 156 | pub type TracingLayer = TracingBuilder; 157 | 158 | impl Layer for TracingBuilder { 159 | type Service = Tracing; 160 | 161 | fn layer(&self, inner: S) -> Self::Service { 162 | self.build(inner) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /tests/client_test_data/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 = "rust-analyzer-client-test" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /tests/client_test_data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-analyzer-client-test" 3 | version = "0.0.0" 4 | 5 | [workspace] 6 | -------------------------------------------------------------------------------- /tests/client_test_data/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Unused here. In-memory `textDocument/didOpen` is preferred over on-disk contents. 2 | -------------------------------------------------------------------------------- /tests/stdio.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(unix), allow(unused))] 2 | use std::io::{Read, Write}; 3 | use std::process::Stdio; 4 | use std::time::Duration; 5 | 6 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 7 | use tokio::time::timeout; 8 | 9 | const CHILD_ENV: &str = "IS_STDIO_TEST_CHILD"; 10 | 11 | const READ_TIMEOUT: Duration = Duration::from_millis(500); 12 | const SCHED_TIMEOUT: Duration = Duration::from_millis(100); 13 | 14 | // Do nothing for non-UNIX targets: `stdio` module is not available. 15 | #[cfg(not(unix))] 16 | fn main() {} 17 | 18 | #[cfg(unix)] 19 | fn main() { 20 | if std::env::var(CHILD_ENV).is_err() { 21 | parent(); 22 | } else { 23 | tokio::runtime::Builder::new_current_thread() 24 | .enable_all() 25 | .build() 26 | .unwrap() 27 | .block_on(child()); 28 | } 29 | } 30 | 31 | #[cfg(unix)] 32 | fn parent() { 33 | let this_exe = std::env::current_exe().unwrap(); 34 | let mut child = std::process::Command::new(this_exe) 35 | .env(CHILD_ENV, "1") 36 | .stdin(Stdio::piped()) 37 | .stdout(Stdio::piped()) 38 | .stderr(Stdio::inherit()) 39 | .spawn() 40 | .expect("failed to spawn"); 41 | let childin = child.stdin.as_mut().unwrap(); 42 | let childout = child.stdout.as_mut().unwrap(); 43 | 44 | let mut buf = [0u8; 64]; 45 | // Wait for the signal. 46 | assert_eq!(childout.read(&mut buf).unwrap(), 4); 47 | assert_eq!(&buf[..4], b"ping"); 48 | // Reply back. 49 | childin.write_all(b"pong").unwrap(); 50 | 51 | // NB. Wait for its exit first, without draining `childout`. Because the child keeps writing 52 | // until the kernel buffer is full. 53 | let output = child.wait_with_output().unwrap(); 54 | assert!(output.status.success()); 55 | // The last one is written by the std blocking call `print!`. 56 | assert_eq!(output.stdout, b"2"); 57 | } 58 | 59 | #[cfg(unix)] 60 | async fn child() { 61 | use async_lsp::stdio::{PipeStdin, PipeStdout}; 62 | 63 | let mut stdin = PipeStdin::lock_tokio().unwrap(); 64 | let mut stdout = PipeStdout::lock_tokio().unwrap(); 65 | let mut buf = [0u8; 64]; 66 | 67 | // Should be blocked since we are holding lock guards in `PipeStd{in,out}`. 68 | let std_stdin = tokio::task::spawn_blocking(|| drop(std::io::stdin().lock())); 69 | let std_stdout = tokio::task::spawn_blocking(|| print!("2")); 70 | 71 | timeout(READ_TIMEOUT, stdin.read(&mut buf)) 72 | .await 73 | .expect_err("should timeout"); 74 | 75 | // Signal the parent to send us something. This should not block due to pipe buffer. 76 | timeout(Duration::ZERO, stdout.write_all(b"ping")) 77 | .await 78 | .expect("should not block") 79 | .unwrap(); 80 | 81 | assert_eq!( 82 | timeout(SCHED_TIMEOUT, stdin.read(&mut buf)) 83 | .await 84 | .expect("should not timeout") 85 | .expect("should read something"), 86 | 4 87 | ); 88 | assert_eq!(&buf[..4], b"pong"); 89 | 90 | // Still blocked yet. 91 | assert!(!std_stdin.is_finished()); 92 | assert!(!std_stdout.is_finished()); 93 | 94 | // Drop lock guards, then std operations unblock. 95 | drop(stdin); 96 | drop(stdout); 97 | timeout(SCHED_TIMEOUT, std_stdin) 98 | .await 99 | .expect("no timeout") 100 | .expect("no panic"); 101 | timeout(SCHED_TIMEOUT, std_stdout) 102 | .await 103 | .expect("no timeout") 104 | .expect("no panic"); 105 | } 106 | -------------------------------------------------------------------------------- /tests/unit_test.rs: -------------------------------------------------------------------------------- 1 | //! An example for unit-testing via mocking servers and/or clients. 2 | // TODO: Make this more egornomic. Maybe provide some test APIs? 3 | use std::ops::ControlFlow; 4 | 5 | use async_lsp::router::Router; 6 | use async_lsp::server::LifecycleLayer; 7 | use async_lsp::{ClientSocket, LanguageClient, LanguageServer}; 8 | use futures::channel::mpsc; 9 | use futures::{AsyncReadExt, StreamExt}; 10 | use lsp_types::{ 11 | notification, request, ConfigurationItem, ConfigurationParams, Hover, HoverContents, 12 | HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, 13 | MarkedString, MessageType, Position, ServerCapabilities, ShowMessageParams, 14 | TextDocumentIdentifier, TextDocumentPositionParams, WorkDoneProgressParams, 15 | }; 16 | use tokio_util::compat::TokioAsyncReadCompatExt; 17 | use tower::ServiceBuilder; 18 | 19 | const MEMORY_CHANNEL_SIZE: usize = 64 << 10; // 64KiB 20 | 21 | struct ServerState { 22 | client: ClientSocket, 23 | } 24 | 25 | struct ClientState { 26 | msg_tx: mpsc::UnboundedSender, 27 | } 28 | 29 | #[tokio::test(flavor = "current_thread")] 30 | async fn mock_server_and_client() { 31 | // The server with handlers. 32 | let (server_main, mut client) = async_lsp::MainLoop::new_server(|client| { 33 | let mut router = Router::new(ServerState { client }); 34 | router 35 | .request::(|_st, _params| async move { 36 | Ok(InitializeResult { 37 | capabilities: ServerCapabilities { 38 | hover_provider: Some(HoverProviderCapability::Simple(true)), 39 | ..ServerCapabilities::default() 40 | }, 41 | server_info: None, 42 | }) 43 | }) 44 | .notification::(|_, _| ControlFlow::Continue(())) 45 | .request::(|_, _| async move { Ok(()) }) 46 | .notification::(|_, _| ControlFlow::Break(Ok(()))) 47 | .request::(|st, _params| { 48 | let mut client = st.client.clone(); 49 | async move { 50 | // Optionally interact with client. 51 | let text = client 52 | .configuration(ConfigurationParams { 53 | items: vec![ConfigurationItem { 54 | scope_uri: None, 55 | section: Some("mylsp.hoverText".into()), 56 | }], 57 | }) 58 | .await 59 | .ok() 60 | .and_then(|ret| Some(ret[0].as_str()?.to_owned())) 61 | .unwrap_or_default(); 62 | 63 | // Respond. 64 | Ok(Some(Hover { 65 | contents: HoverContents::Scalar(MarkedString::String(text)), 66 | range: None, 67 | })) 68 | } 69 | }); 70 | 71 | ServiceBuilder::new() 72 | .layer(LifecycleLayer::default()) 73 | .service(router) 74 | }); 75 | 76 | // The client with handlers. 77 | let (msg_tx, mut msg_rx) = mpsc::unbounded(); 78 | let (client_main, mut server) = async_lsp::MainLoop::new_client(|_server| { 79 | let mut router = Router::new(ClientState { msg_tx }); 80 | router 81 | .notification::(|st, params| { 82 | st.msg_tx.unbounded_send(params.message).unwrap(); 83 | ControlFlow::Continue(()) 84 | }) 85 | .request::(|_st, _params| async move { 86 | Ok(vec!["Some hover text".into()]) 87 | }); 88 | ServiceBuilder::new().service(router) 89 | }); 90 | 91 | // Wire up a loopback channel between the server and the client. 92 | let (server_stream, client_stream) = tokio::io::duplex(MEMORY_CHANNEL_SIZE); 93 | let (server_rx, server_tx) = server_stream.compat().split(); 94 | let server_main = tokio::spawn(async move { 95 | server_main 96 | .run_buffered(server_rx, server_tx) 97 | .await 98 | .unwrap(); 99 | }); 100 | let (client_rx, client_tx) = client_stream.compat().split(); 101 | let client_main = tokio::spawn(async move { 102 | let err = client_main 103 | .run_buffered(client_rx, client_tx) 104 | .await 105 | .unwrap_err(); 106 | assert!( 107 | matches!(err, async_lsp::Error::Eof), 108 | "should fail due to EOF: {err}" 109 | ); 110 | }); 111 | 112 | // Send requests to the server on behalf of the client, via `ServerSocket`. It interacts with 113 | // the client main loop to finalize and send the request through the channel. 114 | server 115 | .initialize(InitializeParams::default()) 116 | .await 117 | .unwrap(); 118 | // Send notifications. Note that notifications are delivered asynchronously, but in order. 119 | server.initialized(InitializedParams {}).unwrap(); 120 | 121 | // After the initialization sequence, do some real requests. 122 | let ret = server 123 | .hover(HoverParams { 124 | text_document_position_params: TextDocumentPositionParams { 125 | text_document: TextDocumentIdentifier::new("file:///foo".parse().unwrap()), 126 | position: Position::new(0, 0), 127 | }, 128 | work_done_progress_params: WorkDoneProgressParams::default(), 129 | }) 130 | .await 131 | .unwrap(); 132 | assert_eq!( 133 | ret, 134 | Some(Hover { 135 | contents: HoverContents::Scalar(MarkedString::String("Some hover text".into())), 136 | range: None 137 | }) 138 | ); 139 | 140 | // In contrast, send notifications to the client on behalf of the server, via `ClientSocket`. 141 | client 142 | .show_message(ShowMessageParams { 143 | typ: MessageType::INFO, 144 | message: "Some message".into(), 145 | }) 146 | .unwrap(); 147 | // Here the client may not get notification delivered yet. Wait for it. 148 | assert_eq!(msg_rx.next().await.unwrap(), "Some message"); 149 | 150 | // Shutdown the server. 151 | server.shutdown(()).await.unwrap(); 152 | server.exit(()).unwrap(); 153 | 154 | // Both main loop should be shutdown. 155 | server_main.await.expect("no panic"); 156 | client_main.await.expect("no panic"); 157 | } 158 | --------------------------------------------------------------------------------