mirror of
https://github.com/oxalica/nil.git
synced 2024-11-22 11:22:46 +03:00
Switch to async-lsp framework
This commit is contained in:
parent
1d5bc81023
commit
0edd88a682
429
Cargo.lock
generated
429
Cargo.lock
generated
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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(
|
||||
|
@ -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?)
|
||||
}
|
||||
|
@ -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:#}");
|
||||
|
@ -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 = ¶ms.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(¶ms.text_document.uri);
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn on_did_change(&mut self, params: DidChangeTextDocumentParams) -> NotifyResult {
|
||||
let mut vfs = self.vfs.write().unwrap();
|
||||
let uri = ¶ms.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 = ¶ms.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(¶ms.text_document.uri);
|
||||
})
|
||||
.on_sync_mut::<notif::DidChangeTextDocument>(|st, params| {
|
||||
let mut vfs = st.vfs.write().unwrap();
|
||||
let uri = ¶ms.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",
|
||||
))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user