mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 07:04:22 +00:00
feature: move linked_projects
discovery to the rust-analyzer server
This commit is contained in:
parent
cf156a7a43
commit
db43a5a6e9
22 changed files with 877 additions and 169 deletions
127
Cargo.lock
generated
127
Cargo.lock
generated
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
@ -28,9 +28,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.83"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
|
@ -52,16 +52,16 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
|||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.71"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object 0.32.2",
|
||||
"object 0.35.0",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
|
@ -104,9 +104,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.6"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
|
||||
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -136,9 +136,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.97"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
|
||||
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg"
|
||||
|
@ -232,18 +232,18 @@ checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a"
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.12"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
|
||||
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
@ -269,9 +269,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
|
@ -366,9 +366,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
||||
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
|
@ -431,6 +431,7 @@ dependencies = [
|
|||
"crossbeam-channel",
|
||||
"paths",
|
||||
"process-wrap",
|
||||
"project-model",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -476,9 +477,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
@ -916,9 +917,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.37"
|
||||
version = "0.1.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7"
|
||||
checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -1086,18 +1087,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.41"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d"
|
||||
checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
@ -1162,9 +1163,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.49.0"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
|
||||
checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
@ -1187,18 +1188,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.33.0"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d"
|
||||
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -1223,9 +1224,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
|
@ -1379,9 +1380,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.82"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -1800,18 +1801,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.201"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.201"
|
||||
version = "1.0.203"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1843,9 +1844,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1867,9 +1868,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
|
||||
checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1922,9 +1923,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.63"
|
||||
version = "2.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
|
||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2009,18 +2010,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.60"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.60"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2104,9 +2105,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.12"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
@ -2116,18 +2117,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.12"
|
||||
version = "0.22.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@ -2201,9 +2202,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-tree"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675"
|
||||
checksum = "b56c62d2c80033cb36fae448730a2f2ef99410fe3ecbffc916681a32f6807dbe"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"tracing-core",
|
||||
|
@ -2213,9 +2214,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
|
||||
checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
|
@ -2555,9 +2556,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.8"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
|
||||
checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -24,6 +24,7 @@ process-wrap.workspace = true
|
|||
paths.workspace = true
|
||||
stdx.workspace = true
|
||||
toolchain.workspace = true
|
||||
project-model.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
|
@ -20,6 +20,7 @@ pub use cargo_metadata::diagnostic::{
|
|||
use toolchain::Tool;
|
||||
|
||||
mod command;
|
||||
pub mod project_json;
|
||||
mod test_runner;
|
||||
|
||||
use command::{CommandHandle, ParseFromLine};
|
||||
|
@ -240,7 +241,7 @@ enum FlycheckStatus {
|
|||
Finished,
|
||||
}
|
||||
|
||||
const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
|
||||
pub const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
|
||||
|
||||
impl FlycheckActor {
|
||||
fn new(
|
||||
|
|
152
crates/flycheck/src/project_json.rs
Normal file
152
crates/flycheck/src/project_json.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
//! A `cargo-metadata`-equivalent for non-Cargo build systems.
|
||||
use std::{io, process::Command};
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use paths::{AbsPathBuf, Utf8Path, Utf8PathBuf};
|
||||
use project_model::ProjectJsonData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::command::{CommandHandle, ParseFromLine};
|
||||
|
||||
pub const ARG_PLACEHOLDER: &str = "{arg}";
|
||||
|
||||
/// A command wrapper for getting a `rust-project.json`.
|
||||
///
|
||||
/// This is analogous to `cargo-metadata`, but for non-Cargo build systems.
|
||||
pub struct Discover {
|
||||
command: Vec<String>,
|
||||
sender: Sender<DiscoverProjectMessage>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum DiscoverArgument {
|
||||
Path(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
|
||||
Buildfile(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
|
||||
}
|
||||
|
||||
fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let path: &Utf8Path = path.as_ref();
|
||||
se.serialize_str(path.as_str())
|
||||
}
|
||||
|
||||
impl Discover {
|
||||
/// Create a new [Discover].
|
||||
pub fn new(sender: Sender<DiscoverProjectMessage>, command: Vec<String>) -> Self {
|
||||
Self { sender, command }
|
||||
}
|
||||
|
||||
/// Spawn the command inside [Discover] and report progress, if any.
|
||||
pub fn spawn(&self, discover_arg: DiscoverArgument) -> io::Result<DiscoverHandle> {
|
||||
let command = &self.command[0];
|
||||
let args = &self.command[1..];
|
||||
|
||||
let args: Vec<String> = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
if arg == ARG_PLACEHOLDER {
|
||||
serde_json::to_string(&discover_arg).expect("Unable to serialize args")
|
||||
} else {
|
||||
arg.to_owned()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut cmd = Command::new(command);
|
||||
cmd.args(args);
|
||||
|
||||
Ok(DiscoverHandle { _handle: CommandHandle::spawn(cmd, self.sender.clone())? })
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a spawned [Discover].
|
||||
#[derive(Debug)]
|
||||
pub struct DiscoverHandle {
|
||||
_handle: CommandHandle<DiscoverProjectMessage>,
|
||||
}
|
||||
|
||||
/// An enum containing either progress messages, an error,
|
||||
/// or the materialized `rust-project`.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(tag = "kind")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum DiscoverProjectData {
|
||||
Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
|
||||
Error { error: String, source: Option<String> },
|
||||
Progress { message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum DiscoverProjectMessage {
|
||||
Finished { project: ProjectJsonData, buildfile: AbsPathBuf },
|
||||
Error { error: String, source: Option<String> },
|
||||
Progress { message: String },
|
||||
}
|
||||
|
||||
impl DiscoverProjectMessage {
|
||||
fn new(data: DiscoverProjectData) -> Self {
|
||||
match data {
|
||||
DiscoverProjectData::Finished { project, buildfile, .. } => {
|
||||
let buildfile = buildfile.try_into().expect("Unable to make path absolute");
|
||||
DiscoverProjectMessage::Finished { project, buildfile }
|
||||
}
|
||||
DiscoverProjectData::Error { error, source } => {
|
||||
DiscoverProjectMessage::Error { error, source }
|
||||
}
|
||||
DiscoverProjectData::Progress { message } => {
|
||||
DiscoverProjectMessage::Progress { message }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseFromLine for DiscoverProjectMessage {
|
||||
fn from_line(line: &str, _error: &mut String) -> Option<Self> {
|
||||
// can the line even be deserialized as JSON?
|
||||
let Ok(data) = serde_json::from_str::<Value>(line) else {
|
||||
let err = DiscoverProjectData::Error { error: line.to_owned(), source: None };
|
||||
return Some(DiscoverProjectMessage::new(err));
|
||||
};
|
||||
|
||||
let Ok(data) = serde_json::from_value::<DiscoverProjectData>(data) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let msg = DiscoverProjectMessage::new(data);
|
||||
Some(msg)
|
||||
}
|
||||
|
||||
fn from_eof() -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialization() {
|
||||
let message = r#"
|
||||
{"kind": "progress", "message":"querying build system","input":{"files":["src/main.rs"]}}
|
||||
"#;
|
||||
let message: DiscoverProjectData =
|
||||
serde_json::from_str(message).expect("Unable to deserialize message");
|
||||
assert!(matches!(message, DiscoverProjectData::Progress { .. }));
|
||||
|
||||
let message = r#"
|
||||
{"kind": "error", "error":"failed to deserialize command output","source":"command"}
|
||||
"#;
|
||||
|
||||
let message: DiscoverProjectData =
|
||||
serde_json::from_str(message).expect("Unable to deserialize message");
|
||||
assert!(matches!(message, DiscoverProjectData::Error { .. }));
|
||||
|
||||
let message = r#"
|
||||
{"kind": "finished", "project": {"sysroot": "foo", "crates": [], "runnables": []}, "buildfile":"rust-analyzer/BUILD"}
|
||||
"#;
|
||||
|
||||
let message: DiscoverProjectData =
|
||||
serde_json::from_str(message).expect("Unable to deserialize message");
|
||||
assert!(matches!(message, DiscoverProjectData::Finished { .. }));
|
||||
}
|
|
@ -17,7 +17,6 @@ use itertools::Itertools;
|
|||
use proc_macro_api::{MacroDylib, ProcMacroServer};
|
||||
use project_model::{CargoConfig, ManifestPath, PackageRoot, ProjectManifest, ProjectWorkspace};
|
||||
use span::Span;
|
||||
use tracing::instrument;
|
||||
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
|
||||
|
||||
pub struct LoadCargoConfig {
|
||||
|
@ -51,7 +50,6 @@ pub fn load_workspace_at(
|
|||
load_workspace(workspace, &cargo_config.extra_env, load_config)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn load_workspace(
|
||||
ws: ProjectWorkspace,
|
||||
extra_env: &FxHashMap<String, String>,
|
||||
|
|
|
@ -276,6 +276,16 @@ impl ProjectJson {
|
|||
self.manifest.as_ref()
|
||||
}
|
||||
|
||||
pub fn crate_by_buildfile(&self, path: &AbsPath) -> Option<Build> {
|
||||
// this is fast enough for now, but it's unfortunate that this is O(crates).
|
||||
let path: &std::path::Path = path.as_ref();
|
||||
self.crates
|
||||
.iter()
|
||||
.filter(|krate| krate.is_workspace_member)
|
||||
.filter_map(|krate| krate.build.clone())
|
||||
.find(|build| build.build_file.as_std_path() == path)
|
||||
}
|
||||
|
||||
/// Returns the path to the project's manifest or root folder, if no manifest exists.
|
||||
pub fn manifest_or_root(&self) -> &AbsPath {
|
||||
self.manifest.as_ref().map_or(&self.project_root, |manifest| manifest.as_ref())
|
||||
|
@ -286,7 +296,7 @@ impl ProjectJson {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ProjectJsonData {
|
||||
sysroot: Option<Utf8PathBuf>,
|
||||
sysroot_src: Option<Utf8PathBuf>,
|
||||
|
@ -295,7 +305,7 @@ pub struct ProjectJsonData {
|
|||
runnables: Vec<RunnableData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
struct CrateData {
|
||||
display_name: Option<String>,
|
||||
root_module: Utf8PathBuf,
|
||||
|
@ -319,7 +329,7 @@ struct CrateData {
|
|||
build: Option<BuildData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
#[serde(rename = "edition")]
|
||||
enum EditionData {
|
||||
#[serde(rename = "2015")]
|
||||
|
@ -332,7 +342,7 @@ enum EditionData {
|
|||
Edition2024,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct BuildData {
|
||||
label: String,
|
||||
build_file: Utf8PathBuf,
|
||||
|
@ -419,7 +429,7 @@ pub(crate) struct Dep {
|
|||
pub(crate) name: CrateName,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
struct CrateSource {
|
||||
include_dirs: Vec<Utf8PathBuf>,
|
||||
exclude_dirs: Vec<Utf8PathBuf>,
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::{
|
|||
utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
|
||||
ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
|
||||
};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>;
|
||||
|
||||
|
@ -250,7 +251,7 @@ impl ProjectWorkspace {
|
|||
};
|
||||
|
||||
let rustc = rustc_dir.and_then(|rustc_dir| {
|
||||
tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
|
||||
info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
|
||||
match CargoWorkspace::fetch_metadata(
|
||||
&rustc_dir,
|
||||
cargo_toml.parent(),
|
||||
|
@ -767,9 +768,9 @@ impl ProjectWorkspace {
|
|||
};
|
||||
|
||||
if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() {
|
||||
tracing::debug!("Patched std to depend on cfg-if")
|
||||
debug!("Patched std to depend on cfg-if")
|
||||
} else {
|
||||
tracing::debug!("Did not patch std to depend on cfg-if")
|
||||
debug!("Did not patch std to depend on cfg-if")
|
||||
}
|
||||
(crate_graph, proc_macros)
|
||||
}
|
||||
|
@ -917,6 +918,11 @@ fn project_json_to_crate_graph(
|
|||
CrateOrigin::Local { repo: None, name: None }
|
||||
},
|
||||
);
|
||||
debug!(
|
||||
?crate_graph_crate_id,
|
||||
crate = display_name.as_ref().map(|name| name.canonical_name().as_str()),
|
||||
"added root to crate graph"
|
||||
);
|
||||
if *is_proc_macro {
|
||||
if let Some(path) = proc_macro_dylib_path.clone() {
|
||||
let node = Ok((
|
||||
|
@ -931,6 +937,7 @@ fn project_json_to_crate_graph(
|
|||
)
|
||||
.collect();
|
||||
|
||||
debug!(map = ?idx_to_crate_id);
|
||||
for (from_idx, krate) in project.crates() {
|
||||
if let Some(&from) = idx_to_crate_id.get(&from_idx) {
|
||||
public_deps.add_to_crate_graph(crate_graph, from);
|
||||
|
@ -1156,7 +1163,7 @@ fn detached_file_to_crate_graph(
|
|||
let file_id = match load(detached_file) {
|
||||
Some(file_id) => file_id,
|
||||
None => {
|
||||
tracing::error!("Failed to load detached file {:?}", detached_file);
|
||||
error!("Failed to load detached file {:?}", detached_file);
|
||||
return (crate_graph, FxHashMap::default());
|
||||
}
|
||||
};
|
||||
|
@ -1351,7 +1358,7 @@ fn add_target_crate_root(
|
|||
crate_id
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
struct SysrootPublicDeps {
|
||||
deps: Vec<(CrateName, CrateId, bool)>,
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ fn run_server() -> anyhow::Result<()> {
|
|||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("InitializeParams: {}", initialize_params);
|
||||
let lsp_types::InitializeParams {
|
||||
root_uri,
|
||||
|
@ -264,7 +265,10 @@ fn run_server() -> anyhow::Result<()> {
|
|||
return Err(e.into());
|
||||
}
|
||||
|
||||
if !config.has_linked_projects() && config.detached_files().is_empty() {
|
||||
if config.discover_workspace_config().is_none()
|
||||
&& !config.has_linked_projects()
|
||||
&& config.detached_files().is_empty()
|
||||
{
|
||||
config.rediscover_workspaces();
|
||||
}
|
||||
|
||||
|
|
|
@ -328,6 +328,102 @@ config_data! {
|
|||
/// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
|
||||
/// available on a nightly build.
|
||||
rustfmt_rangeFormatting_enable: bool = false,
|
||||
|
||||
/// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
|
||||
///
|
||||
/// [`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.
|
||||
/// `progress_label` is used for the title in progress indicators, whereas `files_to_watch`
|
||||
/// is used to determine which build system-specific files should be watched in order to
|
||||
/// reload rust-analyzer.
|
||||
///
|
||||
/// Below is an example of a valid configuration:
|
||||
/// ```json
|
||||
/// "rust-analyzer.workspace.discoverConfig": {
|
||||
/// "command": [
|
||||
/// "rust-project",
|
||||
/// "develop-json",
|
||||
/// {arg}
|
||||
/// ],
|
||||
/// "progressLabel": "rust-analyzer",
|
||||
/// "filesToWatch": [
|
||||
/// "BUCK",
|
||||
/// ],
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## On `DiscoverWorkspaceConfig::command`
|
||||
///
|
||||
/// **Warning**: This format is provisional and subject to change.
|
||||
///
|
||||
/// [`DiscoverWorkspaceConfig::command`] *must* return a JSON object
|
||||
/// corresponding to `DiscoverProjectData::Finished`:
|
||||
///
|
||||
/// ```norun
|
||||
/// #[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
/// #[serde(tag = "kind")]
|
||||
/// #[serde(rename_all = "snake_case")]
|
||||
/// enum DiscoverProjectData {
|
||||
/// Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
|
||||
/// Error { error: String, source: Option<String> },
|
||||
/// Progress { message: String },
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// As JSON, `DiscoverProjectData::Finished` is:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// // the internally-tagged representation of the enum.
|
||||
/// "kind": "finished",
|
||||
/// // the file used by a non-Cargo build system to define
|
||||
/// // a package or target.
|
||||
/// "buildfile": "rust-analyzer/BUILD",
|
||||
/// // the contents of a rust-project.json, elided for brevity
|
||||
/// "project": {
|
||||
/// "sysroot": "foo",
|
||||
/// "crates": []
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is encouraged, but not required, to use the other variants on
|
||||
/// `DiscoverProjectData` to provide a more polished end-user experience.
|
||||
///
|
||||
/// `DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,
|
||||
/// which will be substituted with the JSON-serialized form of the following
|
||||
/// enum:
|
||||
///
|
||||
/// ```norun
|
||||
/// #[derive(PartialEq, Clone, Debug, Serialize)]
|
||||
/// #[serde(rename_all = "camelCase")]
|
||||
/// pub enum DiscoverArgument {
|
||||
/// Path(AbsPathBuf),
|
||||
/// Buildfile(AbsPathBuf),
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The JSON representation of `DiscoverArgument::Path` is:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "path": "src/main.rs"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Similarly, the JSON representation of `DiscoverArgument::Buildfile` is:
|
||||
///
|
||||
/// ```
|
||||
/// {
|
||||
/// "buildfile": "BUILD"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
|
||||
/// and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
|
||||
/// to update an existing workspace. As a reference for implementors,
|
||||
/// buck2's `rust-project` will likely be useful:
|
||||
/// https://github.com/facebook/buck2/tree/main/integrations/rust-project.
|
||||
workspace_discoverConfig: Option<DiscoverWorkspaceConfig> = None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -924,6 +1020,21 @@ impl Config {
|
|||
);
|
||||
(config, e, should_update)
|
||||
}
|
||||
|
||||
pub fn add_linked_projects(&mut self, data: ProjectJsonData, buildfile: AbsPathBuf) {
|
||||
let linked_projects = &mut self.client_config.0.global.linkedProjects;
|
||||
|
||||
let new_project = ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile };
|
||||
match linked_projects {
|
||||
Some(projects) => {
|
||||
match projects.iter_mut().find(|p| p.manifest() == new_project.manifest()) {
|
||||
Some(p) => *p = new_project,
|
||||
None => projects.push(new_project),
|
||||
}
|
||||
}
|
||||
None => *linked_projects = Some(vec![new_project]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -988,6 +1099,14 @@ impl From<ProjectJson> for LinkedProject {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiscoverWorkspaceConfig {
|
||||
pub command: Vec<String>,
|
||||
pub progress_label: String,
|
||||
pub files_to_watch: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct CallInfoConfig {
|
||||
pub params_only: bool,
|
||||
pub docs: bool,
|
||||
|
@ -1528,15 +1647,27 @@ impl Config {
|
|||
pub fn has_linked_projects(&self) -> bool {
|
||||
!self.linkedProjects().is_empty()
|
||||
}
|
||||
pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
|
||||
|
||||
pub fn linked_manifests(&self) -> impl Iterator<Item = &AbsPath> + '_ {
|
||||
self.linkedProjects().iter().filter_map(|it| match it {
|
||||
ManifestOrProjectJson::Manifest(p) => Some(&**p),
|
||||
ManifestOrProjectJson::ProjectJson(_) => None,
|
||||
// despite having a buildfile, using this variant as a manifest
|
||||
// will fail.
|
||||
ManifestOrProjectJson::DiscoveredProjectJson { .. } => None,
|
||||
ManifestOrProjectJson::ProjectJson { .. } => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_linked_project_jsons(&self) -> bool {
|
||||
self.linkedProjects().iter().any(|it| matches!(it, ManifestOrProjectJson::ProjectJson(_)))
|
||||
self.linkedProjects()
|
||||
.iter()
|
||||
.any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
|
||||
}
|
||||
|
||||
pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
|
||||
self.workspace_discoverConfig().as_ref()
|
||||
}
|
||||
|
||||
pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
|
||||
match self.linkedProjects().as_slice() {
|
||||
[] => {
|
||||
|
@ -1561,6 +1692,12 @@ impl Config {
|
|||
.ok()
|
||||
.map(Into::into)
|
||||
}
|
||||
ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
|
||||
let root_path =
|
||||
buildfile.parent().expect("Unable to get parent of buildfile");
|
||||
|
||||
Some(ProjectJson::new(None, root_path, data.clone()).into())
|
||||
}
|
||||
ManifestOrProjectJson::ProjectJson(it) => {
|
||||
Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
|
||||
}
|
||||
|
@ -2101,11 +2238,49 @@ mod single_or_array {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
enum ManifestOrProjectJson {
|
||||
Manifest(Utf8PathBuf),
|
||||
Manifest(
|
||||
#[serde(serialize_with = "serialize_abs_pathbuf")]
|
||||
#[serde(deserialize_with = "deserialize_abs_pathbuf")]
|
||||
AbsPathBuf,
|
||||
),
|
||||
ProjectJson(ProjectJsonData),
|
||||
DiscoveredProjectJson {
|
||||
data: ProjectJsonData,
|
||||
#[serde(serialize_with = "serialize_abs_pathbuf")]
|
||||
#[serde(deserialize_with = "deserialize_abs_pathbuf")]
|
||||
buildfile: AbsPathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
fn deserialize_abs_pathbuf<'de, D>(de: D) -> std::result::Result<AbsPathBuf, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let path = String::deserialize(de)?;
|
||||
|
||||
AbsPathBuf::try_from(path.as_ref())
|
||||
.map_err(|err| serde::de::Error::custom(format!("invalid path name: {err:?}")))
|
||||
}
|
||||
|
||||
fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let path: &Utf8Path = path.as_ref();
|
||||
se.serialize_str(path.as_str())
|
||||
}
|
||||
|
||||
impl ManifestOrProjectJson {
|
||||
fn manifest(&self) -> Option<&AbsPath> {
|
||||
match self {
|
||||
ManifestOrProjectJson::Manifest(manifest) => Some(manifest),
|
||||
ManifestOrProjectJson::DiscoveredProjectJson { buildfile, .. } => Some(buildfile),
|
||||
ManifestOrProjectJson::ProjectJson(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
@ -3084,6 +3259,29 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
|
|||
},
|
||||
],
|
||||
},
|
||||
"Option<DiscoverWorkspaceConfig>" => set! {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"progressLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"filesToWatch": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
_ => panic!("missing entry for {ty}: {default} (field {field})"),
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
use std::{ops::Not as _, time::Instant};
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use flycheck::FlycheckHandle;
|
||||
use flycheck::{project_json, FlycheckHandle};
|
||||
use hir::ChangeWithProcMacros;
|
||||
use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
|
||||
use ide_db::base_db::{CrateId, ProcMacroPaths, SourceDatabaseExt};
|
||||
|
@ -20,9 +20,9 @@ use parking_lot::{
|
|||
use proc_macro_api::ProcMacroServer;
|
||||
use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use tracing::{span, Level};
|
||||
use tracing::{span, trace, Level};
|
||||
use triomphe::Arc;
|
||||
use vfs::{AnchoredPathBuf, ChangeKind, Vfs};
|
||||
use vfs::{AbsPathBuf, AnchoredPathBuf, ChangeKind, Vfs};
|
||||
|
||||
use crate::{
|
||||
config::{Config, ConfigChange, ConfigErrors},
|
||||
|
@ -41,6 +41,11 @@ use crate::{
|
|||
task_pool::{TaskPool, TaskQueue},
|
||||
};
|
||||
|
||||
pub(crate) struct FetchWorkspaceRequest {
|
||||
pub(crate) path: Option<AbsPathBuf>,
|
||||
pub(crate) force_crate_graph_reload: bool,
|
||||
}
|
||||
|
||||
// Enforces drop order
|
||||
pub(crate) struct Handle<H, C> {
|
||||
pub(crate) handle: H,
|
||||
|
@ -95,6 +100,11 @@ pub(crate) struct GlobalState {
|
|||
pub(crate) test_run_receiver: Receiver<flycheck::CargoTestMessage>,
|
||||
pub(crate) test_run_remaining_jobs: usize,
|
||||
|
||||
// Project loading
|
||||
pub(crate) discover_handle: Option<project_json::DiscoverHandle>,
|
||||
pub(crate) discover_sender: Sender<project_json::DiscoverProjectMessage>,
|
||||
pub(crate) discover_receiver: Receiver<project_json::DiscoverProjectMessage>,
|
||||
|
||||
// VFS
|
||||
pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
|
||||
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, IntMap<FileId, LineEndings>)>>,
|
||||
|
@ -134,11 +144,12 @@ pub(crate) struct GlobalState {
|
|||
|
||||
// op queues
|
||||
pub(crate) fetch_workspaces_queue:
|
||||
OpQueue<bool, Option<(Vec<anyhow::Result<ProjectWorkspace>>, bool)>>,
|
||||
OpQueue<FetchWorkspaceRequest, Option<(Vec<anyhow::Result<ProjectWorkspace>>, bool)>>,
|
||||
pub(crate) fetch_build_data_queue:
|
||||
OpQueue<(), (Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>,
|
||||
pub(crate) fetch_proc_macros_queue: OpQueue<Vec<ProcMacroPaths>, bool>,
|
||||
pub(crate) prime_caches_queue: OpQueue,
|
||||
pub(crate) discover_workspace_queue: OpQueue,
|
||||
|
||||
/// A deferred task queue.
|
||||
///
|
||||
|
@ -146,7 +157,7 @@ pub(crate) struct GlobalState {
|
|||
/// handlers, as accessing the database may block latency-sensitive
|
||||
/// interactions and should be moved away from the main thread.
|
||||
///
|
||||
/// For certain features, such as [`lsp_ext::UnindexedProjectParams`],
|
||||
/// For certain features, such as [`GlobalState::handle_discover_msg`],
|
||||
/// this queue should run only *after* [`GlobalState::process_changes`] has
|
||||
/// been called.
|
||||
pub(crate) deferred_task_queue: TaskQueue,
|
||||
|
@ -202,6 +213,9 @@ impl GlobalState {
|
|||
}
|
||||
let (flycheck_sender, flycheck_receiver) = unbounded();
|
||||
let (test_run_sender, test_run_receiver) = unbounded();
|
||||
|
||||
let (discover_sender, discover_receiver) = unbounded();
|
||||
|
||||
let mut this = GlobalState {
|
||||
sender,
|
||||
req_queue: ReqQueue::default(),
|
||||
|
@ -233,6 +247,10 @@ impl GlobalState {
|
|||
test_run_receiver,
|
||||
test_run_remaining_jobs: 0,
|
||||
|
||||
discover_handle: None,
|
||||
discover_sender,
|
||||
discover_receiver,
|
||||
|
||||
vfs: Arc::new(RwLock::new((vfs::Vfs::default(), IntMap::default()))),
|
||||
vfs_config_version: 0,
|
||||
vfs_progress_config_version: 0,
|
||||
|
@ -247,6 +265,7 @@ impl GlobalState {
|
|||
fetch_proc_macros_queue: OpQueue::default(),
|
||||
|
||||
prime_caches_queue: OpQueue::default(),
|
||||
discover_workspace_queue: OpQueue::default(),
|
||||
|
||||
deferred_task_queue: task_queue,
|
||||
};
|
||||
|
@ -296,11 +315,24 @@ impl GlobalState {
|
|||
modified_rust_files.push(file.file_id);
|
||||
}
|
||||
|
||||
let additional_files = self
|
||||
.config
|
||||
.discover_workspace_config()
|
||||
.map(|cfg| {
|
||||
cfg.files_to_watch.iter().map(String::as_str).collect::<Vec<&str>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let path = path.to_path_buf();
|
||||
if file.is_created_or_deleted() {
|
||||
workspace_structure_change.get_or_insert((path, false)).1 |=
|
||||
self.crate_graph_file_dependencies.contains(vfs_path);
|
||||
} else if reload::should_refresh_for_change(&path, file.kind()) {
|
||||
} else if reload::should_refresh_for_change(
|
||||
&path,
|
||||
file.kind(),
|
||||
&additional_files,
|
||||
) {
|
||||
trace!(?path, kind = ?file.kind(), "refreshing for a change");
|
||||
workspace_structure_change.get_or_insert((path.clone(), false));
|
||||
}
|
||||
}
|
||||
|
@ -419,7 +451,7 @@ impl GlobalState {
|
|||
|
||||
self.fetch_workspaces_queue.request_op(
|
||||
format!("workspace vfs file change: {path}"),
|
||||
force_crate_graph_reload,
|
||||
FetchWorkspaceRequest { path: Some(path.to_owned()), force_crate_graph_reload },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath};
|
|||
|
||||
use crate::{
|
||||
config::{Config, ConfigChange},
|
||||
global_state::GlobalState,
|
||||
global_state::{FetchWorkspaceRequest, GlobalState},
|
||||
lsp::{from_proto, utils::apply_document_changes},
|
||||
lsp_ext::{self, RunFlycheckParams},
|
||||
mem_docs::DocumentData,
|
||||
|
@ -73,7 +73,9 @@ pub(crate) fn handle_did_open_text_document(
|
|||
|
||||
tracing::info!("New file content set {:?}", params.text_document.text);
|
||||
state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));
|
||||
if state.config.notifications().unindexed_project {
|
||||
if state.config.discover_workspace_config().is_some()
|
||||
|| state.config.notifications().unindexed_project
|
||||
{
|
||||
tracing::debug!("queuing task");
|
||||
let _ = state
|
||||
.deferred_task_queue
|
||||
|
@ -150,15 +152,29 @@ pub(crate) fn handle_did_save_text_document(
|
|||
|
||||
if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||
// Re-fetch workspaces if a workspace related file has changed
|
||||
if let Some(abs_path) = vfs_path.as_path() {
|
||||
if reload::should_refresh_for_change(abs_path, ChangeKind::Modify) {
|
||||
state
|
||||
.fetch_workspaces_queue
|
||||
.request_op(format!("workspace vfs file change saved {abs_path}"), false);
|
||||
} else if state.detached_files.contains(abs_path) {
|
||||
state
|
||||
.fetch_workspaces_queue
|
||||
.request_op(format!("detached file saved {abs_path}"), false);
|
||||
if let Some(path) = vfs_path.as_path() {
|
||||
let additional_files = &state
|
||||
.config
|
||||
.discover_workspace_config()
|
||||
.map(|cfg| cfg.files_to_watch.iter().map(String::as_str).collect::<Vec<&str>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
if reload::should_refresh_for_change(path, ChangeKind::Modify, additional_files) {
|
||||
state.fetch_workspaces_queue.request_op(
|
||||
format!("workspace vfs file change saved {path}"),
|
||||
FetchWorkspaceRequest {
|
||||
path: Some(path.to_owned()),
|
||||
force_crate_graph_reload: false,
|
||||
},
|
||||
);
|
||||
} else if state.detached_files.contains(path) {
|
||||
state.fetch_workspaces_queue.request_op(
|
||||
format!("detached file saved {path}"),
|
||||
FetchWorkspaceRequest {
|
||||
path: Some(path.to_owned()),
|
||||
force_crate_graph_reload: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +256,9 @@ pub(crate) fn handle_did_change_workspace_folders(
|
|||
|
||||
if !config.has_linked_projects() && config.detached_files().is_empty() {
|
||||
config.rediscover_workspaces();
|
||||
state.fetch_workspaces_queue.request_op("client workspaces changed".to_owned(), false)
|
||||
|
||||
let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
|
||||
state.fetch_workspaces_queue.request_op("client workspaces changed".to_owned(), req);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -37,7 +37,7 @@ use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
|
|||
use crate::{
|
||||
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
|
||||
diff::diff,
|
||||
global_state::{GlobalState, GlobalStateSnapshot},
|
||||
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
|
||||
hack_recover_crate_name,
|
||||
line_index::LineEndings,
|
||||
lsp::{
|
||||
|
@ -57,7 +57,8 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow:
|
|||
state.proc_macro_clients = Arc::from_iter([]);
|
||||
state.build_deps_changed = false;
|
||||
|
||||
state.fetch_workspaces_queue.request_op("reload workspace request".to_owned(), false);
|
||||
let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
|
||||
state.fetch_workspaces_queue.request_op("reload workspace request".to_owned(), req);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -531,7 +531,7 @@ pub struct ServerStatusParams {
|
|||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Health {
|
||||
Ok,
|
||||
|
|
|
@ -74,7 +74,6 @@ impl GlobalState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sends a notification to the client containing the error `message`.
|
||||
/// If `additional_info` is [`Some`], appends a note to the notification telling to check the logs.
|
||||
/// This will always log `message` + `additional_info` to the server's error log.
|
||||
pub(crate) fn show_and_log_error(&mut self, message: String, additional_info: Option<String>) {
|
||||
|
|
|
@ -9,18 +9,19 @@ use std::{
|
|||
|
||||
use always_assert::always;
|
||||
use crossbeam_channel::{select, Receiver};
|
||||
use flycheck::project_json;
|
||||
use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
|
||||
use lsp_server::{Connection, Notification, Request};
|
||||
use lsp_types::{notification::Notification as _, TextDocumentIdentifier};
|
||||
use stdx::thread::ThreadIntent;
|
||||
use tracing::{span, Level};
|
||||
use vfs::FileId;
|
||||
use vfs::{AbsPathBuf, FileId};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration},
|
||||
dispatch::{NotificationDispatcher, RequestDispatcher},
|
||||
global_state::{file_id_to_url, url_to_file_id, GlobalState},
|
||||
global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState},
|
||||
hack_recover_crate_name,
|
||||
lsp::{
|
||||
from_proto, to_proto,
|
||||
|
@ -62,6 +63,7 @@ enum Event {
|
|||
Vfs(vfs::loader::Message),
|
||||
Flycheck(flycheck::Message),
|
||||
TestResult(flycheck::CargoTestMessage),
|
||||
DiscoverProject(project_json::DiscoverProjectMessage),
|
||||
}
|
||||
|
||||
impl fmt::Display for Event {
|
||||
|
@ -73,6 +75,7 @@ impl fmt::Display for Event {
|
|||
Event::Flycheck(_) => write!(f, "Event::Flycheck"),
|
||||
Event::QueuedTask(_) => write!(f, "Event::QueuedTask"),
|
||||
Event::TestResult(_) => write!(f, "Event::TestResult"),
|
||||
Event::DiscoverProject(_) => write!(f, "Event::DiscoverProject"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +89,7 @@ pub(crate) enum QueuedTask {
|
|||
#[derive(Debug)]
|
||||
pub(crate) enum Task {
|
||||
Response(lsp_server::Response),
|
||||
DiscoverLinkedProjects(DiscoverProjectParam),
|
||||
ClientNotification(lsp_ext::UnindexedProjectParams),
|
||||
Retry(lsp_server::Request),
|
||||
Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
|
||||
|
@ -97,6 +101,12 @@ pub(crate) enum Task {
|
|||
BuildDepsHaveChanged,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum DiscoverProjectParam {
|
||||
Buildfile(AbsPathBuf),
|
||||
Path(AbsPathBuf),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PrimeCachesProgress {
|
||||
Begin,
|
||||
|
@ -134,6 +144,7 @@ impl fmt::Debug for Event {
|
|||
Event::Vfs(it) => fmt::Debug::fmt(it, f),
|
||||
Event::Flycheck(it) => fmt::Debug::fmt(it, f),
|
||||
Event::TestResult(it) => fmt::Debug::fmt(it, f),
|
||||
Event::DiscoverProject(it) => fmt::Debug::fmt(it, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,14 +154,24 @@ impl GlobalState {
|
|||
self.update_status_or_notify();
|
||||
|
||||
if self.config.did_save_text_document_dynamic_registration() {
|
||||
self.register_did_save_capability();
|
||||
let additional_patterns = self
|
||||
.config
|
||||
.discover_workspace_config()
|
||||
.map(|cfg| cfg.files_to_watch.clone().into_iter())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|f| format!("**/{f}"));
|
||||
self.register_did_save_capability(additional_patterns);
|
||||
}
|
||||
|
||||
self.fetch_workspaces_queue.request_op("startup".to_owned(), false);
|
||||
if let Some((cause, force_crate_graph_reload)) =
|
||||
self.fetch_workspaces_queue.should_start_op()
|
||||
{
|
||||
self.fetch_workspaces(cause, force_crate_graph_reload);
|
||||
if self.config.discover_workspace_config().is_none() {
|
||||
let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
|
||||
self.fetch_workspaces_queue.request_op("startup".to_owned(), req);
|
||||
if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
|
||||
self.fetch_workspaces_queue.should_start_op()
|
||||
{
|
||||
self.fetch_workspaces(cause, path, force_crate_graph_reload);
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(event) = self.next_event(&inbox) {
|
||||
|
@ -167,32 +188,36 @@ impl GlobalState {
|
|||
anyhow::bail!("client exited without proper shutdown sequence")
|
||||
}
|
||||
|
||||
fn register_did_save_capability(&mut self) {
|
||||
fn register_did_save_capability(&mut self, additional_patterns: impl Iterator<Item = String>) {
|
||||
let additional_filters = additional_patterns.map(|pattern| lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: (Some(pattern)),
|
||||
});
|
||||
|
||||
let mut selectors = vec![
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/*.rs".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/Cargo.toml".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/Cargo.lock".into()),
|
||||
},
|
||||
];
|
||||
selectors.extend(additional_filters);
|
||||
|
||||
let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions {
|
||||
include_text: Some(false),
|
||||
text_document_registration_options: lsp_types::TextDocumentRegistrationOptions {
|
||||
document_selector: Some(vec![
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/*.rs".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/Cargo.toml".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/Cargo.lock".into()),
|
||||
},
|
||||
lsp_types::DocumentFilter {
|
||||
language: None,
|
||||
scheme: None,
|
||||
pattern: Some("**/rust-analyzer.toml".into()),
|
||||
},
|
||||
]),
|
||||
document_selector: Some(selectors),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -230,6 +255,8 @@ impl GlobalState {
|
|||
recv(self.test_run_receiver) -> task =>
|
||||
Some(Event::TestResult(task.unwrap())),
|
||||
|
||||
recv(self.discover_receiver) -> task =>
|
||||
Some(Event::DiscoverProject(task.unwrap())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +367,13 @@ impl GlobalState {
|
|||
self.handle_cargo_test_msg(message);
|
||||
}
|
||||
}
|
||||
Event::DiscoverProject(message) => {
|
||||
self.handle_discover_msg(message);
|
||||
// Coalesce many project discovery events into a single loop turn.
|
||||
while let Ok(message) = self.discover_receiver.try_recv() {
|
||||
self.handle_discover_msg(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
let event_handling_duration = loop_start.elapsed();
|
||||
|
||||
|
@ -427,11 +461,13 @@ impl GlobalState {
|
|||
}
|
||||
}
|
||||
|
||||
if self.config.cargo_autoreload_config() {
|
||||
if let Some((cause, force_crate_graph_reload)) =
|
||||
if self.config.cargo_autoreload_config()
|
||||
|| self.config.discover_workspace_config().is_some()
|
||||
{
|
||||
if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
|
||||
self.fetch_workspaces_queue.should_start_op()
|
||||
{
|
||||
self.fetch_workspaces(cause, force_crate_graph_reload);
|
||||
self.fetch_workspaces(cause, path, force_crate_graph_reload);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,6 +683,35 @@ impl GlobalState {
|
|||
|
||||
self.report_progress("Fetching", state, msg, None, None);
|
||||
}
|
||||
Task::DiscoverLinkedProjects(arg) => {
|
||||
if let Some(cfg) = self.config.discover_workspace_config() {
|
||||
if !self.discover_workspace_queue.op_in_progress() {
|
||||
// the clone is unfortunately necessary to avoid a borrowck error when
|
||||
// `self.report_progress` is called later
|
||||
let title = &cfg.progress_label.clone();
|
||||
let command = cfg.command.clone();
|
||||
let discover =
|
||||
project_json::Discover::new(self.discover_sender.clone(), command);
|
||||
|
||||
self.report_progress(title, Progress::Begin, None, None, None);
|
||||
self.discover_workspace_queue
|
||||
.request_op("Discovering workspace".to_owned(), ());
|
||||
let _ = self.discover_workspace_queue.should_start_op();
|
||||
|
||||
let arg = match arg {
|
||||
DiscoverProjectParam::Buildfile(it) => {
|
||||
project_json::DiscoverArgument::Buildfile(it)
|
||||
}
|
||||
DiscoverProjectParam::Path(it) => {
|
||||
project_json::DiscoverArgument::Path(it)
|
||||
}
|
||||
};
|
||||
|
||||
let handle = discover.spawn(arg).unwrap();
|
||||
self.discover_handle = Some(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::FetchBuildData(progress) => {
|
||||
let (state, msg) = match progress {
|
||||
BuildDataProgress::Begin => (Some(Progress::Begin), None),
|
||||
|
@ -755,10 +820,17 @@ impl GlobalState {
|
|||
let id = from_proto::file_id(&snap, &uri).expect("unable to get FileId");
|
||||
if let Ok(crates) = &snap.analysis.crates_for(id) {
|
||||
if crates.is_empty() {
|
||||
let params = lsp_ext::UnindexedProjectParams {
|
||||
text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
|
||||
if snap.config.discover_workspace_config().is_some() {
|
||||
let path =
|
||||
from_proto::abs_path(&uri).expect("Unable to get AbsPath");
|
||||
let arg = DiscoverProjectParam::Path(path);
|
||||
sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
|
||||
} else if snap.config.notifications().unindexed_project {
|
||||
let params = lsp_ext::UnindexedProjectParams {
|
||||
text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
|
||||
};
|
||||
sender.send(Task::ClientNotification(params)).unwrap();
|
||||
};
|
||||
sender.send(Task::ClientNotification(params)).unwrap();
|
||||
} else {
|
||||
tracing::debug!(?uri, "is indexed");
|
||||
}
|
||||
|
@ -787,6 +859,33 @@ impl GlobalState {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_discover_msg(&mut self, message: project_json::DiscoverProjectMessage) {
|
||||
let title = self
|
||||
.config
|
||||
.discover_workspace_config()
|
||||
.map(|cfg| cfg.progress_label.clone())
|
||||
.expect("No title could be found; this is a bug");
|
||||
match message {
|
||||
project_json::DiscoverProjectMessage::Finished { project, buildfile } => {
|
||||
self.report_progress(&title, Progress::End, None, None, None);
|
||||
self.discover_workspace_queue.op_completed(());
|
||||
|
||||
let mut config = Config::clone(&*self.config);
|
||||
config.add_linked_projects(project, buildfile);
|
||||
self.update_configuration(config);
|
||||
}
|
||||
project_json::DiscoverProjectMessage::Progress { message } => {
|
||||
self.report_progress(&title, Progress::Report, Some(message), None, None)
|
||||
}
|
||||
project_json::DiscoverProjectMessage::Error { error, source } => {
|
||||
let message = format!("Project discovery failed: {error}");
|
||||
self.discover_workspace_queue.op_completed(());
|
||||
self.show_and_log_error(message.clone(), source);
|
||||
self.report_progress(&title, Progress::End, Some(message), None, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_cargo_test_msg(&mut self, message: flycheck::CargoTestMessage) {
|
||||
match message {
|
||||
flycheck::CargoTestMessage::Test { name, state } => {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
pub(crate) type Cause = String;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OpQueue<Args = (), Output = ()> {
|
||||
op_requested: Option<(Cause, Args)>,
|
||||
op_in_progress: bool,
|
||||
|
|
|
@ -33,11 +33,12 @@ use vfs::{AbsPath, AbsPathBuf, ChangeKind};
|
|||
|
||||
use crate::{
|
||||
config::{Config, FilesWatcher, LinkedProject},
|
||||
global_state::GlobalState,
|
||||
global_state::{FetchWorkspaceRequest, GlobalState},
|
||||
lsp_ext,
|
||||
main_loop::Task,
|
||||
main_loop::{DiscoverProjectParam, Task},
|
||||
op_queue::Cause,
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ProjectWorkspaceProgress {
|
||||
|
@ -66,6 +67,7 @@ impl GlobalState {
|
|||
|| self.fetch_workspaces_queue.op_in_progress()
|
||||
|| self.fetch_build_data_queue.op_in_progress()
|
||||
|| self.fetch_proc_macros_queue.op_in_progress()
|
||||
|| self.discover_workspace_queue.op_in_progress()
|
||||
|| self.vfs_progress_config_version < self.vfs_config_version
|
||||
|| self.vfs_progress_n_done < self.vfs_progress_n_total)
|
||||
}
|
||||
|
@ -81,9 +83,11 @@ impl GlobalState {
|
|||
&self.config.lru_query_capacities_config().cloned().unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects()
|
||||
{
|
||||
self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), false)
|
||||
let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
|
||||
self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), req)
|
||||
} else if self.config.flycheck() != old_config.flycheck() {
|
||||
self.reload_flycheck();
|
||||
}
|
||||
|
@ -109,6 +113,7 @@ impl GlobalState {
|
|||
if !self.config.cargo_autoreload()
|
||||
&& self.is_quiescent()
|
||||
&& self.fetch_workspaces_queue.op_requested()
|
||||
&& self.config.discover_workspace_config().is_none()
|
||||
{
|
||||
status.health |= lsp_ext::Health::Warning;
|
||||
message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n");
|
||||
|
@ -124,7 +129,6 @@ impl GlobalState {
|
|||
status.health |= lsp_ext::Health::Warning;
|
||||
message.push_str("Failed to run build scripts of some packages.\n\n");
|
||||
}
|
||||
|
||||
if let Some(err) = &self.config_errors {
|
||||
status.health |= lsp_ext::Health::Warning;
|
||||
format_to!(message, "{err}\n");
|
||||
|
@ -217,8 +221,13 @@ impl GlobalState {
|
|||
status
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_workspaces(&mut self, cause: Cause, force_crate_graph_reload: bool) {
|
||||
tracing::info!(%cause, "will fetch workspaces");
|
||||
pub(crate) fn fetch_workspaces(
|
||||
&mut self,
|
||||
cause: Cause,
|
||||
path: Option<AbsPathBuf>,
|
||||
force_crate_graph_reload: bool,
|
||||
) {
|
||||
info!(%cause, "will fetch workspaces");
|
||||
|
||||
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
|
||||
let linked_projects = self.config.linked_or_discovered_projects();
|
||||
|
@ -231,6 +240,10 @@ impl GlobalState {
|
|||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
let cargo_config = self.config.cargo();
|
||||
let discover_command = self.config.discover_workspace_config().cloned();
|
||||
let is_quiescent = !(self.discover_workspace_queue.op_in_progress()
|
||||
|| self.vfs_progress_config_version < self.vfs_config_version
|
||||
|| self.vfs_progress_n_done < self.vfs_progress_n_total);
|
||||
|
||||
move |sender| {
|
||||
let progress = {
|
||||
|
@ -244,10 +257,28 @@ impl GlobalState {
|
|||
|
||||
sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
|
||||
|
||||
if let (Some(_command), Some(path)) = (&discover_command, &path) {
|
||||
let build = linked_projects.iter().find_map(|project| match project {
|
||||
LinkedProject::InlineJsonProject(it) => it.crate_by_buildfile(path),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some(build) = build {
|
||||
if is_quiescent {
|
||||
let path = AbsPathBuf::try_from(build.build_file)
|
||||
.expect("Unable to convert to an AbsPath");
|
||||
let arg = DiscoverProjectParam::Buildfile(path);
|
||||
sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut workspaces = linked_projects
|
||||
.iter()
|
||||
.map(|project| match project {
|
||||
LinkedProject::ProjectManifest(manifest) => {
|
||||
debug!(path = %manifest, "loading project from manifest");
|
||||
|
||||
project_model::ProjectWorkspace::load(
|
||||
manifest.clone(),
|
||||
&cargo_config,
|
||||
|
@ -255,12 +286,13 @@ impl GlobalState {
|
|||
)
|
||||
}
|
||||
LinkedProject::InlineJsonProject(it) => {
|
||||
Ok(project_model::ProjectWorkspace::load_inline(
|
||||
let workspace = project_model::ProjectWorkspace::load_inline(
|
||||
it.clone(),
|
||||
cargo_config.target.as_deref(),
|
||||
&cargo_config.extra_env,
|
||||
&cargo_config.cfg_overrides,
|
||||
))
|
||||
);
|
||||
Ok(workspace)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -286,7 +318,7 @@ impl GlobalState {
|
|||
));
|
||||
}
|
||||
|
||||
tracing::info!("did fetch workspaces {:?}", workspaces);
|
||||
info!(?workspaces, "did fetch workspaces");
|
||||
sender
|
||||
.send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(
|
||||
workspaces,
|
||||
|
@ -298,7 +330,7 @@ impl GlobalState {
|
|||
}
|
||||
|
||||
pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
|
||||
tracing::info!(%cause, "will fetch build data");
|
||||
info!(%cause, "will fetch build data");
|
||||
let workspaces = Arc::clone(&self.workspaces);
|
||||
let config = self.config.cargo();
|
||||
let root_path = self.config.root_path().clone();
|
||||
|
@ -324,7 +356,7 @@ impl GlobalState {
|
|||
}
|
||||
|
||||
pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec<ProcMacroPaths>) {
|
||||
tracing::info!(%cause, "will load proc macros");
|
||||
info!(%cause, "will load proc macros");
|
||||
let ignored_proc_macros = self.config.ignored_proc_macros().clone();
|
||||
let proc_macro_clients = self.proc_macro_clients.clone();
|
||||
|
||||
|
@ -395,6 +427,7 @@ impl GlobalState {
|
|||
return;
|
||||
};
|
||||
|
||||
info!(%cause, ?force_reload_crate_graph);
|
||||
if self.fetch_workspace_error().is_err() && !self.workspaces.is_empty() {
|
||||
if *force_reload_crate_graph {
|
||||
self.recreate_crate_graph(cause);
|
||||
|
@ -416,7 +449,7 @@ impl GlobalState {
|
|||
if same_workspaces {
|
||||
let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result();
|
||||
if Arc::ptr_eq(workspaces, &self.workspaces) {
|
||||
tracing::debug!("set build scripts to workspaces");
|
||||
info!("set build scripts to workspaces");
|
||||
|
||||
let workspaces = workspaces
|
||||
.iter()
|
||||
|
@ -428,9 +461,10 @@ impl GlobalState {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
// Workspaces are the same, but we've updated build data.
|
||||
info!("same workspace, but new build data");
|
||||
self.workspaces = Arc::new(workspaces);
|
||||
} else {
|
||||
tracing::info!("build scripts do not match the version of the active workspace");
|
||||
info!("build scripts do not match the version of the active workspace");
|
||||
if *force_reload_crate_graph {
|
||||
self.recreate_crate_graph(cause);
|
||||
}
|
||||
|
@ -440,7 +474,7 @@ impl GlobalState {
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("abandon build scripts for workspaces");
|
||||
info!("abandon build scripts for workspaces");
|
||||
|
||||
// Here, we completely changed the workspace (Cargo.toml edit), so
|
||||
// we don't care about build-script results, they are stale.
|
||||
|
@ -535,7 +569,7 @@ impl GlobalState {
|
|||
if (self.proc_macro_clients.is_empty() || !same_workspaces)
|
||||
&& self.config.expand_proc_macros()
|
||||
{
|
||||
tracing::info!("Spawning proc-macro servers");
|
||||
info!("Spawning proc-macro servers");
|
||||
|
||||
self.proc_macro_clients = Arc::from_iter(self.workspaces.iter().map(|ws| {
|
||||
let path = match self.config.proc_macro_srv() {
|
||||
|
@ -562,7 +596,7 @@ impl GlobalState {
|
|||
|
||||
_ => Default::default(),
|
||||
};
|
||||
tracing::info!("Using proc-macro server at {path}");
|
||||
info!("Using proc-macro server at {path}");
|
||||
|
||||
ProcMacroServer::spawn(&path, &env).map_err(|err| {
|
||||
tracing::error!(
|
||||
|
@ -588,12 +622,14 @@ impl GlobalState {
|
|||
self.source_root_config = project_folders.source_root_config;
|
||||
self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
|
||||
|
||||
info!(?cause, "recreating the crate graph");
|
||||
self.recreate_crate_graph(cause);
|
||||
|
||||
tracing::info!("did switch workspaces");
|
||||
info!("did switch workspaces");
|
||||
}
|
||||
|
||||
fn recreate_crate_graph(&mut self, cause: String) {
|
||||
info!(?cause, "Building Crate Graph");
|
||||
self.report_progress(
|
||||
"Building CrateGraph",
|
||||
crate::lsp::utils::Progress::Begin,
|
||||
|
@ -658,12 +694,19 @@ impl GlobalState {
|
|||
let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else {
|
||||
return Ok(());
|
||||
};
|
||||
if last_op_result.is_empty() {
|
||||
stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
|
||||
} else {
|
||||
for ws in last_op_result {
|
||||
if let Err(err) = ws {
|
||||
stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
|
||||
|
||||
if !self.discover_workspace_queue.op_in_progress() {
|
||||
if last_op_result.is_empty() {
|
||||
stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
|
||||
} else {
|
||||
for ws in last_op_result {
|
||||
if let Err(err) = ws {
|
||||
stdx::format_to!(
|
||||
buf,
|
||||
"rust-analyzer failed to load workspace: {:#}\n",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -818,7 +861,11 @@ pub fn ws_to_crate_graph(
|
|||
(crate_graph, proc_macro_paths, layouts, toolchains)
|
||||
}
|
||||
|
||||
pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
|
||||
pub(crate) fn should_refresh_for_change(
|
||||
path: &AbsPath,
|
||||
change_kind: ChangeKind,
|
||||
additional_paths: &[&str],
|
||||
) -> bool {
|
||||
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
|
||||
const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
|
||||
|
||||
|
@ -830,6 +877,11 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind)
|
|||
if let "Cargo.toml" | "Cargo.lock" = file_name {
|
||||
return true;
|
||||
}
|
||||
|
||||
if additional_paths.contains(&file_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if change_kind == ChangeKind::Modify {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,10 @@ where
|
|||
|
||||
let writer = self.writer;
|
||||
|
||||
let ra_fmt_layer = tracing_subscriber::fmt::layer().with_writer(writer).with_filter(filter);
|
||||
let ra_fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_target(false)
|
||||
.with_writer(writer)
|
||||
.with_filter(filter);
|
||||
|
||||
let mut chalk_layer = None;
|
||||
if let Some(chalk_filter) = self.chalk_filter {
|
||||
|
|
|
@ -27,7 +27,6 @@ use lsp_types::{
|
|||
InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
|
||||
RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
|
||||
use serde_json::json;
|
||||
use stdx::format_to_acc;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp/ext.rs hash: 39b47906286ad9c
|
||||
lsp/ext.rs hash: 278250dba58cd879
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
|
|
@ -1015,6 +1015,104 @@ Show documentation.
|
|||
--
|
||||
Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
|
||||
--
|
||||
[[rust-analyzer.workspace.discoverConfig]]rust-analyzer.workspace.discoverConfig (default: `null`)::
|
||||
+
|
||||
--
|
||||
Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
|
||||
|
||||
[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.
|
||||
`progress_label` is used for the title in progress indicators, whereas `files_to_watch`
|
||||
is used to determine which build system-specific files should be watched in order to
|
||||
reload rust-analyzer.
|
||||
|
||||
Below is an example of a valid configuration:
|
||||
```json
|
||||
"rust-analyzer.workspace.discoverConfig": {
|
||||
"command": [
|
||||
"rust-project",
|
||||
"develop-json",
|
||||
{arg}
|
||||
],
|
||||
"progressLabel": "rust-analyzer",
|
||||
"filesToWatch": [
|
||||
"BUCK",
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## On `DiscoverWorkspaceConfig::command`
|
||||
|
||||
**Warning**: This format is provisional and subject to change.
|
||||
|
||||
[`DiscoverWorkspaceConfig::command`] *must* return a JSON object
|
||||
corresponding to `DiscoverProjectData::Finished`:
|
||||
|
||||
```norun
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(tag = "kind")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum DiscoverProjectData {
|
||||
Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
|
||||
Error { error: String, source: Option<String> },
|
||||
Progress { message: String },
|
||||
}
|
||||
```
|
||||
|
||||
As JSON, `DiscoverProjectData::Finished` is:
|
||||
|
||||
```json
|
||||
{
|
||||
// the internally-tagged representation of the enum.
|
||||
"kind": "finished",
|
||||
// the file used by a non-Cargo build system to define
|
||||
// a package or target.
|
||||
"buildfile": "rust-analyzer/BUILD",
|
||||
// the contents of a rust-project.json, elided for brevity
|
||||
"project": {
|
||||
"sysroot": "foo",
|
||||
"crates": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It is encouraged, but not required, to use the other variants on
|
||||
`DiscoverProjectData` to provide a more polished end-user experience.
|
||||
|
||||
`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,
|
||||
which will be substituted with the JSON-serialized form of the following
|
||||
enum:
|
||||
|
||||
```norun
|
||||
#[derive(PartialEq, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum DiscoverArgument {
|
||||
Path(AbsPathBuf),
|
||||
Buildfile(AbsPathBuf),
|
||||
}
|
||||
```
|
||||
|
||||
The JSON representation of `DiscoverArgument::Path` is:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "src/main.rs"
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, the JSON representation of `DiscoverArgument::Buildfile` is:
|
||||
|
||||
```
|
||||
{
|
||||
"buildfile": "BUILD"
|
||||
}
|
||||
```
|
||||
|
||||
`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
|
||||
and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
|
||||
to update an existing workspace. As a reference for implementors,
|
||||
buck2's `rust-project` will likely be useful:
|
||||
https://github.com/facebook/buck2/tree/main/integrations/rust-project.
|
||||
--
|
||||
[[rust-analyzer.workspace.symbol.search.kind]]rust-analyzer.workspace.symbol.search.kind (default: `"only_types"`)::
|
||||
+
|
||||
--
|
||||
|
|
|
@ -2595,6 +2595,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "workspace",
|
||||
"properties": {
|
||||
"rust-analyzer.workspace.discoverConfig": {
|
||||
"markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\",\n {arg}\n ],\n \"progressLabel\": \"rust-analyzer\",\n \"filesToWatch\": [\n \"BUCK\",\n ],\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option<String> },\n Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUILD\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```\n{\n \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
|
||||
"default": null,
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"progressLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"filesToWatch": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "workspace",
|
||||
"properties": {
|
||||
|
|
Loading…
Reference in a new issue