1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-22 02:55:39 +03:00

Switch to async-lsp framework

This commit is contained in:
oxalica 2023-04-17 11:21:52 +08:00
parent 1d5bc81023
commit 0edd88a682
6 changed files with 698 additions and 513 deletions

429
Cargo.lock generated
View File

@ -36,13 +36,32 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
[[package]]
name = "async-lsp"
version = "0.0.1"
source = "git+https://github.com/oxalica/async-lsp.git#36d0b1617c3c559aff86f4d74ddedb20a166bb3a"
dependencies = [
"either",
"futures",
"lsp-types",
"pin-project-lite",
"rustix",
"serde",
"serde_json",
"thiserror",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
"winapi",
]
@ -69,6 +88,18 @@ dependencies = [
"serde_json",
]
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -91,16 +122,6 @@ version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
@ -122,6 +143,27 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "expect-test"
version = "1.4.1"
@ -141,6 +183,83 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -165,6 +284,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "ide"
version = "0.0.0"
@ -222,6 +347,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
@ -255,6 +391,12 @@ version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
[[package]]
name = "lock_api"
version = "0.4.9"
@ -274,18 +416,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "lsp-server"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9b4c78d1c3f35c5864c90e9633377b5f374a4a4983ac64c30b8ae898f9305"
dependencies = [
"crossbeam-channel",
"log",
"serde",
"serde_json",
]
[[package]]
name = "lsp-types"
version = "0.94.0"
@ -323,24 +453,37 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "nil"
version = "0.0.0"
dependencies = [
"anyhow",
"argh",
"async-lsp",
"atty",
"codespan-reporting",
"crossbeam-channel",
"ide",
"libc",
"log",
"lsp-server",
"lsp-types",
"nix-interop",
"serde_json",
"syntax",
"text-size",
"tokio",
"tower",
"tracing",
"tracing-subscriber",
]
@ -462,6 +605,12 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.56"
@ -557,6 +706,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "ryu"
version = "1.0.13"
@ -655,6 +818,15 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@ -670,6 +842,16 @@ dependencies = [
"serde",
]
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -717,6 +899,26 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "thread_local"
version = "1.1.7"
@ -742,6 +944,44 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2",
"windows-sys 0.45.0",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
@ -749,6 +989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -854,6 +1095,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
@ -884,3 +1131,135 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View File

@ -8,17 +8,18 @@ rust-version.workspace = true
[dependencies]
anyhow = "1.0.68"
argh = "0.1.10"
async-lsp = { git = "https://github.com/oxalica/async-lsp.git" }
atty = "0.2.14"
codespan-reporting = "0.11.1"
crossbeam-channel = "0.5.6"
ide = { path = "../ide" }
log = "0.4.17"
lsp-server = "0.7.0"
lsp-types = "0.94.0"
nix-interop = { path = "../nix-interop" }
serde_json = "1.0.82"
syntax = { path = "../syntax" }
text-size = "1.1.0"
tokio = { version = "1.27.0", features = ["io-std", "rt", "sync"] }
tower = "0.4.13"
tracing = { version = "0.1.36", features = ["release_max_level_info"] }
[dependencies.tracing-subscriber]

View File

