aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs140
-rw-r--r--crates/ra_hir_def/src/import_map.rs76
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs14
-rw-r--r--crates/ra_project_model/src/lib.rs4
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/global_state.rs2
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs18
-rw-r--r--crates/rust-analyzer/src/main_loop.rs71
-rw-r--r--crates/rust-analyzer/src/reload.rs82
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs1
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs17
-rw-r--r--docs/dev/lsp-extensions.md12
-rw-r--r--editors/code/src/client.ts1
-rw-r--r--editors/code/src/ctx.ts43
-rw-r--r--editors/code/src/lsp_ext.ts3
15 files changed, 408 insertions, 78 deletions
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 7b6499a08..4cd77adbf 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -811,6 +811,146 @@ fn main() {
811 } 811 }
812 812
813 #[test] 813 #[test]
814 fn trait_method_cross_crate() {
815 check_assist(
816 auto_import,
817 r"
818 //- /main.rs crate:main deps:dep
819 fn main() {
820 let test_struct = dep::test_mod::TestStruct {};
821 test_struct.test_meth<|>od()
822 }
823 //- /dep.rs crate:dep
824 pub mod test_mod {
825 pub trait TestTrait {
826 fn test_method(&self);
827 }
828 pub struct TestStruct {}
829 impl TestTrait for TestStruct {
830 fn test_method(&self) {}
831 }
832 }
833 ",
834 r"
835 use dep::test_mod::TestTrait;
836
837 fn main() {
838 let test_struct = dep::test_mod::TestStruct {};
839 test_struct.test_method()
840 }
841 ",
842 );
843 }
844
845 #[test]
846 fn assoc_fn_cross_crate() {
847 check_assist(
848 auto_import,
849 r"
850 //- /main.rs crate:main deps:dep
851 fn main() {
852 dep::test_mod::TestStruct::test_func<|>tion
853 }
854 //- /dep.rs crate:dep
855 pub mod test_mod {
856 pub trait TestTrait {
857 fn test_function();
858 }
859 pub struct TestStruct {}
860 impl TestTrait for TestStruct {
861 fn test_function() {}
862 }
863 }
864 ",
865 r"
866 use dep::test_mod::TestTrait;
867
868 fn main() {
869 dep::test_mod::TestStruct::test_function
870 }
871 ",
872 );
873 }
874
875 #[test]
876 fn assoc_const_cross_crate() {
877 check_assist(
878 auto_import,
879 r"
880 //- /main.rs crate:main deps:dep
881 fn main() {
882 dep::test_mod::TestStruct::CONST<|>
883 }
884 //- /dep.rs crate:dep
885 pub mod test_mod {
886 pub trait TestTrait {
887 const CONST: bool;
888 }
889 pub struct TestStruct {}
890 impl TestTrait for TestStruct {
891 const CONST: bool = true;
892 }
893 }
894 ",
895 r"
896 use dep::test_mod::TestTrait;
897
898 fn main() {
899 dep::test_mod::TestStruct::CONST
900 }
901 ",
902 );
903 }
904
905 #[test]
906 fn assoc_fn_as_method_cross_crate() {
907 check_assist_not_applicable(
908 auto_import,
909 r"
910 //- /main.rs crate:main deps:dep
911 fn main() {
912 let test_struct = dep::test_mod::TestStruct {};
913 test_struct.test_func<|>tion()
914 }
915 //- /dep.rs crate:dep
916 pub mod test_mod {
917 pub trait TestTrait {
918 fn test_function();
919 }
920 pub struct TestStruct {}
921 impl TestTrait for TestStruct {
922 fn test_function() {}
923 }
924 }
925 ",
926 );
927 }
928
929 #[test]
930 fn private_trait_cross_crate() {
931 check_assist_not_applicable(
932 auto_import,
933 r"
934 //- /main.rs crate:main deps:dep
935 fn main() {
936 let test_struct = dep::test_mod::TestStruct {};
937 test_struct.test_meth<|>od()
938 }
939 //- /dep.rs crate:dep
940 pub mod test_mod {
941 trait TestTrait {
942 fn test_method(&self);
943 }
944 pub struct TestStruct {}
945 impl TestTrait for TestStruct {
946 fn test_method(&self) {}
947 }
948 }
949 ",
950 );
951 }
952
953 #[test]
814 fn not_applicable_for_imported_trait_for_method() { 954 fn not_applicable_for_imported_trait_for_method() {
815 check_assist_not_applicable( 955 check_assist_not_applicable(
816 auto_import, 956 auto_import,
diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs
index 68e20d06b..869f3ca5a 100644
--- a/crates/ra_hir_def/src/import_map.rs
+++ b/crates/ra_hir_def/src/import_map.rs
@@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
5use fst::{self, Streamer}; 5use fst::{self, Streamer};
6use indexmap::{map::Entry, IndexMap}; 6use indexmap::{map::Entry, IndexMap};
7use ra_db::CrateId; 7use ra_db::CrateId;
8use rustc_hash::FxHasher; 8use ra_syntax::SmolStr;
9use rustc_hash::{FxHashMap, FxHasher};
10use smallvec::SmallVec;
9 11
10use crate::{ 12use crate::{
11 db::DefDatabase, 13 db::DefDatabase,
12 item_scope::ItemInNs, 14 item_scope::ItemInNs,
13 path::{ModPath, PathKind}, 15 path::{ModPath, PathKind},
14 visibility::Visibility, 16 visibility::Visibility,
15 ModuleDefId, ModuleId, 17 AssocItemId, ModuleDefId, ModuleId, TraitId,
16}; 18};
17 19
18type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; 20type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@@ -34,6 +36,7 @@ pub struct ImportInfo {
34/// 36///
35/// Note that all paths are relative to the containing crate's root, so the crate name still needs 37/// Note that all paths are relative to the containing crate's root, so the crate name still needs
36/// to be prepended to the `ModPath` before the path is valid. 38/// to be prepended to the `ModPath` before the path is valid.
39#[derive(Default)]
37pub struct ImportMap { 40pub struct ImportMap {
38 map: FxIndexMap<ItemInNs, ImportInfo>, 41 map: FxIndexMap<ItemInNs, ImportInfo>,
39 42
@@ -45,13 +48,17 @@ pub struct ImportMap {
45 /// the index of the first one. 48 /// the index of the first one.
46 importables: Vec<ItemInNs>, 49 importables: Vec<ItemInNs>,
47 fst: fst::Map<Vec<u8>>, 50 fst: fst::Map<Vec<u8>>,
51
52 /// Maps names of associated items to the item's ID. Only includes items whose defining trait is
53 /// exported.
54 assoc_map: FxHashMap<SmolStr, SmallVec<[AssocItemId; 1]>>,
48} 55}
49 56
50impl ImportMap { 57impl ImportMap {
51 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { 58 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
52 let _p = ra_prof::profile("import_map_query"); 59 let _p = ra_prof::profile("import_map_query");
53 let def_map = db.crate_def_map(krate); 60 let def_map = db.crate_def_map(krate);
54 let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default()); 61 let mut import_map = Self::default();
55 62
56 // We look only into modules that are public(ly reexported), starting with the crate root. 63 // We look only into modules that are public(ly reexported), starting with the crate root.
57 let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; 64 let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@@ -85,7 +92,7 @@ impl ImportMap {
85 92
86 for item in per_ns.iter_items() { 93 for item in per_ns.iter_items() {
87 let path = mk_path(); 94 let path = mk_path();
88 match import_map.entry(item) { 95 match import_map.map.entry(item) {
89 Entry::Vacant(entry) => { 96 Entry::Vacant(entry) => {
90 entry.insert(ImportInfo { path, container: module }); 97 entry.insert(ImportInfo { path, container: module });
91 } 98 }
@@ -105,11 +112,16 @@ impl ImportMap {
105 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { 112 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
106 worklist.push((mod_id, mk_path())); 113 worklist.push((mod_id, mk_path()));
107 } 114 }
115
116 // If we've added a path to a trait, add the trait's methods to the method map.
117 if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
118 import_map.collect_trait_methods(db, tr);
119 }
108 } 120 }
109 } 121 }
110 } 122 }
111 123
112 let mut importables = import_map.iter().collect::<Vec<_>>(); 124 let mut importables = import_map.map.iter().collect::<Vec<_>>();
113 125
114 importables.sort_by(cmp); 126 importables.sort_by(cmp);
115 127
@@ -133,10 +145,10 @@ impl ImportMap {
133 builder.insert(key, start as u64).unwrap(); 145 builder.insert(key, start as u64).unwrap();
134 } 146 }
135 147
136 let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); 148 import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
137 let importables = importables.iter().map(|(item, _)| **item).collect(); 149 import_map.importables = importables.iter().map(|(item, _)| **item).collect();
138 150
139 Arc::new(Self { map: import_map, fst, importables }) 151 Arc::new(import_map)
140 } 152 }
141 153
142 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. 154 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
@@ -147,6 +159,13 @@ impl ImportMap {
147 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { 159 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
148 self.map.get(&item) 160 self.map.get(&item)
149 } 161 }
162
163 fn collect_trait_methods(&mut self, db: &dyn DefDatabase, tr: TraitId) {
164 let data = db.trait_data(tr);
165 for (name, item) in data.items.iter() {
166 self.assoc_map.entry(name.to_string().into()).or_default().push(*item);
167 }
168 }
150} 169}
151 170
152impl PartialEq for ImportMap { 171impl PartialEq for ImportMap {
@@ -290,13 +309,26 @@ pub fn search_dependencies<'a>(
290 } 309 }
291 } 310 }
292 311
312 // Add all exported associated items whose names match the query (exactly).
313 for map in &import_maps {
314 if let Some(v) = map.assoc_map.get(&*query.query) {
315 res.extend(v.iter().map(|&assoc| {
316 ItemInNs::Types(match assoc {
317 AssocItemId::FunctionId(it) => it.into(),
318 AssocItemId::ConstId(it) => it.into(),
319 AssocItemId::TypeAliasId(it) => it.into(),
320 })
321 }));
322 }
323 }
324
293 res 325 res
294} 326}
295 327
296#[cfg(test)] 328#[cfg(test)]
297mod tests { 329mod tests {
298 use super::*; 330 use super::*;
299 use crate::test_db::TestDB; 331 use crate::{test_db::TestDB, AssocContainerId, Lookup};
300 use insta::assert_snapshot; 332 use insta::assert_snapshot;
301 use itertools::Itertools; 333 use itertools::Itertools;
302 use ra_db::fixture::WithFixture; 334 use ra_db::fixture::WithFixture;
@@ -339,6 +371,7 @@ mod tests {
339 ItemInNs::Values(_) => "v", 371 ItemInNs::Values(_) => "v",
340 ItemInNs::Macros(_) => "m", 372 ItemInNs::Macros(_) => "m",
341 }; 373 };
374 let item = assoc_to_trait(&db, item);
342 item.krate(db.upcast()).map(|krate| { 375 item.krate(db.upcast()).map(|krate| {
343 let map = db.import_map(krate); 376 let map = db.import_map(krate);
344 let path = map.path_of(item).unwrap(); 377 let path = map.path_of(item).unwrap();
@@ -353,6 +386,29 @@ mod tests {
353 .join("\n") 386 .join("\n")
354 } 387 }
355 388
389 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs {
390 let assoc: AssocItemId = match item {
391 ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
392 ModuleDefId::TypeAliasId(it) => it.into(),
393 ModuleDefId::FunctionId(it) => it.into(),
394 ModuleDefId::ConstId(it) => it.into(),
395 _ => return item,
396 },
397 _ => return item,
398 };
399
400 let container = match assoc {
401 AssocItemId::FunctionId(it) => it.lookup(db).container,
402 AssocItemId::ConstId(it) => it.lookup(db).container,
403 AssocItemId::TypeAliasId(it) => it.lookup(db).container,
404 };
405
406 match container {
407 AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()),
408 _ => item,
409 }
410 }
411
356 #[test] 412 #[test]
357 fn smoke() { 413 fn smoke() {
358 let map = import_map( 414 let map = import_map(
@@ -610,6 +666,7 @@ mod tests {
610 dep::Fmt (m) 666 dep::Fmt (m)
611 dep::fmt::Display (t) 667 dep::fmt::Display (t)
612 dep::format (v) 668 dep::format (v)
669 dep::fmt::Display (t)
613 "###); 670 "###);
614 671
615 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); 672 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
@@ -618,6 +675,7 @@ mod tests {
618 dep::Fmt (t) 675 dep::Fmt (t)
619 dep::Fmt (v) 676 dep::Fmt (v)
620 dep::Fmt (m) 677 dep::Fmt (m)
678 dep::fmt::Display (t)
621 "###); 679 "###);
622 } 680 }
623 681
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 47a1d393d..361fc8eea 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -45,7 +45,7 @@ impl ops::Index<Target> for CargoWorkspace {
45 } 45 }
46} 46}
47 47
48#[derive(Clone, Debug, PartialEq, Eq)] 48#[derive(Default, Clone, Debug, PartialEq, Eq)]
49pub struct CargoConfig { 49pub struct CargoConfig {
50 /// Do not activate the `default` feature. 50 /// Do not activate the `default` feature.
51 pub no_default_features: bool, 51 pub no_default_features: bool,
@@ -64,18 +64,6 @@ pub struct CargoConfig {
64 pub target: Option<String>, 64 pub target: Option<String>,
65} 65}
66 66
67impl Default for CargoConfig {
68 fn default() -> Self {
69 CargoConfig {
70 no_default_features: false,
71 all_features: false,
72 features: Vec::new(),
73 load_out_dirs_from_check: false,
74 target: None,
75 }
76 }
77}
78
79pub type Package = Idx<PackageData>; 67pub type Package = Idx<PackageData>;
80 68
81pub type Target = Idx<TargetData>; 69pub type Target = Idx<TargetData>;
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 8dbf4e6ea..464c3b2e3 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -150,7 +150,7 @@ impl ProjectManifest {
150impl ProjectWorkspace { 150impl ProjectWorkspace {
151 pub fn load( 151 pub fn load(
152 manifest: ProjectManifest, 152 manifest: ProjectManifest,
153 cargo_features: &CargoConfig, 153 cargo_config: &CargoConfig,
154 with_sysroot: bool, 154 with_sysroot: bool,
155 ) -> Result<ProjectWorkspace> { 155 ) -> Result<ProjectWorkspace> {
156 let res = match manifest { 156 let res = match manifest {
@@ -166,7 +166,7 @@ impl ProjectWorkspace {
166 ProjectWorkspace::Json { project } 166 ProjectWorkspace::Json { project }
167 } 167 }
168 ProjectManifest::CargoToml(cargo_toml) => { 168 ProjectManifest::CargoToml(cargo_toml) => {
169 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) 169 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config)
170 .with_context(|| { 170 .with_context(|| {
171 format!( 171 format!(
172 "Failed to read Cargo metadata from Cargo.toml file {}", 172 "Failed to read Cargo metadata from Cargo.toml file {}",
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6c311648a..21acfe644 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -130,6 +130,7 @@ pub struct ClientCapsConfig {
130 pub code_action_group: bool, 130 pub code_action_group: bool,
131 pub resolve_code_action: bool, 131 pub resolve_code_action: bool,
132 pub hover_actions: bool, 132 pub hover_actions: bool,
133 pub status_notification: bool,
133} 134}
134 135
135impl Config { 136impl Config {
@@ -365,6 +366,7 @@ impl Config {
365 self.client_caps.code_action_group = get_bool("codeActionGroup"); 366 self.client_caps.code_action_group = get_bool("codeActionGroup");
366 self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); 367 self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
367 self.client_caps.hover_actions = get_bool("hoverActions"); 368 self.client_caps.hover_actions = get_bool("hoverActions");
369 self.client_caps.status_notification = get_bool("statusNotification");
368 } 370 }
369 } 371 }
370} 372}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index b7b4edf66..640b3959d 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -31,6 +31,8 @@ use crate::{
31pub(crate) enum Status { 31pub(crate) enum Status {
32 Loading, 32 Loading,
33 Ready, 33 Ready,
34 Invalid,
35 NeedsReload,
34} 36}
35 37
36impl Default for Status { 38impl Default for Status {
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 82207bbb8..d225ad5ff 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -3,7 +3,7 @@
3use std::{collections::HashMap, path::PathBuf}; 3use std::{collections::HashMap, path::PathBuf};
4 4
5use lsp_types::request::Request; 5use lsp_types::request::Request;
6use lsp_types::{Position, Range, TextDocumentIdentifier}; 6use lsp_types::{notification::Notification, Position, Range, TextDocumentIdentifier};
7use serde::{Deserialize, Serialize}; 7use serde::{Deserialize, Serialize};
8 8
9pub enum AnalyzerStatus {} 9pub enum AnalyzerStatus {}
@@ -208,6 +208,22 @@ pub struct SsrParams {
208 pub parse_only: bool, 208 pub parse_only: bool,
209} 209}
210 210
211pub enum StatusNotification {}
212
213#[serde(rename_all = "camelCase")]
214#[derive(Serialize, Deserialize)]
215pub enum Status {
216 Loading,
217 Ready,
218 NeedsReload,
219 Invalid,
220}
221
222impl Notification for StatusNotification {
223 type Params = Status;
224 const METHOD: &'static str = "rust-analyzer/status";
225}
226
211pub enum CodeActionRequest {} 227pub enum CodeActionRequest {}
212 228
213impl Request for CodeActionRequest { 229impl Request for CodeActionRequest {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index e03038b25..cfde55431 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -21,6 +21,7 @@ use crate::{
21 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, 21 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
22 Result, 22 Result,
23}; 23};
24use ra_project_model::ProjectWorkspace;
24 25
25pub fn main_loop(config: Config, connection: Connection) -> Result<()> { 26pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
26 log::info!("initial config: {:#?}", config); 27 log::info!("initial config: {:#?}", config);
@@ -58,6 +59,7 @@ enum Event {
58pub(crate) enum Task { 59pub(crate) enum Task {
59 Response(Response), 60 Response(Response),
60 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>), 61 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
62 Workspaces(Vec<anyhow::Result<ProjectWorkspace>>),
61 Unit, 63 Unit,
62} 64}
63 65
@@ -111,7 +113,44 @@ impl GlobalState {
111 } 113 }
112 114
113 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { 115 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
114 self.reload(); 116 if self.config.linked_projects.is_empty() && self.config.notifications.cargo_toml_not_found
117 {
118 self.show_message(
119 lsp_types::MessageType::Error,
120 "rust-analyzer failed to discover workspace".to_string(),
121 );
122 };
123
124 let registration_options = lsp_types::TextDocumentRegistrationOptions {
125 document_selector: Some(vec![
126 lsp_types::DocumentFilter {
127 language: None,
128 scheme: None,
129 pattern: Some("**/*.rs".into()),
130 },
131 lsp_types::DocumentFilter {
132 language: None,
133 scheme: None,
134 pattern: Some("**/Cargo.toml".into()),
135 },
136 lsp_types::DocumentFilter {
137 language: None,
138 scheme: None,
139 pattern: Some("**/Cargo.lock".into()),
140 },
141 ]),
142 };
143 let registration = lsp_types::Registration {
144 id: "textDocument/didSave".to_string(),
145 method: "textDocument/didSave".to_string(),
146 register_options: Some(serde_json::to_value(registration_options).unwrap()),
147 };
148 self.send_request::<lsp_types::request::RegisterCapability>(
149 lsp_types::RegistrationParams { registrations: vec![registration] },
150 |_, _| (),
151 );
152
153 self.fetch_workspaces();
115 154
116 while let Some(event) = self.next_event(&inbox) { 155 while let Some(event) = self.next_event(&inbox) {
117 if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { 156 if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
@@ -153,6 +192,7 @@ impl GlobalState {
153 self.diagnostics.set_native_diagnostics(file_id, diagnostics) 192 self.diagnostics.set_native_diagnostics(file_id, diagnostics)
154 } 193 }
155 } 194 }
195 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces),
156 Task::Unit => (), 196 Task::Unit => (),
157 } 197 }
158 self.analysis_host.maybe_collect_garbage(); 198 self.analysis_host.maybe_collect_garbage();
@@ -169,16 +209,16 @@ impl GlobalState {
169 } 209 }
170 vfs::loader::Message::Progress { n_total, n_done } => { 210 vfs::loader::Message::Progress { n_total, n_done } => {
171 if n_total == 0 { 211 if n_total == 0 {
172 self.status = Status::Ready; 212 self.transition(Status::Invalid);
173 } else { 213 } else {
174 let state = if n_done == 0 { 214 let state = if n_done == 0 {
175 self.status = Status::Loading; 215 self.transition(Status::Loading);
176 Progress::Begin 216 Progress::Begin
177 } else if n_done < n_total { 217 } else if n_done < n_total {
178 Progress::Report 218 Progress::Report
179 } else { 219 } else {
180 assert_eq!(n_done, n_total); 220 assert_eq!(n_done, n_total);
181 self.status = Status::Ready; 221 self.transition(Status::Ready);
182 Progress::End 222 Progress::End
183 }; 223 };
184 self.report_progress( 224 self.report_progress(
@@ -274,11 +314,24 @@ impl GlobalState {
274 Ok(()) 314 Ok(())
275 } 315 }
276 316
317 fn transition(&mut self, new_status: Status) {
318 self.status = Status::Ready;
319 if self.config.client_caps.status_notification {
320 let lsp_status = match new_status {
321 Status::Loading => lsp_ext::Status::Loading,
322 Status::Ready => lsp_ext::Status::Ready,
323 Status::Invalid => lsp_ext::Status::Invalid,
324 Status::NeedsReload => lsp_ext::Status::NeedsReload,
325 };
326 self.send_notification::<lsp_ext::StatusNotification>(lsp_status);
327 }
328 }
329
277 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { 330 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
278 self.register_request(&req, request_received); 331 self.register_request(&req, request_received);
279 332
280 RequestDispatcher { req: Some(req), global_state: self } 333 RequestDispatcher { req: Some(req), global_state: self }
281 .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.reload()))? 334 .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces()))?
282 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? 335 .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
283 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? 336 .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))?
284 .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))? 337 .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))?
@@ -383,10 +436,16 @@ impl GlobalState {
383 ); 436 );
384 Ok(()) 437 Ok(())
385 })? 438 })?
386 .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { 439 .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
387 if let Some(flycheck) = &this.flycheck { 440 if let Some(flycheck) = &this.flycheck {
388 flycheck.handle.update(); 441 flycheck.handle.update();
389 } 442 }
443 let uri = params.text_document.uri.as_str();
444 if uri.ends_with("Cargo.toml") || uri.ends_with("Cargo.lock") {
445 if matches!(this.status, Status::Ready | Status::Invalid) {
446 this.transition(Status::NeedsReload);
447 }
448 }
390 Ok(()) 449 Ok(())
391 })? 450 })?
392 .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| { 451 .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 0c1fd1b8b..74c3344df 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -11,6 +11,7 @@ use vfs::{file_set::FileSetConfig, AbsPath};
11use crate::{ 11use crate::{
12 config::{Config, FilesWatcher, LinkedProject}, 12 config::{Config, FilesWatcher, LinkedProject},
13 global_state::{GlobalState, Handle}, 13 global_state::{GlobalState, Handle},
14 main_loop::Task,
14}; 15};
15 16
16impl GlobalState { 17impl GlobalState {
@@ -20,52 +21,51 @@ impl GlobalState {
20 self.analysis_host.update_lru_capacity(old_config.lru_capacity); 21 self.analysis_host.update_lru_capacity(old_config.lru_capacity);
21 } 22 }
22 if self.config.linked_projects != old_config.linked_projects { 23 if self.config.linked_projects != old_config.linked_projects {
23 self.reload() 24 self.fetch_workspaces()
24 } else if self.config.flycheck != old_config.flycheck { 25 } else if self.config.flycheck != old_config.flycheck {
25 self.reload_flycheck(); 26 self.reload_flycheck();
26 } 27 }
27 } 28 }
28 pub(crate) fn reload(&mut self) { 29 pub(crate) fn fetch_workspaces(&mut self) {
29 log::info!("reloading projects: {:?}", self.config.linked_projects); 30 self.task_pool.handle.spawn({
30 let workspaces = { 31 let linked_projects = self.config.linked_projects.clone();
31 if self.config.linked_projects.is_empty() 32 let cargo_config = self.config.cargo.clone();
32 && self.config.notifications.cargo_toml_not_found 33 let with_sysroot = self.config.with_sysroot.clone();
33 { 34 move || {
34 self.show_message( 35 let workspaces = linked_projects
35 lsp_types::MessageType::Error, 36 .iter()
36 "rust-analyzer failed to discover workspace".to_string(), 37 .map(|project| match project {
37 ); 38 LinkedProject::ProjectManifest(manifest) => {
38 }; 39 ra_project_model::ProjectWorkspace::load(
39 40 manifest.clone(),
40 self.config 41 &cargo_config,
41 .linked_projects 42 with_sysroot,
42 .iter() 43 )
43 .map(|project| match project { 44 }
44 LinkedProject::ProjectManifest(manifest) => { 45 LinkedProject::InlineJsonProject(it) => {
45 ra_project_model::ProjectWorkspace::load( 46 Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
46 manifest.clone(), 47 }
47 &self.config.cargo,
48 self.config.with_sysroot,
49 )
50 }
51 LinkedProject::InlineJsonProject(it) => {
52 Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
53 }
54 })
55 .collect::<Vec<_>>()
56 .into_iter()
57 .filter_map(|res| {
58 res.map_err(|err| {
59 log::error!("failed to load workspace: {:#}", err);
60 self.show_message(
61 lsp_types::MessageType::Error,
62 format!("rust-analyzer failed to load workspace: {:#}", err),
63 );
64 }) 48 })
65 .ok() 49 .collect::<Vec<_>>();
50 Task::Workspaces(workspaces)
51 }
52 });
53 }
54 pub(crate) fn switch_workspaces(&mut self, workspaces: Vec<anyhow::Result<ProjectWorkspace>>) {
55 log::info!("reloading projects: {:?}", self.config.linked_projects);
56 let workspaces = workspaces
57 .into_iter()
58 .filter_map(|res| {
59 res.map_err(|err| {
60 log::error!("failed to load workspace: {:#}", err);
61 self.show_message(
62 lsp_types::MessageType::Error,
63 format!("rust-analyzer failed to load workspace: {:#}", err),
64 );
66 }) 65 })
67 .collect::<Vec<_>>() 66 .ok()
68 }; 67 })
68 .collect::<Vec<_>>();
69 69
70 if let FilesWatcher::Client = self.config.files.watcher { 70 if let FilesWatcher::Client = self.config.files.watcher {
71 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { 71 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
@@ -78,7 +78,7 @@ impl GlobalState {
78 .collect(), 78 .collect(),
79 }; 79 };
80 let registration = lsp_types::Registration { 80 let registration = lsp_types::Registration {
81 id: "file-watcher".to_string(), 81 id: "workspace/didChangeWatchedFiles".to_string(),
82 method: "workspace/didChangeWatchedFiles".to_string(), 82 method: "workspace/didChangeWatchedFiles".to_string(),
83 register_options: Some(serde_json::to_value(registration_options).unwrap()), 83 register_options: Some(serde_json::to_value(registration_options).unwrap()),
84 }; 84 };
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index cc079790e..7b908d30c 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -447,6 +447,7 @@ version = \"0.0.0\"
447", 447",
448 ) 448 )
449 .server(); 449 .server();
450 server.wait_until_workspace_is_loaded();
450 451
451 server.request::<OnEnter>( 452 server.request::<OnEnter>(
452 TextDocumentPositionParams { 453 TextDocumentPositionParams {
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 49f194f7e..7bf687794 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -176,12 +176,19 @@ impl Server {
176 while let Some(msg) = self.recv() { 176 while let Some(msg) = self.recv() {
177 match msg { 177 match msg {
178 Message::Request(req) => { 178 Message::Request(req) => {
179 if req.method != "window/workDoneProgress/create" 179 if req.method == "window/workDoneProgress/create" {
180 && !(req.method == "client/registerCapability" 180 continue;
181 && req.params.to_string().contains("workspace/didChangeWatchedFiles"))
182 {
183 panic!("unexpected request: {:?}", req)
184 } 181 }
182 if req.method == "client/registerCapability" {
183 let params = req.params.to_string();
184 if ["workspace/didChangeWatchedFiles", "textDocument/didSave"]
185 .iter()
186 .any(|&it| params.contains(it))
187 {
188 continue;
189 }
190 }
191 panic!("unexpected request: {:?}", req)
185 } 192 }
186 Message::Notification(_) => (), 193 Message::Notification(_) => (),
187 Message::Response(res) => { 194 Message::Response(res) => {
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index c0afb16d3..6d6bbac7c 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -399,6 +399,18 @@ Returns internal status message, mostly for debugging purposes.
399 399
400Reloads project information (that is, re-executes `cargo metadata`). 400Reloads project information (that is, re-executes `cargo metadata`).
401 401
402## Status Notification
403
404**Client Capability:** `{ "statusNotification": boolean }`
405
406**Method:** `rust-analyzer/status`
407
408**Notification:** `"loading" | "ready" | "invalid" | "needsReload"`
409
410This notification is sent from server to client.
411The client can use it to display persistent status to the user (in modline).
412For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request.
413
402## Syntax Tree 414## Syntax Tree
403 415
404**Method:** `rust-analyzer/syntaxTree` 416**Method:** `rust-analyzer/syntaxTree`
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 65ad573d8..3e5915c28 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -161,6 +161,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
161 caps.codeActionGroup = true; 161 caps.codeActionGroup = true;
162 caps.resolveCodeAction = true; 162 caps.resolveCodeAction = true;
163 caps.hoverActions = true; 163 caps.hoverActions = true;
164 caps.statusNotification = true;
164 capabilities.experimental = caps; 165 capabilities.experimental = caps;
165 } 166 }
166 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 167 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 41df11991..6e767babf 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,9 +1,11 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext';
3 4
4import { Config } from './config'; 5import { Config } from './config';
5import { createClient } from './client'; 6import { createClient } from './client';
6import { isRustEditor, RustEditor } from './util'; 7import { isRustEditor, RustEditor } from './util';
8import { Status } from './lsp_ext';
7 9
8export class Ctx { 10export class Ctx {
9 private constructor( 11 private constructor(
@@ -11,6 +13,7 @@ export class Ctx {
11 private readonly extCtx: vscode.ExtensionContext, 13 private readonly extCtx: vscode.ExtensionContext,
12 readonly client: lc.LanguageClient, 14 readonly client: lc.LanguageClient,
13 readonly serverPath: string, 15 readonly serverPath: string,
16 readonly statusBar: vscode.StatusBarItem,
14 ) { 17 ) {
15 18
16 } 19 }
@@ -22,9 +25,18 @@ export class Ctx {
22 cwd: string, 25 cwd: string,
23 ): Promise<Ctx> { 26 ): Promise<Ctx> {
24 const client = createClient(serverPath, cwd); 27 const client = createClient(serverPath, cwd);
25 const res = new Ctx(config, extCtx, client, serverPath); 28
29 const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
30 extCtx.subscriptions.push(statusBar);
31 statusBar.text = "rust-analyzer";
32 statusBar.tooltip = "ready";
33 statusBar.show();
34
35 const res = new Ctx(config, extCtx, client, serverPath, statusBar);
36
26 res.pushCleanup(client.start()); 37 res.pushCleanup(client.start());
27 await client.onReady(); 38 await client.onReady();
39 client.onNotification(ra.status, (status) => res.setStatus(status));
28 return res; 40 return res;
29 } 41 }
30 42
@@ -54,6 +66,35 @@ export class Ctx {
54 return this.extCtx.subscriptions; 66 return this.extCtx.subscriptions;
55 } 67 }
56 68
69 setStatus(status: Status) {
70 switch (status) {
71 case "loading":
72 this.statusBar.text = "$(sync~spin) rust-analyzer";
73 this.statusBar.tooltip = "Loading the project";
74 this.statusBar.command = undefined;
75 this.statusBar.color = undefined;
76 break;
77 case "ready":
78 this.statusBar.text = "rust-analyzer";
79 this.statusBar.tooltip = "Ready";
80 this.statusBar.command = undefined;
81 this.statusBar.color = undefined;
82 break;
83 case "invalid":
84 this.statusBar.text = "$(error) rust-analyzer";
85 this.statusBar.tooltip = "Failed to load the project";
86 this.statusBar.command = undefined;
87 this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
88 break;
89 case "needsReload":
90 this.statusBar.text = "$(warning) rust-analyzer";
91 this.statusBar.tooltip = "Click to reload";
92 this.statusBar.command = "rust-analyzer.reloadWorkspace";
93 this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
94 break;
95 }
96 }
97
57 pushCleanup(d: Disposable) { 98 pushCleanup(d: Disposable) {
58 this.extCtx.subscriptions.push(d); 99 this.extCtx.subscriptions.push(d);
59 } 100 }
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 981b6f40e..bf4703239 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -6,6 +6,9 @@ import * as lc from "vscode-languageclient";
6 6
7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); 7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus");
8 8
9export type Status = "loading" | "ready" | "invalid" | "needsReload";
10export const status = new lc.NotificationType<Status>("rust-analyzer/status");
11
9export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace"); 12export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace");
10 13
11export interface SyntaxTreeParams { 14export interface SyntaxTreeParams {