@ -1,9 +1,9 @@
use crate::{semantic_tokens, LineMap, LspError, Result, Vfs};
use crate::{semantic_tokens, LineMap, Result, Vfs};
use async_lsp::{ErrorCode, ResponseError};
use ide::{
Assist, AssistKind, CompletionItem, CompletionItemKind, Diagnostic, FileId, FilePos, FileRange,
HlRange, HlRelated, HoverResult, NameKind, Severity, SymbolTree, TextEdit, WorkspaceEdit,
};
use lsp_server::ErrorCode;
use lsp_types::{
self as lsp, CodeAction, CodeActionKind, CodeActionOrCommand, DiagnosticRelatedInformation,
DiagnosticSeverity, DiagnosticTag, DocumentHighlight, DocumentHighlightKind, DocumentSymbol,
@ -164,11 +164,8 @@ pub(crate) fn to_completion_item(line_map: &LineMap, item: CompletionItem) -> ls
}
}
pub(crate) fn to_rename_error(message: String) -> LspError {
LspError {
code: ErrorCode::InvalidRequest,
message,
}
pub(crate) fn to_rename_error(message: String) -> ResponseError {
ResponseError::new(ErrorCode::REQUEST_FAILED, message)
}
pub(crate) fn to_prepare_rename_response(

View File

@ -8,10 +8,14 @@ mod server;
mod vfs;
use anyhow::Result;
use async_lsp::client_monitor::ClientProcessMonitorLayer;
use async_lsp::concurrency::ConcurrencyLayer;
use async_lsp::server::LifecycleLayer;
use async_lsp::tracing::TracingLayer;
use ide::VfsPath;
use lsp_server::{Connection, ErrorCode};
use lsp_types::Url;
use std::fmt;
use tokio::io::BufReader;
use tower::ServiceBuilder;
pub(crate) use server::{Server, StateSnapshot};
pub(crate) use vfs::{LineMap, Vfs};
@ -27,21 +31,6 @@ pub(crate) use vfs::{LineMap, Vfs};
/// If you have any real world usages for files larger than this, please file an issue.
pub const MAX_FILE_LEN: usize = 128 << 20;
#[derive(Debug)]
pub(crate) struct LspError {
code: ErrorCode,
message: String,
}
impl fmt::Display for LspError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NB. This will be displayed in the editor.
self.message.fmt(f)
}
}
impl std::error::Error for LspError {}
pub(crate) trait UrlExt: Sized {
fn to_vfs_path(&self) -> VfsPath;
fn from_vfs_path(path: &VfsPath) -> Self;
@ -67,14 +56,27 @@ impl UrlExt for Url {
}
}
pub fn run_server_stdio() -> Result<()> {
let (conn, io_threads) = Connection::stdio();
pub async fn run_server_stdio() -> Result<()> {
let concurrency = match std::thread::available_parallelism() {
Ok(n) => n.get(),
Err(err) => {
tracing::error!("Failed to get available parallelism: {err}");
1
}
};
tracing::info!("Max concurrent requests: {concurrency}");
let server = Server::new(conn.sender, conn.receiver);
server.run()?;
let (frontend, _) = async_lsp::Frontend::new_server(|client| {
ServiceBuilder::new()
.layer(TracingLayer::default())
.layer(LifecycleLayer)
// TODO: Use `CatchUnwindLayer`.
.layer(ConcurrencyLayer::new(concurrency))
.layer(ClientProcessMonitorLayer::new(client.clone()))
.service(Server::new_router(client))
});
tracing::info!("Leaving main loop");
io_threads.join()?;
Ok(())
let input = BufReader::new(tokio::io::stdin());
let output = tokio::io::stdout();
Ok(frontend.run(input, output).await?)
}

View File

@ -93,7 +93,12 @@ fn main() {
);
}
match nil::run_server_stdio() {
let ret = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to spawn tokio runtime")
.block_on(nil::run_server_stdio());
match ret {
Ok(()) => {}
Err(err) => {
tracing::error!("Unexpected error: {err:#}");

View File

@ -1,45 +1,47 @@
use crate::capabilities::server_capabilities;
use crate::config::{Config, CONFIG_KEY};
use crate::{convert, handler, lsp_ext, LspError, UrlExt, Vfs, MAX_FILE_LEN};
use anyhow::{anyhow, bail, Context, Result};
use crossbeam_channel::{Receiver, Sender};
use crate::{convert, handler, lsp_ext, UrlExt, Vfs, MAX_FILE_LEN};
use anyhow::{bail, Context, Result};
use async_lsp::router::Router;
use async_lsp::{ClientSocket, ErrorCode, LanguageClient, ResponseError};
use ide::{Analysis, AnalysisHost, Cancelled, FlakeInfo, VfsPath};
use lsp_server::{
Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response,
};
use lsp_types::notification::Notification as _;
use lsp_types::request::{self as req, Request};
use lsp_types::{
notification as notif, request as req, ConfigurationItem, ConfigurationParams, Diagnostic,
InitializeParams, InitializeResult, MessageType, NumberOrString, PublishDiagnosticsParams,
ServerInfo, ShowMessageParams, Url,
notification as notif, ConfigurationItem, ConfigurationParams, Diagnostic,
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, InitializeParams, InitializeResult, InitializedParams, MessageType,
PublishDiagnosticsParams, ServerInfo, ShowMessageParams, Url,
};
use nix_interop::nixos_options::NixosOptions;
use nix_interop::{flake_lock, FLAKE_FILE, FLAKE_LOCK_FILE};
use std::backtrace::Backtrace;
use std::borrow::BorrowMut;
use std::cell::Cell;
use std::collections::HashMap;
use std::future::{ready, Future};
use std::io::ErrorKind;
use std::ops::ControlFlow;
use std::panic::UnwindSafe;
use std::path::Path;
use std::sync::{Arc, Once, RwLock};
use std::{fs, panic, thread};
use std::{fmt, fs, panic};
use tokio::task;
const LSP_SERVER_NAME: &str = "nil";
const NIXOS_OPTIONS_FLAKE_INPUT: &str = "nixpkgs";
type ReqHandler = Box<dyn FnOnce(&mut Server, Response) + 'static>;
type NotifyResult = ControlFlow<async_lsp::Result<()>>;
type Task = Box<dyn FnOnce() -> Event + Send + 'static>;
enum Event {
Response(Response),
LoadConfig(serde_json::Value),
Diagnostics {
uri: Url,
version: u64,
diagnostics: Vec<Diagnostic>,
},
ClientExited,
LoadFlake(Result<LoadFlakeResult>),
NixosOptions(Result<NixosOptions>),
}
@ -59,17 +61,13 @@ pub struct Server {
vfs: Arc<RwLock<Vfs>>,
opened_files: HashMap<Url, FileData>,
config: Arc<Config>,
is_shutdown: bool,
/// Monotonic version counter for diagnostics calculation ordering.
version_counter: u64,
/// Tried to load flake?
/// This is used to reload flake only once after the configuration is first loaded.
tried_flake_load: bool,
// Message passing.
req_queue: ReqQueue<(), ReqHandler>,
lsp_tx: Sender<Message>,
lsp_rx: Receiver<Message>,
task_tx: Sender<Task>,
event_tx: Sender<Event>,
event_rx: Receiver<Event>,
client: ClientSocket,
}
#[derive(Debug, Default)]
@ -79,133 +77,176 @@ struct FileData {
}
impl Server {
pub fn new(lsp_tx: Sender<Message>, lsp_rx: Receiver<Message>) -> Self {
let (task_tx, task_rx) = crossbeam_channel::unbounded();
let (event_tx, event_rx) = crossbeam_channel::unbounded();
let worker_cnt = thread::available_parallelism().map_or(1, |n| n.get());
for _ in 0..worker_cnt {
let task_rx = task_rx.clone();
let event_tx = event_tx.clone();
thread::Builder::new()
.name("Worker".into())
.spawn(move || Self::worker(task_rx, event_tx))
.expect("Failed to spawn worker threads");
}
tracing::info!("Started {worker_cnt} workers");
pub fn new_router(client: ClientSocket) -> Router<Self> {
let this = Self::new(client);
let mut router = Router::new(this);
router
//// Lifecycle ////
.request::<req::Initialize, _>(Self::on_initialize)
.notification::<notif::Initialized>(Self::on_initialized)
.request::<req::Shutdown, _>(|_, _| ready(Ok(())))
.notification::<notif::Exit>(|_, _| ControlFlow::Break(Ok(())))
//// Notifications ////
.notification::<notif::DidOpenTextDocument>(Self::on_did_open)
.notification::<notif::DidCloseTextDocument>(Self::on_did_close)
.notification::<notif::DidChangeTextDocument>(Self::on_did_change)
.notification::<notif::DidChangeConfiguration>(Self::on_did_change_configuration)
// Workaround:
// > In former implementations clients pushed file events without the server actively asking for it.
// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWatchedFiles
.notification::<notif::DidChangeWatchedFiles>(|_, _| ControlFlow::Continue(()))
//// Requests ////
.request_snap::<req::GotoDefinition>(handler::goto_definition)
.request_snap::<req::References>(handler::references)
.request_snap::<req::Completion>(handler::completion)
.request_snap::<req::SelectionRangeRequest>(handler::selection_range)
.request_snap::<req::PrepareRenameRequest>(handler::prepare_rename)
.request_snap::<req::Rename>(handler::rename)
.request_snap::<req::SemanticTokensFullRequest>(handler::semantic_token_full)
.request_snap::<req::SemanticTokensRangeRequest>(handler::semantic_token_range)
.request_snap::<req::HoverRequest>(handler::hover)
.request_snap::<req::DocumentSymbolRequest>(handler::document_symbol)
.request_snap::<req::Formatting>(handler::formatting)
.request_snap::<req::DocumentLinkRequest>(handler::document_links)
.request_snap::<req::CodeActionRequest>(handler::code_action)
.request_snap::<req::DocumentHighlightRequest>(handler::document_highlight)
.request_snap::<lsp_ext::ParentModule>(handler::parent_module)
//// Events ////
.event(Self::on_event);
router
}
pub fn new(client: ClientSocket) -> Self {
Self {
host: AnalysisHost::default(),
vfs: Arc::new(RwLock::new(Vfs::new())),
opened_files: HashMap::default(),
// Will be initialized in `Server::run`.
// Will be set during initialization.
config: Arc::new(Config::new("/non-existing-path".into())),
is_shutdown: false,
version_counter: 0,
tried_flake_load: false,
req_queue: ReqQueue::default(),
lsp_tx,
lsp_rx,
task_tx,
event_tx,
event_rx,
client,
}
}
fn worker(task_rx: Receiver<Task>, event_tx: Sender<Event>) {
while let Ok(task) = task_rx.recv() {
if event_tx.send(task()).is_err() {
break;
}
}
// TODO: Refactor blocking tasks into async tasks as possible.
fn spawn_task(&self, task: Task) {
let client = self.client.clone();
task::spawn(async move {
let ret: Event = task::spawn_blocking(task).await.expect("Task panicked");
let _: Result<_, _> = client.emit(ret);
});
}
pub fn run(mut self) -> Result<()> {
self.init()?;
fn on_initialize(
&mut self,
params: InitializeParams,
) -> impl Future<Output = Result<InitializeResult, ResponseError>> {
tracing::info!("Init params: {params:?}");
loop {
crossbeam_channel::select! {
recv(self.lsp_rx) -> msg => {
match msg.context("Channel closed")? {
Message::Request(req) => self.dispatch_request(req),
Message::Notification(notif) => {
if notif.method == notif::Exit::METHOD {
return Ok(());
}
self.dispatch_notification(notif);
}
Message::Response(resp) => {
if let Some(callback) = self.req_queue.outgoing.complete(resp.id.clone()) {
callback(&mut self, resp);
}
}
}
}
recv(self.event_rx) -> event => {
self.dispatch_event(event.context("Worker panicked")?)?;
}
}
}
}
fn init(&mut self) -> Result<()> {
let init_params = {
let conn = Connection {
sender: self.lsp_tx.clone(),
receiver: self.lsp_rx.clone(),
};
let (req_id, init_params) = conn.initialize_start()?;
tracing::info!("Init params: {}", init_params);
let init_params = serde_json::from_value::<InitializeParams>(init_params)
.context("Invalid init_params")?;
let init_ret = InitializeResult {
capabilities: server_capabilities(),
server_info: Some(ServerInfo {
name: LSP_SERVER_NAME.into(),
version: option_env!("CFG_RELEASE").map(Into::into),
}),
};
conn.initialize_finish(req_id, serde_json::to_value(init_ret).unwrap())?;
init_params
};
let root_path = match init_params
// TODO: Use `workspaceFolders`.
let root_path = match params
.root_uri
.as_ref()
.and_then(|uri| uri.to_file_path().ok())
{
Some(path) => path,
None => std::env::current_dir().context("Failed to the current directory")?,
None => std::env::current_dir().expect("Failed to the current directory"),
};
*Arc::get_mut(&mut self.config).expect("No concurrent access yet") = Config::new(root_path);
if let Some(pid) = init_params.process_id {
let event_tx = self.event_tx.clone();
thread::spawn(move || match wait_for_pid(pid as _) {
Ok(()) => {
let _ = event_tx.send(Event::ClientExited);
}
Err(err) => {
tracing::warn!("Failed to monitor parent pid {}: {}", pid, err);
}
});
}
ready(Ok(InitializeResult {
capabilities: server_capabilities(),
server_info: Some(ServerInfo {
name: LSP_SERVER_NAME.into(),
version: option_env!("CFG_RELEASE").map(Into::into),
}),
}))
}
fn on_initialized(&mut self, _params: InitializedParams) -> NotifyResult {
// Load configurations before loading flake.
// The latter depends on `nix.binary`.
self.load_config(|st| {
// TODO: Register file watcher for flake.lock.
st.load_flake();
});
self.spawn_reload_config();
Ok(())
ControlFlow::Continue(())
}
fn dispatch_event(&mut self, event: Event) -> Result<()> {
fn on_did_open(&mut self, params: DidOpenTextDocumentParams) -> NotifyResult {
// Ignore the open event for unsupported files, thus all following interactions
// will error due to unopened files.
let len = params.text_document.text.len();
if len > MAX_FILE_LEN {
self.client.show_message_ext(
MessageType::WARNING,
"Disable LSP functionalities for too large file ({len} > {MAX_FILE_LEN})",
);
return ControlFlow::Continue(());
}
let uri = &params.text_document.uri;
self.set_vfs_file_content(uri, params.text_document.text);
self.opened_files.insert(uri.clone(), FileData::default());
ControlFlow::Continue(())
}
fn on_did_close(&mut self, params: DidCloseTextDocumentParams) -> NotifyResult {
// N.B. Don't clear text here.
self.opened_files.remove(&params.text_document.uri);
ControlFlow::Continue(())
}
fn on_did_change(&mut self, params: DidChangeTextDocumentParams) -> NotifyResult {
let mut vfs = self.vfs.write().unwrap();
let uri = &params.text_document.uri;
// Ignore files not maintained in Vfs.
let Ok(file) = vfs.file_for_uri(uri) else { return ControlFlow::Continue(()) };
for change in params.content_changes {
let ret = (|| {
let del_range = match change.range {
None => None,
Some(range) => Some(convert::from_range(&vfs, file, range).ok()?.1),
};
vfs.change_file_content(file, del_range, &change.text)
.ok()?;
Some(())
})();
if ret.is_none() {
tracing::error!(
"File is out of sync! Failed to apply change for {uri}: {change:?}"
);
// Clear file states to minimize pollution of the broken state.
self.opened_files.remove(uri);
// TODO: Remove the file from Vfs.
}
}
drop(vfs);
// FIXME: This blocks.
self.apply_vfs_change();
ControlFlow::Continue(())
}
fn on_did_change_configuration(
&mut self,
_params: DidChangeConfigurationParams,
) -> NotifyResult {
// As stated in https://github.com/microsoft/language-server-protocol/issues/676,
// this notification's parameters should be ignored and the actual config queried separately.
self.spawn_reload_config();
ControlFlow::Continue(())
}
fn on_event(&mut self, event: Event) -> NotifyResult {
match event {
Event::Response(resp) => {
if let Some(()) = self.req_queue.incoming.complete(resp.id.clone()) {
self.lsp_tx.send(resp.into()).unwrap();
Event::LoadConfig(v) => {
self.update_config(v);
if !self.tried_flake_load {
self.tried_flake_load = true;
// TODO: Register file watcher for flake.lock.
self.spawn_load_flake();
}
}
Event::Diagnostics {
@ -220,20 +261,22 @@ impl Server {
"Push {} diagnostics of {uri}, version {version}",
diagnostics.len(),
);
self.send_notification::<notif::PublishDiagnostics>(PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
task::spawn({
let mut client = self.client.clone();
async move {
client.publish_diagnostics(PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
})
}
});
}
_ => tracing::debug!("Ignore raced diagnostics of {uri}, version {version}"),
},
Event::ClientExited => {
bail!("The process initializing this server is exited. Exit now")
}
Event::LoadFlake(ret) => match ret {
Err(err) => {
self.show_message(
self.client.show_message_ext(
MessageType::ERROR,
format!("Failed to load flake workspace: {err:#}"),
);
@ -246,7 +289,7 @@ impl Server {
"Workspace is a flake (missing_inputs = {missing_inputs}): {flake_info:?}"
);
if missing_inputs {
self.show_message(MessageType::WARNING, "Some flake inputs are not available, please run `nix flake archive` to fetch all inputs");
self.client.show_message_ext(MessageType::WARNING, "Some flake inputs are not available, please run `nix flake archive` to fetch all inputs");
}
// TODO: A better way to retrieve the nixpkgs for options?
@ -258,14 +301,12 @@ impl Server {
let nixpkgs_path = nixpkgs_path.to_owned();
let nix_binary = self.config.nix_binary.clone();
tracing::info!("Evaluating NixOS options from {}", nixpkgs_path.display());
self.task_tx
.send(Box::new(move || {
Event::NixosOptions(nix_interop::nixos_options::eval_all_options(
&nix_binary,
&nixpkgs_path,
))
}))
.unwrap();
self.spawn_task(Box::new(move || {
Event::NixosOptions(nix_interop::nixos_options::eval_all_options(
&nix_binary,
&nixpkgs_path,
))
}));
}
self.vfs.write().unwrap().set_flake_info(Some(flake_info));
@ -292,115 +333,11 @@ impl Server {
}
},
}
Ok(())
ControlFlow::Continue(())
}
fn dispatch_request(&mut self, req: Request) {
if self.is_shutdown {
let resp = Response::new_err(
req.id,
ErrorCode::InvalidRequest as i32,
"Shutdown already requested.".into(),
);
self.lsp_tx.send(resp.into()).unwrap();
return;
}
RequestDispatcher(self, Some(req))
.on_sync_mut::<req::Shutdown>(|st, ()| {
st.is_shutdown = true;
Ok(())
})
.on::<req::GotoDefinition>(handler::goto_definition)
.on::<req::References>(handler::references)
.on::<req::Completion>(handler::completion)
.on::<req::SelectionRangeRequest>(handler::selection_range)
.on::<req::PrepareRenameRequest>(handler::prepare_rename)
.on::<req::Rename>(handler::rename)
.on::<req::SemanticTokensFullRequest>(handler::semantic_token_full)
.on::<req::SemanticTokensRangeRequest>(handler::semantic_token_range)
.on::<req::HoverRequest>(handler::hover)
.on::<req::DocumentSymbolRequest>(handler::document_symbol)
.on::<req::Formatting>(handler::formatting)
.on::<req::DocumentLinkRequest>(handler::document_links)
.on::<req::CodeActionRequest>(handler::code_action)
.on::<req::DocumentHighlightRequest>(handler::document_highlight)
.on::<lsp_ext::ParentModule>(handler::parent_module)
.finish();
}
fn dispatch_notification(&mut self, notif: Notification) {
NotificationDispatcher(self, Some(notif))
.on_sync_mut::<notif::Cancel>(|st, params| {
let id: RequestId = match params.id {
NumberOrString::Number(id) => id.into(),
NumberOrString::String(id) => id.into(),
};
if let Some(resp) = st.req_queue.incoming.cancel(id) {
st.lsp_tx.send(resp.into()).unwrap();
}
})
.on_sync_mut::<notif::DidOpenTextDocument>(|st, params| {
// Ignore the open event for unsupported files, thus all following interactions
// will error due to unopened files.
let len = params.text_document.text.len();
if len > MAX_FILE_LEN {
st.show_message(
MessageType::WARNING,
"Disable LSP functionalities for too large file ({len} > {MAX_FILE_LEN})",
);
return;
}
let uri = &params.text_document.uri;
st.set_vfs_file_content(uri, params.text_document.text);
st.opened_files.insert(uri.clone(), FileData::default());
})
.on_sync_mut::<notif::DidCloseTextDocument>(|st, params| {
// N.B. Don't clear text here.
st.opened_files.remove(&params.text_document.uri);
})
.on_sync_mut::<notif::DidChangeTextDocument>(|st, params| {
let mut vfs = st.vfs.write().unwrap();
let uri = &params.text_document.uri;
// Ignore files not maintained in Vfs.
let Ok(file) = vfs.file_for_uri(uri) else { return };
for change in params.content_changes {
let ret = (|| {
let del_range = match change.range {
None => None,
Some(range) => Some(convert::from_range(&vfs, file, range).ok()?.1),
};
vfs.change_file_content(file, del_range, &change.text)
.ok()?;
Some(())
})();
if ret.is_none() {
tracing::error!(
"File is out of sync! Failed to apply change for {uri}: {change:?}"
);
// Clear file states to minimize pollution of the broken state.
st.opened_files.remove(uri);
// TODO: Remove the file from Vfs.
}
}
drop(vfs);
st.apply_vfs_change();
})
// As stated in https://github.com/microsoft/language-server-protocol/issues/676,
// this notification's parameters should be ignored and the actual config queried separately.
.on_sync_mut::<notif::DidChangeConfiguration>(|st, _params| {
st.load_config(|_| {});
})
// Workaround:
// > In former implementations clients pushed file events without the server actively asking for it.
// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWatchedFiles
.on_sync_mut::<notif::DidChangeWatchedFiles>(|_st, _params| {})
.finish();
}
/// Enqueue a task to reload the flake.{nix,lock} and the locked inputs.
fn load_flake(&self) {
/// Spawn a task to reload the flake.{nix,lock} and the locked inputs.
fn spawn_load_flake(&self) {
tracing::info!("Loading flake configuration");
let flake_path = self.config.root_path.join(FLAKE_FILE);
@ -472,70 +409,34 @@ impl Server {
},
})
};
self.task_tx
.send(Box::new(move || Event::LoadFlake(task())))
.unwrap();
self.spawn_task(Box::new(move || Event::LoadFlake(task())));
}
fn send_request<R: req::Request>(
&mut self,
params: R::Params,
callback: impl FnOnce(&mut Self, Result<R::Result>) + 'static,
) {
let callback = |this: &mut Self, resp: Response| {
let ret = match resp.error {
None => serde_json::from_value(resp.result.unwrap_or_default()).map_err(Into::into),
Some(err) => Err(anyhow!(
"Request failed with {}: {}, data: {:?}",
err.code,
err.message,
err.data
)),
};
callback(this, ret);
};
let req = self
.req_queue
.outgoing
.register(R::METHOD.into(), params, Box::new(callback));
self.lsp_tx.send(req.into()).unwrap();
}
fn send_notification<N: notif::Notification>(&self, params: N::Params) {
self.lsp_tx
.send(Notification::new(N::METHOD.into(), params).into())
.unwrap();
}
// Maybe connect all tracing::* to LSP ShowMessage?
fn show_message(&self, typ: MessageType, message: impl Into<String>) {
let message = message.into();
if typ == MessageType::ERROR {
tracing::error!("{message}");
}
self.send_notification::<notif::ShowMessage>(ShowMessageParams { typ, message });
}
fn load_config(&mut self, callback: impl FnOnce(&mut Self) + 'static) {
self.send_request::<req::WorkspaceConfiguration>(
ConfigurationParams {
items: vec![ConfigurationItem {
scope_uri: None,
section: Some(CONFIG_KEY.into()),
}],
},
move |st, resp| {
match resp {
Ok(mut v) => {
tracing::debug!("Updating config: {:?}", v);
st.update_config(v.pop().unwrap_or_default());
}
Err(err) => tracing::error!("Failed to update config: {}", err),
fn spawn_reload_config(&self) {
let mut client = self.client.clone();
tokio::spawn(async move {
let ret = client
.configuration(ConfigurationParams {
items: vec![ConfigurationItem {
scope_uri: None,
section: Some(CONFIG_KEY.into()),
}],
})
.await;
let mut v = match ret {
Ok(v) => v,
Err(err) => {
client.show_message_ext(
MessageType::ERROR,
format_args!("Failed to update config: {err}"),
);
return;
}
callback(st);
},
);
};
tracing::debug!("Updating config: {:?}", v);
let v = v.pop().unwrap_or_default();
let _: Result<_, _> = client.emit(Event::LoadConfig(v));
});
}
fn update_config(&mut self, value: serde_json::Value) {
@ -549,7 +450,7 @@ impl Server {
.into_iter()
.chain(errors.iter().flat_map(|s| ["\n- ", s]))
.collect::<String>();
self.show_message(MessageType::ERROR, msg);
self.client.show_message_ext(MessageType::ERROR, msg);
}
// Refresh all diagnostics since the filter may be changed.
@ -581,7 +482,7 @@ impl Server {
diagnostics,
}
};
self.task_tx.send(Box::new(task)).unwrap();
self.spawn_task(Box::new(task));
}
fn next_version(&mut self) -> u64 {
@ -625,97 +526,52 @@ impl Server {
self.update_diagnostics(uri, version);
} else {
// Clear diagnostics.
self.event_tx
.send(Event::Diagnostics {
uri,
version,
diagnostics: Vec::new(),
})
.unwrap();
}
}
}
}
#[must_use = "RequestDispatcher::finish not called"]
struct RequestDispatcher<'s>(&'s mut Server, Option<Request>);
impl<'s> RequestDispatcher<'s> {
fn on_sync_mut<R: req::Request>(
mut self,
f: fn(&mut Server, R::Params) -> Result<R::Result>,
) -> Self {
if matches!(&self.1, Some(notif) if notif.method == R::METHOD) {
let req = self.1.take().unwrap();
let ret = (|| {
let params = serde_json::from_value::<R::Params>(req.params)?;
let v = f(self.0, params)?;
Ok(serde_json::to_value(v).unwrap())
})();
let resp = result_to_response(req.id, ret);
self.0.lsp_tx.send(resp.into()).unwrap();
}
self
}
fn on<R>(mut self, f: fn(StateSnapshot, R::Params) -> Result<R::Result>) -> Self
where
R: req::Request,
R::Params: 'static,
R::Result: 'static,
{
if matches!(&self.1, Some(notif) if notif.method == R::METHOD) {
let req = self.1.take().unwrap();
let snap = self.0.snapshot();
self.0.req_queue.incoming.register(req.id.clone(), ());
let task = move || {
let ret = with_catch_unwind(R::METHOD, || {
let params = serde_json::from_value::<R::Params>(req.params)?;
let resp = f(snap, params)?;
Ok(serde_json::to_value(resp)?)
let _: Result<_, _> = self.client.emit(Event::Diagnostics {
uri,
version,
diagnostics: Vec::new(),
});
Event::Response(result_to_response(req.id, ret))
};
self.0.task_tx.send(Box::new(task)).unwrap();
}
self
}
fn finish(self) {
if let Some(req) = self.1 {
let resp = Response::new_err(req.id, ErrorCode::MethodNotFound as _, String::new());
self.0.lsp_tx.send(resp.into()).unwrap();
}
}
}
#[must_use = "NotificationDispatcher::finish not called"]
struct NotificationDispatcher<'s>(&'s mut Server, Option<Notification>);
impl<'s> NotificationDispatcher<'s> {
fn on_sync_mut<N: notif::Notification>(mut self, f: fn(&mut Server, N::Params)) -> Self {
if matches!(&self.1, Some(notif) if notif.method == N::METHOD) {
match serde_json::from_value::<N::Params>(self.1.take().unwrap().params) {
Ok(params) => {
f(self.0, params);
}
Err(err) => {
tracing::error!("Failed to parse notification {}: {}", N::METHOD, err);
}
}
}
self
}
fn finish(self) {
if let Some(notif) = self.1 {
if !notif.method.starts_with("$/") {
tracing::error!("Unhandled notification: {:?}", notif);
}
}
}
}
trait RouterExt: BorrowMut<Router<Server>> {
fn request_snap<R: Request>(
&mut self,
f: impl Fn(StateSnapshot, R::Params) -> Result<R::Result> + Send + Copy + UnwindSafe + 'static,
) -> &mut Self
where
R::Params: Send + UnwindSafe + 'static,
R::Result: Send + 'static,
{
self.borrow_mut().request::<R, _>(move |this, params| {
let snap = this.snapshot();
async move {
task::spawn_blocking(move || with_catch_unwind(R::METHOD, move || f(snap, params)))
.await
.expect("Already catch_unwind")
.map_err(error_to_response)
}
});
self
}
}
impl RouterExt for Router<Server> {}
trait ClientExt: BorrowMut<ClientSocket> {
fn show_message_ext(&mut self, typ: MessageType, msg: impl fmt::Display) {
// Maybe connect all tracing::* to LSP ShowMessage?
let _: Result<_, _> = self.borrow_mut().show_message(ShowMessageParams {
typ,
message: msg.to_string(),
});
}
}
impl ClientExt for ClientSocket {}
fn with_catch_unwind<T>(ctx: &str, f: impl FnOnce() -> Result<T> + UnwindSafe) -> Result<T> {
static INSTALL_PANIC_HOOK: Once = Once::new();
thread_local! {
@ -755,30 +611,14 @@ fn with_catch_unwind<T>(ctx: &str, f: impl FnOnce() -> Result<T> + UnwindSafe) -
}
}
fn result_to_response(id: RequestId, ret: Result<serde_json::Value>) -> Response {
let err = match ret {
Ok(v) => {
return Response {
id,
result: Some(v),
error: None,
}
}
Err(err) => err,
};
fn error_to_response(err: anyhow::Error) -> ResponseError {
if err.is::<Cancelled>() {
// When client cancelled a request, a response is immediately sent back,
// and the response will be ignored.
return Response::new_err(id, ErrorCode::ServerCancelled as i32, "Cancelled".into());
return ResponseError::new(ErrorCode::REQUEST_CANCELLED, "Client cancelled");
}
if let Some(err) = err.downcast_ref::<LspError>() {
return Response::new_err(id, err.code as i32, err.to_string());
match err.downcast::<ResponseError>() {
Ok(resp) => resp,
Err(err) => ResponseError::new(ErrorCode::INTERNAL_ERROR, err),
}
if let Some(err) = err.downcast_ref::<serde_json::Error>() {
return Response::new_err(id, ErrorCode::InvalidParams as i32, err.to_string());
}
Response::new_err(id, ErrorCode::InternalError as i32, err.to_string())
}
#[derive(Debug)]
@ -793,42 +633,3 @@ impl StateSnapshot {
self.vfs.read().unwrap()
}
}
#[cfg(target_os = "linux")]
fn wait_for_pid(pid: u32) -> std::io::Result<()> {
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
let pidfd = unsafe {
let ret = libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0 as libc::c_int);
if ret >= 0 {
OwnedFd::from_raw_fd(ret as RawFd)
} else {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ESRCH) {
return Ok(());
}
return Err(err);
}
};
let mut pollfd = libc::pollfd {
fd: pidfd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe {
libc::poll(&mut pollfd, 1, -1 /* No timeout */)
};
// 1 fds.
if ret == 1 {
return Ok(());
}
Err(std::io::Error::last_os_error())
}
#[cfg(not(target_os = "linux"))]
fn wait_for_pid(_pid: u32) -> std::io::Result<()> {
Err(std::io::Error::new(
ErrorKind::Other,
"Waiting for arbitrary PID is not supported on this platform",
))
}