aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock29
-rw-r--r--crates/ra_hir/src/code_model.rs8
-rw-r--r--crates/ra_hir_def/src/attr.rs8
-rw-r--r--crates/ra_hir_def/src/data.rs6
-rw-r--r--crates/ra_hir_def/src/docs.rs50
-rw-r--r--crates/ra_hir_expand/src/name.rs1
-rw-r--r--crates/ra_ide/src/completion.rs78
-rw-r--r--crates/ra_ide/src/display/function_signature.rs13
-rw-r--r--crates/ra_ide/src/hover.rs127
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html48
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html1
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html1
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs25
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs1
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs8
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs31
-rw-r--r--crates/ra_parser/src/grammar.rs7
-rw-r--r--crates/ra_project_model/src/json_project.rs89
-rw-r--r--crates/ra_project_model/src/lib.rs60
-rw-r--r--crates/ra_syntax/src/ast/traits.rs13
-rw-r--r--crates/rust-analyzer/src/bin/main.rs41
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs4
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs5
-rw-r--r--crates/rust-analyzer/src/config.rs48
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap2
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs3
-rw-r--r--crates/rust-analyzer/src/from_proto.rs8
-rw-r--r--crates/rust-analyzer/src/global_state.rs (renamed from crates/rust-analyzer/src/world.rs)42
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs147
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs390
-rw-r--r--crates/rust-analyzer/src/to_proto.rs88
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs54
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs31
-rw-r--r--crates/stdx/src/lib.rs5
-rw-r--r--crates/test_utils/Cargo.toml3
-rw-r--r--crates/test_utils/src/lib.rs6
-rw-r--r--docs/dev/README.md105
-rw-r--r--docs/user/generated_assists.adoc1015
-rw-r--r--docs/user/generated_features.adoc298
-rw-r--r--docs/user/manual.adoc51
-rw-r--r--editors/code/package.json19
-rw-r--r--xtask/src/codegen.rs6
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs11
-rw-r--r--xtask/src/lib.rs2
-rw-r--r--xtask/src/main.rs1
-rw-r--r--xtask/tests/tidy.rs25
50 files changed, 1191 insertions, 1829 deletions
diff --git a/.gitignore b/.gitignore
index dab51647d..aef0fac33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ crates/*/target
8*.iml 8*.iml
9.vscode/settings.json 9.vscode/settings.json
10*.html 10*.html
11generated_assists.adoc
12generated_features.adoc
diff --git a/Cargo.lock b/Cargo.lock
index af27bfc85..5f88ad0c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -462,9 +462,9 @@ dependencies = [
462 462
463[[package]] 463[[package]]
464name = "indexmap" 464name = "indexmap"
465version = "1.3.2" 465version = "1.4.0"
466source = "registry+https://github.com/rust-lang/crates.io-index" 466source = "registry+https://github.com/rust-lang/crates.io-index"
467checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 467checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
468dependencies = [ 468dependencies = [
469 "autocfg", 469 "autocfg",
470] 470]
@@ -809,9 +809,9 @@ dependencies = [
809 809
810[[package]] 810[[package]]
811name = "paste" 811name = "paste"
812version = "0.1.15" 812version = "0.1.16"
813source = "registry+https://github.com/rust-lang/crates.io-index" 813source = "registry+https://github.com/rust-lang/crates.io-index"
814checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b" 814checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec"
815dependencies = [ 815dependencies = [
816 "paste-impl", 816 "paste-impl",
817 "proc-macro-hack", 817 "proc-macro-hack",
@@ -819,9 +819,9 @@ dependencies = [
819 819
820[[package]] 820[[package]]
821name = "paste-impl" 821name = "paste-impl"
822version = "0.1.15" 822version = "0.1.16"
823source = "registry+https://github.com/rust-lang/crates.io-index" 823source = "registry+https://github.com/rust-lang/crates.io-index"
824checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17" 824checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32"
825dependencies = [ 825dependencies = [
826 "proc-macro-hack", 826 "proc-macro-hack",
827 "proc-macro2", 827 "proc-macro2",
@@ -871,9 +871,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
871 871
872[[package]] 872[[package]]
873name = "proc-macro2" 873name = "proc-macro2"
874version = "1.0.17" 874version = "1.0.18"
875source = "registry+https://github.com/rust-lang/crates.io-index" 875source = "registry+https://github.com/rust-lang/crates.io-index"
876checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" 876checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
877dependencies = [ 877dependencies = [
878 "unicode-xid", 878 "unicode-xid",
879] 879]
@@ -1401,9 +1401,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
1401 1401
1402[[package]] 1402[[package]]
1403name = "ryu" 1403name = "ryu"
1404version = "1.0.4" 1404version = "1.0.5"
1405source = "registry+https://github.com/rust-lang/crates.io-index" 1405source = "registry+https://github.com/rust-lang/crates.io-index"
1406checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" 1406checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1407 1407
1408[[package]] 1408[[package]]
1409name = "salsa" 1409name = "salsa"
@@ -1577,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f"
1577 1577
1578[[package]] 1578[[package]]
1579name = "syn" 1579name = "syn"
1580version = "1.0.29" 1580version = "1.0.30"
1581source = "registry+https://github.com/rust-lang/crates.io-index" 1581source = "registry+https://github.com/rust-lang/crates.io-index"
1582checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" 1582checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
1583dependencies = [ 1583dependencies = [
1584 "proc-macro2", 1584 "proc-macro2",
1585 "quote", 1585 "quote",
@@ -1640,6 +1640,7 @@ dependencies = [
1640 "relative-path", 1640 "relative-path",
1641 "rustc-hash", 1641 "rustc-hash",
1642 "serde_json", 1642 "serde_json",
1643 "stdx",
1643 "text-size", 1644 "text-size",
1644] 1645]
1645 1646
@@ -1798,9 +1799,9 @@ dependencies = [
1798 1799
1799[[package]] 1800[[package]]
1800name = "yaml-rust" 1801name = "yaml-rust"
1801version = "0.4.3" 1802version = "0.4.4"
1802source = "registry+https://github.com/rust-lang/crates.io-index" 1803source = "registry+https://github.com/rust-lang/crates.io-index"
1803checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 1804checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
1804dependencies = [ 1805dependencies = [
1805 "linked-hash-map", 1806 "linked-hash-map",
1806] 1807]
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index e40aeffbc..4a06f3bcd 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -637,6 +637,10 @@ impl Function {
637 db.function_data(self.id).params.clone() 637 db.function_data(self.id).params.clone()
638 } 638 }
639 639
640 pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool {
641 db.function_data(self.id).is_unsafe
642 }
643
640 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 644 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
641 let _p = profile("Function::diagnostics"); 645 let _p = profile("Function::diagnostics");
642 let infer = db.infer(self.id.into()); 646 let infer = db.infer(self.id.into());
@@ -1190,6 +1194,10 @@ impl Type {
1190 ) 1194 )
1191 } 1195 }
1192 1196
1197 pub fn is_raw_ptr(&self) -> bool {
1198 matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }))
1199 }
1200
1193 pub fn contains_unknown(&self) -> bool { 1201 pub fn contains_unknown(&self) -> bool {
1194 return go(&self.ty.value); 1202 return go(&self.ty.value);
1195 1203
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 8b6c0bede..2eeba0572 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -87,12 +87,18 @@ impl Attrs {
87 } 87 }
88 88
89 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { 89 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
90 let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
91 |docs_text| Attr {
92 input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
93 path: ModPath::from(hir_expand::name!(doc)),
94 },
95 );
90 let mut attrs = owner.attrs().peekable(); 96 let mut attrs = owner.attrs().peekable();
91 let entries = if attrs.peek().is_none() { 97 let entries = if attrs.peek().is_none() {
92 // Avoid heap allocation 98 // Avoid heap allocation
93 None 99 None
94 } else { 100 } else {
95 Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) 101 Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
96 }; 102 };
97 Attrs { entries } 103 Attrs { entries }
98 } 104 }
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs
index e2130d931..807195d25 100644
--- a/crates/ra_hir_def/src/data.rs
+++ b/crates/ra_hir_def/src/data.rs
@@ -34,6 +34,7 @@ pub struct FunctionData {
34 /// True if the first param is `self`. This is relevant to decide whether this 34 /// True if the first param is `self`. This is relevant to decide whether this
35 /// can be called as a method. 35 /// can be called as a method.
36 pub has_self_param: bool, 36 pub has_self_param: bool,
37 pub is_unsafe: bool,
37 pub visibility: RawVisibility, 38 pub visibility: RawVisibility,
38} 39}
39 40
@@ -85,11 +86,14 @@ impl FunctionData {
85 ret_type 86 ret_type
86 }; 87 };
87 88
89 let is_unsafe = src.value.unsafe_token().is_some();
90
88 let vis_default = RawVisibility::default_for_container(loc.container); 91 let vis_default = RawVisibility::default_for_container(loc.container);
89 let visibility = 92 let visibility =
90 RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); 93 RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility()));
91 94
92 let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; 95 let sig =
96 FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs };
93 Arc::new(sig) 97 Arc::new(sig)
94 } 98 }
95} 99}
diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs
index b221ae1ce..2630b3d89 100644
--- a/crates/ra_hir_def/src/docs.rs
+++ b/crates/ra_hir_def/src/docs.rs
@@ -29,6 +29,13 @@ impl Documentation {
29 Documentation(s.into()) 29 Documentation(s.into())
30 } 30 }
31 31
32 pub fn from_ast<N>(node: &N) -> Option<Documentation>
33 where
34 N: ast::DocCommentsOwner + ast::AttrsOwner,
35 {
36 docs_from_ast(node)
37 }
38
32 pub fn as_str(&self) -> &str { 39 pub fn as_str(&self) -> &str {
33 &*self.0 40 &*self.0
34 } 41 }
@@ -70,6 +77,45 @@ impl Documentation {
70 } 77 }
71} 78}
72 79
73pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option<Documentation> { 80pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
74 node.doc_comment_text().map(|it| Documentation::new(&it)) 81where
82 N: ast::DocCommentsOwner + ast::AttrsOwner,
83{
84 let doc_comment_text = node.doc_comment_text();
85 let doc_attr_text = expand_doc_attrs(node);
86 let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text);
87 docs.map(|it| Documentation::new(&it))
88}
89
90fn merge_doc_comments_and_attrs(
91 doc_comment_text: Option<String>,
92 doc_attr_text: Option<String>,
93) -> Option<String> {
94 match (doc_comment_text, doc_attr_text) {
95 (Some(mut comment_text), Some(attr_text)) => {
96 comment_text.push_str("\n\n");
97 comment_text.push_str(&attr_text);
98 Some(comment_text)
99 }
100 (Some(comment_text), None) => Some(comment_text),
101 (None, Some(attr_text)) => Some(attr_text),
102 (None, None) => None,
103 }
104}
105
106fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
107 let mut docs = String::new();
108 for attr in owner.attrs() {
109 if let Some(("doc", value)) =
110 attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str()))
111 {
112 docs.push_str(value);
113 docs.push_str("\n\n");
114 }
115 }
116 if docs.is_empty() {
117 None
118 } else {
119 Some(docs.trim_end_matches("\n\n").to_owned())
120 }
75} 121}
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs
index ea495cb11..660bdfe33 100644
--- a/crates/ra_hir_expand/src/name.rs
+++ b/crates/ra_hir_expand/src/name.rs
@@ -153,6 +153,7 @@ pub mod known {
153 str, 153 str,
154 // Special names 154 // Special names
155 macro_rules, 155 macro_rules,
156 doc,
156 // Components of known path (value or mod name) 157 // Components of known path (value or mod name)
157 std, 158 std,
158 core, 159 core,
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index d890b69d2..a721e23c6 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -125,3 +125,81 @@ pub(crate) fn completions(
125 125
126 Some(acc) 126 Some(acc)
127} 127}
128
129#[cfg(test)]
130mod tests {
131 use crate::completion::completion_config::CompletionConfig;
132 use crate::mock_analysis::analysis_and_position;
133
134 struct DetailAndDocumentation<'a> {
135 detail: &'a str,
136 documentation: &'a str,
137 }
138
139 fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) {
140 let (analysis, position) = analysis_and_position(fixture);
141 let config = CompletionConfig::default();
142 let completions = analysis.completions(&config, position).unwrap().unwrap();
143 for item in completions {
144 if item.detail() == Some(expected.detail) {
145 let opt = item.documentation();
146 let doc = opt.as_ref().map(|it| it.as_str());
147 assert_eq!(doc, Some(expected.documentation));
148 return;
149 }
150 }
151 panic!("completion detail not found: {}", expected.detail)
152 }
153
154 #[test]
155 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
156 check_detail_and_documentation(
157 r#"
158 //- /lib.rs
159 macro_rules! bar {
160 () => {
161 struct Bar;
162 impl Bar {
163 #[doc = "Do the foo"]
164 fn foo(&self) {}
165 }
166 }
167 }
168
169 bar!();
170
171 fn foo() {
172 let bar = Bar;
173 bar.fo<|>;
174 }
175 "#,
176 DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
177 );
178 }
179
180 #[test]
181 fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
182 check_detail_and_documentation(
183 r#"
184 //- /lib.rs
185 macro_rules! bar {
186 () => {
187 struct Bar;
188 impl Bar {
189 /// Do the foo
190 fn foo(&self) {}
191 }
192 }
193 }
194
195 bar!();
196
197 fn foo() {
198 let bar = Bar;
199 bar.fo<|>;
200 }
201 "#,
202 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
203 );
204 }
205}
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index 9572debd8..ca8a6a650 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -10,7 +10,7 @@ use std::{
10use hir::{Docs, Documentation, HasSource, HirDisplay}; 10use hir::{Docs, Documentation, HasSource, HirDisplay};
11use ra_ide_db::RootDatabase; 11use ra_ide_db::RootDatabase;
12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; 12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
13use stdx::SepBy; 13use stdx::{split1, SepBy};
14 14
15use crate::display::{generic_parameters, where_predicates}; 15use crate::display::{generic_parameters, where_predicates};
16 16
@@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
207 res.push(raw_param); 207 res.push(raw_param);
208 } 208 }
209 209
210 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 210 // macro-generated functions are missing whitespace
211 fn fmt_param(param: ast::Param) -> String {
212 let text = param.syntax().text().to_string();
213 match split1(&text, ':') {
214 Some((left, right)) => format!("{}: {}", left.trim(), right.trim()),
215 _ => text,
216 }
217 }
218
219 res.extend(param_list.params().map(fmt_param));
211 res_types.extend(param_list.params().map(|param| { 220 res_types.extend(param_list.params().map(|param| {
212 let param_text = param.syntax().text().to_string(); 221 let param_text = param.syntax().text().to_string();
213 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { 222 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index d96cb5596..9636cd0d6 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,8 +1,8 @@
1use std::iter::once; 1use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
5 ModuleSource, Semantics, 5 ModuleDef, ModuleSource, Semantics,
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -10,12 +10,7 @@ use ra_ide_db::{
10 defs::{classify_name, classify_name_ref, Definition}, 10 defs::{classify_name, classify_name_ref, Definition},
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use ra_syntax::{ 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14 ast::{self, DocCommentsOwner},
15 match_ast, AstNode,
16 SyntaxKind::*,
17 SyntaxToken, TokenAtOffset,
18};
19 14
20use crate::{ 15use crate::{
21 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, 16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
@@ -169,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
169 return match def { 164 return match def {
170 Definition::Macro(it) => { 165 Definition::Macro(it) => {
171 let src = it.source(db); 166 let src = it.source(db);
172 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) 167 let docs = Documentation::from_ast(&src.value).map(Into::into);
168 hover_text(docs, Some(macro_label(&src.value)), mod_path)
173 } 169 }
174 Definition::Field(it) => { 170 Definition::Field(it) => {
175 let src = it.source(db); 171 let src = it.source(db);
176 match src.value { 172 match src.value {
177 FieldSource::Named(it) => { 173 FieldSource::Named(it) => {
178 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 174 let docs = Documentation::from_ast(&it).map(Into::into);
175 hover_text(docs, it.short_label(), mod_path)
179 } 176 }
180 _ => None, 177 _ => None,
181 } 178 }
@@ -183,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
183 Definition::ModuleDef(it) => match it { 180 Definition::ModuleDef(it) => match it {
184 ModuleDef::Module(it) => match it.definition_source(db).value { 181 ModuleDef::Module(it) => match it.definition_source(db).value {
185 ModuleSource::Module(it) => { 182 ModuleSource::Module(it) => {
186 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 183 let docs = Documentation::from_ast(&it).map(Into::into);
184 hover_text(docs, it.short_label(), mod_path)
187 } 185 }
188 _ => None, 186 _ => None,
189 }, 187 },
@@ -208,10 +206,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
208 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> 206 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
209 where 207 where
210 D: HasSource<Ast = A>, 208 D: HasSource<Ast = A>,
211 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, 209 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
212 { 210 {
213 let src = def.source(db); 211 let src = def.source(db);
214 hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) 212 let docs = Documentation::from_ast(&src.value).map(Into::into);
213 hover_text(docs, src.value.short_label(), mod_path)
215 } 214 }
216} 215}
217 216
@@ -951,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; }
951 &["mod my"], 950 &["mod my"],
952 ); 951 );
953 } 952 }
953
954 #[test]
955 fn test_hover_struct_doc_comment() {
956 check_hover_result(
957 r#"
958 //- /lib.rs
959 /// bar docs
960 struct Bar;
961
962 fn foo() {
963 let bar = Ba<|>r;
964 }
965 "#,
966 &["struct Bar\n```\n___\n\nbar docs"],
967 );
968 }
969
970 #[test]
971 fn test_hover_struct_doc_attr() {
972 check_hover_result(
973 r#"
974 //- /lib.rs
975 #[doc = "bar docs"]
976 struct Bar;
977
978 fn foo() {
979 let bar = Ba<|>r;
980 }
981 "#,
982 &["struct Bar\n```\n___\n\nbar docs"],
983 );
984 }
985
986 #[test]
987 fn test_hover_struct_doc_attr_multiple_and_mixed() {
988 check_hover_result(
989 r#"
990 //- /lib.rs
991 /// bar docs 0
992 #[doc = "bar docs 1"]
993 #[doc = "bar docs 2"]
994 struct Bar;
995
996 fn foo() {
997 let bar = Ba<|>r;
998 }
999 "#,
1000 &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"],
1001 );
1002 }
1003
1004 #[test]
1005 fn test_hover_macro_generated_struct_fn_doc_comment() {
1006 check_hover_result(
1007 r#"
1008 //- /lib.rs
1009 macro_rules! bar {
1010 () => {
1011 struct Bar;
1012 impl Bar {
1013 /// Do the foo
1014 fn foo(&self) {}
1015 }
1016 }
1017 }
1018
1019 bar!();
1020
1021 fn foo() {
1022 let bar = Bar;
1023 bar.fo<|>o();
1024 }
1025 "#,
1026 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"],
1027 );
1028 }
1029
1030 #[test]
1031 fn test_hover_macro_generated_struct_fn_doc_attr() {
1032 check_hover_result(
1033 r#"
1034 //- /lib.rs
1035 macro_rules! bar {
1036 () => {
1037 struct Bar;
1038 impl Bar {
1039 #[doc = "Do the foo"]
1040 fn foo(&self) {}
1041 }
1042 }
1043 }
1044
1045 bar!();
1046
1047 fn foo() {
1048 let bar = Bar;
1049 bar.fo<|>o();
1050 }
1051 "#,
1052 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
1053 );
1054 }
954} 1055}
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html
index 68fc589bc..fcdc98201 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index 41cddd0ff..e97192b61 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html
new file mode 100644
index 000000000..17ffc727c
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html
@@ -0,0 +1,48 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
14.parameter { color: #94BFF3; }
15.text { color: #DCDCCC; }
16.type { color: #7CB8BB; }
17.builtin_type { color: #8CD0D3; }
18.type_param { color: #DFAF8F; }
19.attribute { color: #94BFF3; }
20.numeric_literal { color: #BFEBBF; }
21.bool_literal { color: #BFE6EB; }
22.macro { color: #94BFF3; }
23.module { color: #AFD8AF; }
24.variable { color: #DCDCCC; }
25.format_specifier { color: #CC696B; }
26.mutable { text-decoration: underline; }
27
28.keyword { color: #F0DFAF; font-weight: bold; }
29.keyword.unsafe { color: #BC8383; font-weight: bold; }
30.control { font-style: italic; }
31</style>
32<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
33
34<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
35
36<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
37 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
38}
39
40<span class="keyword">fn</span> <span class="function declaration">main</span>() {
41 <span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
42 <span class="keyword unsafe">unsafe</span> {
43 <span class="function unsafe">unsafe_fn</span>();
44 <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
45 <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>;
46 <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
47 }
48}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 352e35095..42c5f3e55 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 2a0294f71..2dd61d20d 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.operator.unsafe { color: #E28C14; }
13.parameter { color: #94BFF3; } 14.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 15.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 16.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0b53ebe69..19ecd54d6 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -406,6 +406,23 @@ fn highlight_element(
406 _ => h, 406 _ => h,
407 } 407 }
408 } 408 }
409 PREFIX_EXPR => {
410 let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?;
411 match prefix_expr.op_kind() {
412 Some(ast::PrefixOp::Deref) => {}
413 _ => return None,
414 }
415
416 let expr = prefix_expr.expr()?;
417 let ty = sema.type_of_expr(&expr)?;
418 if !ty.is_raw_ptr() {
419 return None;
420 }
421
422 let mut h = Highlight::new(HighlightTag::Operator);
423 h |= HighlightModifier::Unsafe;
424 h
425 }
409 426
410 k if k.is_keyword() => { 427 k if k.is_keyword() => {
411 let h = Highlight::new(HighlightTag::Keyword); 428 let h = Highlight::new(HighlightTag::Keyword);
@@ -458,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
458 Definition::Field(_) => HighlightTag::Field, 475 Definition::Field(_) => HighlightTag::Field,
459 Definition::ModuleDef(def) => match def { 476 Definition::ModuleDef(def) => match def {
460 hir::ModuleDef::Module(_) => HighlightTag::Module, 477 hir::ModuleDef::Module(_) => HighlightTag::Module,
461 hir::ModuleDef::Function(_) => HighlightTag::Function, 478 hir::ModuleDef::Function(func) => {
479 let mut h = HighlightTag::Function.into();
480 if func.is_unsafe(db) {
481 h |= HighlightModifier::Unsafe;
482 }
483 return h;
484 }
462 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 485 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
463 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 486 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
464 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, 487 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index edfe61f39..7d946c98d 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -69,6 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
69.string_literal { color: #CC9393; } 69.string_literal { color: #CC9393; }
70.field { color: #94BFF3; } 70.field { color: #94BFF3; }
71.function { color: #93E0E3; } 71.function { color: #93E0E3; }
72.operator.unsafe { color: #E28C14; }
72.parameter { color: #94BFF3; } 73.parameter { color: #94BFF3; }
73.text { color: #DCDCCC; } 74.text { color: #DCDCCC; }
74.type { color: #7CB8BB; } 75.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 1514531de..94f466966 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -24,12 +24,14 @@ pub enum HighlightTag {
24 Enum, 24 Enum,
25 EnumVariant, 25 EnumVariant,
26 Field, 26 Field,
27 FormatSpecifier,
27 Function, 28 Function,
28 Keyword, 29 Keyword,
29 Lifetime, 30 Lifetime,
30 Macro, 31 Macro,
31 Module, 32 Module,
32 NumericLiteral, 33 NumericLiteral,
34 Operator,
33 SelfKeyword, 35 SelfKeyword,
34 SelfType, 36 SelfType,
35 Static, 37 Static,
@@ -41,8 +43,6 @@ pub enum HighlightTag {
41 Union, 43 Union,
42 Local, 44 Local,
43 UnresolvedReference, 45 UnresolvedReference,
44 FormatSpecifier,
45 Operator,
46} 46}
47 47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -72,12 +72,14 @@ impl HighlightTag {
72 HighlightTag::Enum => "enum", 72 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 73 HighlightTag::EnumVariant => "enum_variant",
74 HighlightTag::Field => "field", 74 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier",
75 HighlightTag::Function => "function", 76 HighlightTag::Function => "function",
76 HighlightTag::Keyword => "keyword", 77 HighlightTag::Keyword => "keyword",
77 HighlightTag::Lifetime => "lifetime", 78 HighlightTag::Lifetime => "lifetime",
78 HighlightTag::Macro => "macro", 79 HighlightTag::Macro => "macro",
79 HighlightTag::Module => "module", 80 HighlightTag::Module => "module",
80 HighlightTag::NumericLiteral => "numeric_literal", 81 HighlightTag::NumericLiteral => "numeric_literal",
82 HighlightTag::Operator => "operator",
81 HighlightTag::SelfKeyword => "self_keyword", 83 HighlightTag::SelfKeyword => "self_keyword",
82 HighlightTag::SelfType => "self_type", 84 HighlightTag::SelfType => "self_type",
83 HighlightTag::Static => "static", 85 HighlightTag::Static => "static",
@@ -89,8 +91,6 @@ impl HighlightTag {
89 HighlightTag::Union => "union", 91 HighlightTag::Union => "union",
90 HighlightTag::Local => "variable", 92 HighlightTag::Local => "variable",
91 HighlightTag::UnresolvedReference => "unresolved_reference", 93 HighlightTag::UnresolvedReference => "unresolved_reference",
92 HighlightTag::FormatSpecifier => "format_specifier",
93 HighlightTag::Operator => "operator",
94 } 94 }
95 } 95 }
96} 96}
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 7dc229cab..36a1aa419 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -258,3 +258,34 @@ fn main() {
258 fs::write(dst_file, &actual_html).unwrap(); 258 fs::write(dst_file, &actual_html).unwrap();
259 assert_eq_text!(expected_html, actual_html); 259 assert_eq_text!(expected_html, actual_html);
260} 260}
261
262#[test]
263fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file(
265 r#"
266unsafe fn unsafe_fn() {}
267
268struct HasUnsafeFn;
269
270impl HasUnsafeFn {
271 unsafe fn unsafe_method(&self) {}
272}
273
274fn main() {
275 let x = &5 as *const usize;
276 unsafe {
277 unsafe_fn();
278 HasUnsafeFn.unsafe_method();
279 let y = *x;
280 let z = -x;
281 }
282}
283"#
284 .trim(),
285 );
286 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html");
287 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
288 let expected_html = &read_text(&dst_file);
289 fs::write(dst_file, &actual_html).unwrap();
290 assert_eq_text!(expected_html, actual_html);
291}
diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs
index be0cd5661..293baecf6 100644
--- a/crates/ra_parser/src/grammar.rs
+++ b/crates/ra_parser/src/grammar.rs
@@ -18,9 +18,10 @@
18//! // fn foo() {} 18//! // fn foo() {}
19//! ``` 19//! ```
20//! 20//!
21//! After adding a new inline-test, run `cargo collect-tests` to extract 21//! After adding a new inline-test, run `cargo xtask codegen` to
22//! it as a standalone text-fixture into `tests/data/parser/inline`, and 22//! extract it as a standalone text-fixture into
23//! run `cargo test` once to create the "gold" value. 23//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to
24//! create the "gold" value.
24//! 25//!
25//! Coding convention: rules like `where_clause` always produce either a 26//! Coding convention: rules like `where_clause` always produce either a
26//! node or an error, rules like `opt_where_clause` may produce nothing. 27//! node or an error, rules like `opt_where_clause` may produce nothing.
diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs
index b030c8a6a..09c06fef9 100644
--- a/crates/ra_project_model/src/json_project.rs
+++ b/crates/ra_project_model/src/json_project.rs
@@ -5,6 +5,13 @@ use std::path::PathBuf;
5use rustc_hash::{FxHashMap, FxHashSet}; 5use rustc_hash::{FxHashMap, FxHashSet};
6use serde::Deserialize; 6use serde::Deserialize;
7 7
8/// Roots and crates that compose this Rust project.
9#[derive(Clone, Debug, Deserialize)]
10pub struct JsonProject {
11 pub(crate) roots: Vec<Root>,
12 pub(crate) crates: Vec<Crate>,
13}
14
8/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in 15/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in
9/// all roots. Roots might be nested. 16/// all roots. Roots might be nested.
10#[derive(Clone, Debug, Deserialize)] 17#[derive(Clone, Debug, Deserialize)]
@@ -20,8 +27,17 @@ pub struct Crate {
20 pub(crate) root_module: PathBuf, 27 pub(crate) root_module: PathBuf,
21 pub(crate) edition: Edition, 28 pub(crate) edition: Edition,
22 pub(crate) deps: Vec<Dep>, 29 pub(crate) deps: Vec<Dep>,
30
31 // This is the preferred method of providing cfg options.
32 #[serde(default)]
33 pub(crate) cfg: FxHashSet<String>,
34
35 // These two are here for transition only.
36 #[serde(default)]
23 pub(crate) atom_cfgs: FxHashSet<String>, 37 pub(crate) atom_cfgs: FxHashSet<String>,
38 #[serde(default)]
24 pub(crate) key_value_cfgs: FxHashMap<String, String>, 39 pub(crate) key_value_cfgs: FxHashMap<String, String>,
40
25 pub(crate) out_dir: Option<PathBuf>, 41 pub(crate) out_dir: Option<PathBuf>,
26 pub(crate) proc_macro_dylib_path: Option<PathBuf>, 42 pub(crate) proc_macro_dylib_path: Option<PathBuf>,
27} 43}
@@ -48,9 +64,72 @@ pub struct Dep {
48 pub(crate) name: String, 64 pub(crate) name: String,
49} 65}
50 66
51/// Roots and crates that compose this Rust project. 67#[cfg(test)]
52#[derive(Clone, Debug, Deserialize)] 68mod tests {
53pub struct JsonProject { 69 use super::*;
54 pub(crate) roots: Vec<Root>, 70 use serde_json::json;
55 pub(crate) crates: Vec<Crate>, 71
72 #[test]
73 fn test_crate_deserialization() {
74 let raw_json = json!( {
75 "crate_id": 2,
76 "root_module": "this/is/a/file/path.rs",
77 "deps": [
78 {
79 "crate": 1,
80 "name": "some_dep_crate"
81 },
82 ],
83 "edition": "2015",
84 "cfg": [
85 "atom_1",
86 "atom_2",
87 "feature=feature_1",
88 "feature=feature_2",
89 "other=value",
90 ],
91
92 });
93
94 let krate: Crate = serde_json::from_value(raw_json).unwrap();
95
96 assert!(krate.cfg.contains(&"atom_1".to_string()));
97 assert!(krate.cfg.contains(&"atom_2".to_string()));
98 assert!(krate.cfg.contains(&"feature=feature_1".to_string()));
99 assert!(krate.cfg.contains(&"feature=feature_2".to_string()));
100 assert!(krate.cfg.contains(&"other=value".to_string()));
101 }
102
103 #[test]
104 fn test_crate_deserialization_old_json() {
105 let raw_json = json!( {
106 "crate_id": 2,
107 "root_module": "this/is/a/file/path.rs",
108 "deps": [
109 {
110 "crate": 1,
111 "name": "some_dep_crate"
112 },
113 ],
114 "edition": "2015",
115 "atom_cfgs": [
116 "atom_1",
117 "atom_2",
118 ],
119 "key_value_cfgs": {
120 "feature": "feature_1",
121 "feature": "feature_2",
122 "other": "value",
123 },
124 });
125
126 let krate: Crate = serde_json::from_value(raw_json).unwrap();
127
128 assert!(krate.atom_cfgs.contains(&"atom_1".to_string()));
129 assert!(krate.atom_cfgs.contains(&"atom_2".to_string()));
130 assert!(krate.key_value_cfgs.contains_key(&"feature".to_string()));
131 assert_eq!(krate.key_value_cfgs.get("feature"), Some(&"feature_2".to_string()));
132 assert!(krate.key_value_cfgs.contains_key(&"other".to_string()));
133 assert_eq!(krate.key_value_cfgs.get("other"), Some(&"value".to_string()));
134 }
56} 135}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index a2e9f65ef..7ad941279 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -14,7 +14,7 @@ use std::{
14use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
15use ra_cfg::CfgOptions; 15use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; 16use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
17use rustc_hash::FxHashMap; 17use rustc_hash::{FxHashMap, FxHashSet};
18use serde_json::from_reader; 18use serde_json::from_reader;
19 19
20pub use crate::{ 20pub use crate::{
@@ -32,6 +32,12 @@ pub enum ProjectWorkspace {
32 Json { project: JsonProject }, 32 Json { project: JsonProject },
33} 33}
34 34
35impl From<JsonProject> for ProjectWorkspace {
36 fn from(project: JsonProject) -> ProjectWorkspace {
37 ProjectWorkspace::Json { project }
38 }
39}
40
35/// `PackageRoot` describes a package root folder. 41/// `PackageRoot` describes a package root folder.
36/// Which may be an external dependency, or a member of 42/// Which may be an external dependency, or a member of
37/// the current workspace. 43/// the current workspace.
@@ -57,25 +63,25 @@ impl PackageRoot {
57 } 63 }
58} 64}
59 65
60#[derive(Debug, Clone, PartialEq, Eq, Hash)] 66#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
61pub enum ProjectRoot { 67pub enum ProjectManifest {
62 ProjectJson(PathBuf), 68 ProjectJson(PathBuf),
63 CargoToml(PathBuf), 69 CargoToml(PathBuf),
64} 70}
65 71
66impl ProjectRoot { 72impl ProjectManifest {
67 pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> { 73 pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> {
68 if path.ends_with("rust-project.json") { 74 if path.ends_with("rust-project.json") {
69 return Ok(ProjectRoot::ProjectJson(path)); 75 return Ok(ProjectManifest::ProjectJson(path));
70 } 76 }
71 if path.ends_with("Cargo.toml") { 77 if path.ends_with("Cargo.toml") {
72 return Ok(ProjectRoot::CargoToml(path)); 78 return Ok(ProjectManifest::CargoToml(path));
73 } 79 }
74 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) 80 bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
75 } 81 }
76 82
77 pub fn discover_single(path: &Path) -> Result<ProjectRoot> { 83 pub fn discover_single(path: &Path) -> Result<ProjectManifest> {
78 let mut candidates = ProjectRoot::discover(path)?; 84 let mut candidates = ProjectManifest::discover(path)?;
79 let res = match candidates.pop() { 85 let res = match candidates.pop() {
80 None => bail!("no projects"), 86 None => bail!("no projects"),
81 Some(it) => it, 87 Some(it) => it,
@@ -87,12 +93,12 @@ impl ProjectRoot {
87 Ok(res) 93 Ok(res)
88 } 94 }
89 95
90 pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { 96 pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> {
91 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { 97 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
92 return Ok(vec![ProjectRoot::ProjectJson(project_json)]); 98 return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
93 } 99 }
94 return find_cargo_toml(path) 100 return find_cargo_toml(path)
95 .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); 101 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
96 102
97 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { 103 fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
98 match find_in_parent_dirs(path, "Cargo.toml") { 104 match find_in_parent_dirs(path, "Cargo.toml") {
@@ -128,16 +134,28 @@ impl ProjectRoot {
128 .collect() 134 .collect()
129 } 135 }
130 } 136 }
137
138 pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> {
139 let mut res = paths
140 .iter()
141 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
142 .flatten()
143 .collect::<FxHashSet<_>>()
144 .into_iter()
145 .collect::<Vec<_>>();
146 res.sort();
147 res
148 }
131} 149}
132 150
133impl ProjectWorkspace { 151impl ProjectWorkspace {
134 pub fn load( 152 pub fn load(
135 root: ProjectRoot, 153 manifest: ProjectManifest,
136 cargo_features: &CargoConfig, 154 cargo_features: &CargoConfig,
137 with_sysroot: bool, 155 with_sysroot: bool,
138 ) -> Result<ProjectWorkspace> { 156 ) -> Result<ProjectWorkspace> {
139 let res = match root { 157 let res = match manifest {
140 ProjectRoot::ProjectJson(project_json) => { 158 ProjectManifest::ProjectJson(project_json) => {
141 let file = File::open(&project_json).with_context(|| { 159 let file = File::open(&project_json).with_context(|| {
142 format!("Failed to open json file {}", project_json.display()) 160 format!("Failed to open json file {}", project_json.display())
143 })?; 161 })?;
@@ -148,7 +166,7 @@ impl ProjectWorkspace {
148 })?, 166 })?,
149 } 167 }
150 } 168 }
151 ProjectRoot::CargoToml(cargo_toml) => { 169 ProjectManifest::CargoToml(cargo_toml) => {
152 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) 170 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
153 .with_context(|| { 171 .with_context(|| {
154 format!( 172 format!(
@@ -252,6 +270,16 @@ impl ProjectWorkspace {
252 }; 270 };
253 let cfg_options = { 271 let cfg_options = {
254 let mut opts = default_cfg_options.clone(); 272 let mut opts = default_cfg_options.clone();
273 for cfg in &krate.cfg {
274 match cfg.find('=') {
275 None => opts.insert_atom(cfg.into()),
276 Some(pos) => {
277 let key = &cfg[..pos];
278 let value = cfg[pos + 1..].trim_matches('"');
279 opts.insert_key_value(key.into(), value.into());
280 }
281 }
282 }
255 for name in &krate.atom_cfgs { 283 for name in &krate.atom_cfgs {
256 opts.insert_atom(name.into()); 284 opts.insert_atom(name.into());
257 } 285 }
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs
index bfc05e08b..a8f2454fd 100644
--- a/crates/ra_syntax/src/ast/traits.rs
+++ b/crates/ra_syntax/src/ast/traits.rs
@@ -83,13 +83,22 @@ pub trait DocCommentsOwner: AstNode {
83 CommentIter { iter: self.syntax().children_with_tokens() } 83 CommentIter { iter: self.syntax().children_with_tokens() }
84 } 84 }
85 85
86 fn doc_comment_text(&self) -> Option<String> {
87 self.doc_comments().doc_comment_text()
88 }
89}
90
91impl CommentIter {
92 pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter {
93 CommentIter { iter: syntax_node.children_with_tokens() }
94 }
95
86 /// Returns the textual content of a doc comment block as a single string. 96 /// Returns the textual content of a doc comment block as a single string.
87 /// That is, strips leading `///` (+ optional 1 character of whitespace), 97 /// That is, strips leading `///` (+ optional 1 character of whitespace),
88 /// trailing `*/`, trailing whitespace and then joins the lines. 98 /// trailing `*/`, trailing whitespace and then joins the lines.
89 fn doc_comment_text(&self) -> Option<String> { 99 pub fn doc_comment_text(self) -> Option<String> {
90 let mut has_comments = false; 100 let mut has_comments = false;
91 let docs = self 101 let docs = self
92 .doc_comments()
93 .filter(|comment| comment.kind().doc.is_some()) 102 .filter(|comment| comment.kind().doc.is_some())
94 .map(|comment| { 103 .map(|comment| {
95 has_comments = true; 104 has_comments = true;
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index e82fd57de..8d071ab1c 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -4,9 +4,14 @@
4mod args; 4mod args;
5 5
6use lsp_server::Connection; 6use lsp_server::Connection;
7use rust_analyzer::{cli, config::Config, from_json, Result}; 7use rust_analyzer::{
8 cli,
9 config::{Config, LinkedProject},
10 from_json, Result,
11};
8 12
9use crate::args::HelpPrinted; 13use crate::args::HelpPrinted;
14use ra_project_model::ProjectManifest;
10 15
11fn main() -> Result<()> { 16fn main() -> Result<()> {
12 setup_logging()?; 17 setup_logging()?;
@@ -97,17 +102,6 @@ fn run_server() -> Result<()> {
97 log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); 102 log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
98 } 103 }
99 104
100 let cwd = std::env::current_dir()?;
101 let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
102
103 let workspace_roots = initialize_params
104 .workspace_folders
105 .map(|workspaces| {
106 workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::<Vec<_>>()
107 })
108 .filter(|workspaces| !workspaces.is_empty())
109 .unwrap_or_else(|| vec![root]);
110
111 let config = { 105 let config = {
112 let mut config = Config::default(); 106 let mut config = Config::default();
113 if let Some(value) = &initialize_params.initialization_options { 107 if let Some(value) = &initialize_params.initialization_options {
@@ -115,10 +109,31 @@ fn run_server() -> Result<()> {
115 } 109 }
116 config.update_caps(&initialize_params.capabilities); 110 config.update_caps(&initialize_params.capabilities);
117 111
112 if config.linked_projects.is_empty() {
113 let cwd = std::env::current_dir()?;
114 let root =
115 initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
116 let workspace_roots = initialize_params
117 .workspace_folders
118 .map(|workspaces| {
119 workspaces
120 .into_iter()
121 .filter_map(|it| it.uri.to_file_path().ok())
122 .collect::<Vec<_>>()
123 })
124 .filter(|workspaces| !workspaces.is_empty())
125 .unwrap_or_else(|| vec![root]);
126
127 config.linked_projects = ProjectManifest::discover_all(&workspace_roots)
128 .into_iter()
129 .map(LinkedProject::from)
130 .collect();
131 }
132
118 config 133 config
119 }; 134 };
120 135
121 rust_analyzer::main_loop(workspace_roots, config, connection)?; 136 rust_analyzer::main_loop(config, connection)?;
122 137
123 log::info!("shutting down IO..."); 138 log::info!("shutting down IO...");
124 io_threads.join()?; 139 io_threads.join()?;
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 008518a08..44f856f6b 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -4,7 +4,7 @@ use ra_cfg::CfgExpr;
4use ra_ide::{FileId, RunnableKind, TestId}; 4use ra_ide::{FileId, RunnableKind, TestId};
5use ra_project_model::{self, ProjectWorkspace, TargetKind}; 5use ra_project_model::{self, ProjectWorkspace, TargetKind};
6 6
7use crate::{world::WorldSnapshot, Result}; 7use crate::{global_state::GlobalStateSnapshot, Result};
8 8
9/// Abstract representation of Cargo target. 9/// Abstract representation of Cargo target.
10/// 10///
@@ -89,7 +89,7 @@ impl CargoTargetSpec {
89 } 89 }
90 90
91 pub(crate) fn for_file( 91 pub(crate) fn for_file(
92 world: &WorldSnapshot, 92 world: &GlobalStateSnapshot,
93 file_id: FileId, 93 file_id: FileId,
94 ) -> Result<Option<CargoTargetSpec>> { 94 ) -> Result<Option<CargoTargetSpec>> {
95 let &crate_id = match world.analysis().crate_for(file_id)?.first() { 95 let &crate_id = match world.analysis().crate_for(file_id)?.first() {
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 8eaf75ff6..c7e86fe0c 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -8,7 +8,8 @@ use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{ExternSourceId, FileId, SourceRootId}; 8use ra_db::{ExternSourceId, FileId, SourceRootId};
9use ra_ide::{AnalysisChange, AnalysisHost}; 9use ra_ide::{AnalysisChange, AnalysisHost};
10use ra_project_model::{ 10use ra_project_model::{
11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, 11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest,
12 ProjectWorkspace,
12}; 13};
13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; 14use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
14use rustc_hash::{FxHashMap, FxHashSet}; 15use rustc_hash::{FxHashMap, FxHashSet};
@@ -28,7 +29,7 @@ pub fn load_cargo(
28 with_proc_macro: bool, 29 with_proc_macro: bool,
29) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { 30) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
30 let root = std::env::current_dir()?.join(root); 31 let root = std::env::current_dir()?.join(root);
31 let root = ProjectRoot::discover_single(&root)?; 32 let root = ProjectManifest::discover_single(&root)?;
32 let ws = ProjectWorkspace::load( 33 let ws = ProjectWorkspace::load(
33 root, 34 root,
34 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 35 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 3337078ac..23168c3ae 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -12,14 +12,13 @@ use std::{ffi::OsString, path::PathBuf};
12use lsp_types::ClientCapabilities; 12use lsp_types::ClientCapabilities;
13use ra_flycheck::FlycheckConfig; 13use ra_flycheck::FlycheckConfig;
14use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; 14use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
15use ra_project_model::CargoConfig; 15use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
16use serde::Deserialize; 16use serde::Deserialize;
17 17
18#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
19pub struct Config { 19pub struct Config {
20 pub client_caps: ClientCapsConfig, 20 pub client_caps: ClientCapsConfig,
21 21
22 pub with_sysroot: bool,
23 pub publish_diagnostics: bool, 22 pub publish_diagnostics: bool,
24 pub lru_capacity: Option<usize>, 23 pub lru_capacity: Option<usize>,
25 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>, 24 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
@@ -35,6 +34,27 @@ pub struct Config {
35 pub assist: AssistConfig, 34 pub assist: AssistConfig,
36 pub call_info_full: bool, 35 pub call_info_full: bool,
37 pub lens: LensConfig, 36 pub lens: LensConfig,
37
38 pub with_sysroot: bool,
39 pub linked_projects: Vec<LinkedProject>,
40}
41
42#[derive(Debug, Clone)]
43pub enum LinkedProject {
44 ProjectManifest(ProjectManifest),
45 JsonProject(JsonProject),
46}
47
48impl From<ProjectManifest> for LinkedProject {
49 fn from(v: ProjectManifest) -> Self {
50 LinkedProject::ProjectManifest(v)
51 }
52}
53
54impl From<JsonProject> for LinkedProject {
55 fn from(v: JsonProject) -> Self {
56 LinkedProject::JsonProject(v)
57 }
38} 58}
39 59
40#[derive(Clone, Debug, PartialEq, Eq)] 60#[derive(Clone, Debug, PartialEq, Eq)]
@@ -142,6 +162,7 @@ impl Default for Config {
142 assist: AssistConfig::default(), 162 assist: AssistConfig::default(),
143 call_info_full: true, 163 call_info_full: true,
144 lens: LensConfig::default(), 164 lens: LensConfig::default(),
165 linked_projects: Vec::new(),
145 } 166 }
146 } 167 }
147} 168}
@@ -241,6 +262,22 @@ impl Config {
241 self.lens = LensConfig::NO_LENS; 262 self.lens = LensConfig::NO_LENS;
242 } 263 }
243 264
265 if let Some(linked_projects) = get::<Vec<ManifestOrJsonProject>>(value, "/linkedProjects") {
266 if !linked_projects.is_empty() {
267 self.linked_projects.clear();
268 for linked_project in linked_projects {
269 let linked_project = match linked_project {
270 ManifestOrJsonProject::Manifest(it) => match ProjectManifest::from_manifest_file(it) {
271 Ok(it) => it.into(),
272 Err(_) => continue,
273 }
274 ManifestOrJsonProject::JsonProject(it) => it.into(),
275 };
276 self.linked_projects.push(linked_project);
277 }
278 }
279 }
280
244 log::info!("Config::update() = {:#?}", self); 281 log::info!("Config::update() = {:#?}", self);
245 282
246 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> { 283 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
@@ -308,3 +345,10 @@ impl Config {
308 } 345 }
309 } 346 }
310} 347}
348
349#[derive(Deserialize)]
350#[serde(untagged)]
351enum ManifestOrJsonProject {
352 Manifest(PathBuf),
353 JsonProject(JsonProject),
354}
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
index 9a7972ff5..f0273315e 100644
--- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap
@@ -29,7 +29,7 @@ expression: diag
29 }, 29 },
30 }, 30 },
31 severity: Some( 31 severity: Some(
32 Warning, 32 Hint,
33 ), 33 ),
34 code: Some( 34 code: Some(
35 String( 35 String(
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 257910e09..04e286780 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -184,7 +184,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
184 return Vec::new(); 184 return Vec::new();
185 } 185 }
186 186
187 let severity = map_level_to_severity(rd.level); 187 let mut severity = map_level_to_severity(rd.level);
188 188
189 let mut source = String::from("rustc"); 189 let mut source = String::from("rustc");
190 let mut code = rd.code.as_ref().map(|c| c.code.clone()); 190 let mut code = rd.code.as_ref().map(|c| c.code.clone());
@@ -226,6 +226,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
226 } 226 }
227 227
228 if is_unused_or_unnecessary(rd) { 228 if is_unused_or_unnecessary(rd) {
229 severity = Some(DiagnosticSeverity::Hint);
229 tags.push(DiagnosticTag::Unnecessary); 230 tags.push(DiagnosticTag::Unnecessary);
230 } 231 }
231 232
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index 4bb16a496..206673829 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -3,7 +3,7 @@ use ra_db::{FileId, FilePosition, FileRange};
3use ra_ide::{LineCol, LineIndex}; 3use ra_ide::{LineCol, LineIndex};
4use ra_syntax::{TextRange, TextSize}; 4use ra_syntax::{TextRange, TextSize};
5 5
6use crate::{world::WorldSnapshot, Result}; 6use crate::{global_state::GlobalStateSnapshot, Result};
7 7
8pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { 8pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize {
9 let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; 9 let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 };
@@ -16,12 +16,12 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex
16 TextRange::new(start, end) 16 TextRange::new(start, end)
17} 17}
18 18
19pub(crate) fn file_id(world: &WorldSnapshot, url: &lsp_types::Url) -> Result<FileId> { 19pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
20 world.uri_to_file_id(url) 20 world.uri_to_file_id(url)
21} 21}
22 22
23pub(crate) fn file_position( 23pub(crate) fn file_position(
24 world: &WorldSnapshot, 24 world: &GlobalStateSnapshot,
25 tdpp: lsp_types::TextDocumentPositionParams, 25 tdpp: lsp_types::TextDocumentPositionParams,
26) -> Result<FilePosition> { 26) -> Result<FilePosition> {
27 let file_id = file_id(world, &tdpp.text_document.uri)?; 27 let file_id = file_id(world, &tdpp.text_document.uri)?;
@@ -31,7 +31,7 @@ pub(crate) fn file_position(
31} 31}
32 32
33pub(crate) fn file_range( 33pub(crate) fn file_range(
34 world: &WorldSnapshot, 34 world: &GlobalStateSnapshot,
35 text_document_identifier: lsp_types::TextDocumentIdentifier, 35 text_document_identifier: lsp_types::TextDocumentIdentifier,
36 range: lsp_types::Range, 36 range: lsp_types::Range,
37) -> Result<FileRange> { 37) -> Result<FileRange> {
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/global_state.rs
index 367272925..0bebb5bf6 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -50,15 +50,15 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) ->
50 }) 50 })
51} 51}
52 52
53/// `WorldState` is the primary mutable state of the language server 53/// `GlobalState` is the primary mutable state of the language server
54/// 54///
55/// The most interesting components are `vfs`, which stores a consistent 55/// The most interesting components are `vfs`, which stores a consistent
56/// snapshot of the file systems, and `analysis_host`, which stores our 56/// snapshot of the file systems, and `analysis_host`, which stores our
57/// incremental salsa database. 57/// incremental salsa database.
58#[derive(Debug)] 58#[derive(Debug)]
59pub struct WorldState { 59pub struct GlobalState {
60 pub config: Config, 60 pub config: Config,
61 pub roots: Vec<PathBuf>, 61 pub local_roots: Vec<PathBuf>,
62 pub workspaces: Arc<Vec<ProjectWorkspace>>, 62 pub workspaces: Arc<Vec<ProjectWorkspace>>,
63 pub analysis_host: AnalysisHost, 63 pub analysis_host: AnalysisHost,
64 pub vfs: Arc<RwLock<Vfs>>, 64 pub vfs: Arc<RwLock<Vfs>>,
@@ -70,7 +70,7 @@ pub struct WorldState {
70} 70}
71 71
72/// An immutable snapshot of the world's state at a point in time. 72/// An immutable snapshot of the world's state at a point in time.
73pub struct WorldSnapshot { 73pub struct GlobalStateSnapshot {
74 pub config: Config, 74 pub config: Config,
75 pub workspaces: Arc<Vec<ProjectWorkspace>>, 75 pub workspaces: Arc<Vec<ProjectWorkspace>>,
76 pub analysis: Analysis, 76 pub analysis: Analysis,
@@ -79,20 +79,20 @@ pub struct WorldSnapshot {
79 vfs: Arc<RwLock<Vfs>>, 79 vfs: Arc<RwLock<Vfs>>,
80} 80}
81 81
82impl WorldState { 82impl GlobalState {
83 pub fn new( 83 pub fn new(
84 folder_roots: Vec<PathBuf>,
85 workspaces: Vec<ProjectWorkspace>, 84 workspaces: Vec<ProjectWorkspace>,
86 lru_capacity: Option<usize>, 85 lru_capacity: Option<usize>,
87 exclude_globs: &[Glob], 86 exclude_globs: &[Glob],
88 watch: Watch, 87 watch: Watch,
89 config: Config, 88 config: Config,
90 ) -> WorldState { 89 ) -> GlobalState {
91 let mut change = AnalysisChange::new(); 90 let mut change = AnalysisChange::new();
92 91
93 let extern_dirs: FxHashSet<_> = 92 let extern_dirs: FxHashSet<_> =
94 workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); 93 workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect();
95 94
95 let mut local_roots = Vec::new();
96 let roots: Vec<_> = { 96 let roots: Vec<_> = {
97 let create_filter = |is_member| { 97 let create_filter = |is_member| {
98 RustPackageFilterBuilder::default() 98 RustPackageFilterBuilder::default()
@@ -100,12 +100,16 @@ impl WorldState {
100 .exclude(exclude_globs.iter().cloned()) 100 .exclude(exclude_globs.iter().cloned())
101 .into_vfs_filter() 101 .into_vfs_filter()
102 }; 102 };
103 folder_roots 103 workspaces
104 .iter() 104 .iter()
105 .map(|path| RootEntry::new(path.clone(), create_filter(true))) 105 .flat_map(ProjectWorkspace::to_roots)
106 .chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| { 106 .map(|pkg_root| {
107 RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member())) 107 let path = pkg_root.path().to_owned();
108 })) 108 if pkg_root.is_member() {
109 local_roots.push(path.clone());
110 }
111 RootEntry::new(path, create_filter(pkg_root.is_member()))
112 })
109 .chain( 113 .chain(
110 extern_dirs 114 extern_dirs
111 .iter() 115 .iter()
@@ -121,7 +125,7 @@ impl WorldState {
121 let mut extern_source_roots = FxHashMap::default(); 125 let mut extern_source_roots = FxHashMap::default();
122 for r in vfs_roots { 126 for r in vfs_roots {
123 let vfs_root_path = vfs.root2path(r); 127 let vfs_root_path = vfs.root2path(r);
124 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); 128 let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it));
125 change.add_root(SourceRootId(r.0), is_local); 129 change.add_root(SourceRootId(r.0), is_local);
126 change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); 130 change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string());
127 131
@@ -176,9 +180,9 @@ impl WorldState {
176 180
177 let mut analysis_host = AnalysisHost::new(lru_capacity); 181 let mut analysis_host = AnalysisHost::new(lru_capacity);
178 analysis_host.apply_change(change); 182 analysis_host.apply_change(change);
179 WorldState { 183 GlobalState {
180 config, 184 config,
181 roots: folder_roots, 185 local_roots,
182 workspaces: Arc::new(workspaces), 186 workspaces: Arc::new(workspaces),
183 analysis_host, 187 analysis_host,
184 vfs: Arc::new(RwLock::new(vfs)), 188 vfs: Arc::new(RwLock::new(vfs)),
@@ -216,7 +220,7 @@ impl WorldState {
216 match c { 220 match c {
217 VfsChange::AddRoot { root, files } => { 221 VfsChange::AddRoot { root, files } => {
218 let root_path = self.vfs.read().root2path(root); 222 let root_path = self.vfs.read().root2path(root);
219 let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); 223 let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r));
220 if is_local { 224 if is_local {
221 *roots_scanned += 1; 225 *roots_scanned += 1;
222 for (file, path, text) in files { 226 for (file, path, text) in files {
@@ -251,8 +255,8 @@ impl WorldState {
251 self.analysis_host.apply_change(change); 255 self.analysis_host.apply_change(change);
252 } 256 }
253 257
254 pub fn snapshot(&self) -> WorldSnapshot { 258 pub fn snapshot(&self) -> GlobalStateSnapshot {
255 WorldSnapshot { 259 GlobalStateSnapshot {
256 config: self.config.clone(), 260 config: self.config.clone(),
257 workspaces: Arc::clone(&self.workspaces), 261 workspaces: Arc::clone(&self.workspaces),
258 analysis: self.analysis_host.analysis(), 262 analysis: self.analysis_host.analysis(),
@@ -275,7 +279,7 @@ impl WorldState {
275 } 279 }
276} 280}
277 281
278impl WorldSnapshot { 282impl GlobalStateSnapshot {
279 pub fn analysis(&self) -> &Analysis { 283 pub fn analysis(&self) -> &Analysis {
280 &self.analysis 284 &self.analysis
281 } 285 }
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 57d0e9218..609cb69d3 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -26,7 +26,7 @@ mod main_loop;
26mod markdown; 26mod markdown;
27pub mod lsp_ext; 27pub mod lsp_ext;
28pub mod config; 28pub mod config;
29mod world; 29mod global_state;
30mod diagnostics; 30mod diagnostics;
31mod semantic_tokens; 31mod semantic_tokens;
32 32
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index ad9dd4c59..e60337b8e 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -12,13 +12,11 @@ use std::{
12 fmt, 12 fmt,
13 ops::Range, 13 ops::Range,
14 panic, 14 panic,
15 path::PathBuf,
16 sync::Arc, 15 sync::Arc,
17 time::{Duration, Instant}, 16 time::{Duration, Instant},
18}; 17};
19 18
20use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; 19use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
21use itertools::Itertools;
22use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 20use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
23use lsp_types::{ 21use lsp_types::{
24 DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, 22 DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
@@ -36,14 +34,15 @@ use serde::{de::DeserializeOwned, Serialize};
36use threadpool::ThreadPool; 34use threadpool::ThreadPool;
37 35
38use crate::{ 36use crate::{
39 config::{Config, FilesWatcher}, 37 config::{Config, FilesWatcher, LinkedProject},
40 diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, 38 diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
41 from_proto, lsp_ext, 39 from_proto,
40 global_state::{GlobalState, GlobalStateSnapshot},
41 lsp_ext,
42 main_loop::{ 42 main_loop::{
43 pending_requests::{PendingRequest, PendingRequests}, 43 pending_requests::{PendingRequest, PendingRequests},
44 subscriptions::Subscriptions, 44 subscriptions::Subscriptions,
45 }, 45 },
46 world::{WorldSnapshot, WorldState},
47 Result, 46 Result,
48}; 47};
49 48
@@ -69,7 +68,7 @@ impl fmt::Display for LspError {
69 68
70impl Error for LspError {} 69impl Error for LspError {}
71 70
72pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> { 71pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
73 log::info!("initial config: {:#?}", config); 72 log::info!("initial config: {:#?}", config);
74 73
75 // Windows scheduler implements priority boosts: if thread waits for an 74 // Windows scheduler implements priority boosts: if thread waits for an
@@ -92,43 +91,37 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
92 } 91 }
93 92
94 let mut loop_state = LoopState::default(); 93 let mut loop_state = LoopState::default();
95 let mut world_state = { 94 let mut global_state = {
96 let workspaces = { 95 let workspaces = {
97 // FIXME: support dynamic workspace loading. 96 if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found {
98 let project_roots: FxHashSet<_> = ws_roots
99 .iter()
100 .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
101 .flatten()
102 .collect();
103
104 if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
105 show_message( 97 show_message(
106 lsp_types::MessageType::Error, 98 lsp_types::MessageType::Error,
107 format!( 99 "rust-analyzer failed to discover workspace".to_string(),
108 "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
109 ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
110 ),
111 &connection.sender, 100 &connection.sender,
112 ); 101 );
113 }; 102 };
114 103
115 project_roots 104 config
116 .into_iter() 105 .linked_projects
117 .filter_map(|root| { 106 .iter()
118 ra_project_model::ProjectWorkspace::load( 107 .filter_map(|project| match project {
119 root, 108 LinkedProject::ProjectManifest(manifest) => {
120 &config.cargo, 109 ra_project_model::ProjectWorkspace::load(
121 config.with_sysroot, 110 manifest.clone(),
122 ) 111 &config.cargo,
123 .map_err(|err| { 112 config.with_sysroot,
124 log::error!("failed to load workspace: {:#}", err); 113 )
125 show_message( 114 .map_err(|err| {
126 lsp_types::MessageType::Error, 115 log::error!("failed to load workspace: {:#}", err);
127 format!("rust-analyzer failed to load workspace: {:#}", err), 116 show_message(
128 &connection.sender, 117 lsp_types::MessageType::Error,
129 ); 118 format!("rust-analyzer failed to load workspace: {:#}", err),
130 }) 119 &connection.sender,
131 .ok() 120 );
121 })
122 .ok()
123 }
124 LinkedProject::JsonProject(it) => Some(it.clone().into()),
132 }) 125 })
133 .collect::<Vec<_>>() 126 .collect::<Vec<_>>()
134 }; 127 };
@@ -163,8 +156,7 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
163 connection.sender.send(request.into()).unwrap(); 156 connection.sender.send(request.into()).unwrap();
164 } 157 }
165 158
166 WorldState::new( 159 GlobalState::new(
167 ws_roots,
168 workspaces, 160 workspaces,
169 config.lru_capacity, 161 config.lru_capacity,
170 &globs, 162 &globs,
@@ -173,7 +165,7 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
173 ) 165 )
174 }; 166 };
175 167
176 loop_state.roots_total = world_state.vfs.read().n_roots(); 168 loop_state.roots_total = global_state.vfs.read().n_roots();
177 169
178 let pool = ThreadPool::default(); 170 let pool = ThreadPool::default();
179 let (task_sender, task_receiver) = unbounded::<Task>(); 171 let (task_sender, task_receiver) = unbounded::<Task>();
@@ -191,12 +183,12 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
191 Err(RecvError) => return Err("client exited without shutdown".into()), 183 Err(RecvError) => return Err("client exited without shutdown".into()),
192 }, 184 },
193 recv(task_receiver) -> task => Event::Task(task.unwrap()), 185 recv(task_receiver) -> task => Event::Task(task.unwrap()),
194 recv(world_state.task_receiver) -> task => match task { 186 recv(global_state.task_receiver) -> task => match task {
195 Ok(task) => Event::Vfs(task), 187 Ok(task) => Event::Vfs(task),
196 Err(RecvError) => return Err("vfs died".into()), 188 Err(RecvError) => return Err("vfs died".into()),
197 }, 189 },
198 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), 190 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
199 recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { 191 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
200 Ok(task) => Event::CheckWatcher(task), 192 Ok(task) => Event::CheckWatcher(task),
201 Err(RecvError) => return Err("check watcher died".into()), 193 Err(RecvError) => return Err("check watcher died".into()),
202 } 194 }
@@ -211,16 +203,16 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
211 &task_sender, 203 &task_sender,
212 &libdata_sender, 204 &libdata_sender,
213 &connection, 205 &connection,
214 &mut world_state, 206 &mut global_state,
215 &mut loop_state, 207 &mut loop_state,
216 event, 208 event,
217 )?; 209 )?;
218 } 210 }
219 } 211 }
220 world_state.analysis_host.request_cancellation(); 212 global_state.analysis_host.request_cancellation();
221 log::info!("waiting for tasks to finish..."); 213 log::info!("waiting for tasks to finish...");
222 task_receiver.into_iter().for_each(|task| { 214 task_receiver.into_iter().for_each(|task| {
223 on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) 215 on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state)
224 }); 216 });
225 libdata_receiver.into_iter().for_each(drop); 217 libdata_receiver.into_iter().for_each(drop);
226 log::info!("...tasks have finished"); 218 log::info!("...tasks have finished");
@@ -229,7 +221,7 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
229 drop(pool); 221 drop(pool);
230 log::info!("...threadpool has finished"); 222 log::info!("...threadpool has finished");
231 223
232 let vfs = Arc::try_unwrap(world_state.vfs).expect("all snapshots should be dead"); 224 let vfs = Arc::try_unwrap(global_state.vfs).expect("all snapshots should be dead");
233 drop(vfs); 225 drop(vfs);
234 226
235 Ok(()) 227 Ok(())
@@ -320,7 +312,7 @@ fn loop_turn(
320 task_sender: &Sender<Task>, 312 task_sender: &Sender<Task>,
321 libdata_sender: &Sender<LibraryData>, 313 libdata_sender: &Sender<LibraryData>,
322 connection: &Connection, 314 connection: &Connection,
323 world_state: &mut WorldState, 315 global_state: &mut GlobalState,
324 loop_state: &mut LoopState, 316 loop_state: &mut LoopState,
325 event: Event, 317 event: Event,
326) -> Result<()> { 318) -> Result<()> {
@@ -336,22 +328,22 @@ fn loop_turn(
336 328
337 match event { 329 match event {
338 Event::Task(task) => { 330 Event::Task(task) => {
339 on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state); 331 on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state);
340 world_state.maybe_collect_garbage(); 332 global_state.maybe_collect_garbage();
341 } 333 }
342 Event::Vfs(task) => { 334 Event::Vfs(task) => {
343 world_state.vfs.write().handle_task(task); 335 global_state.vfs.write().handle_task(task);
344 } 336 }
345 Event::Lib(lib) => { 337 Event::Lib(lib) => {
346 world_state.add_lib(lib); 338 global_state.add_lib(lib);
347 world_state.maybe_collect_garbage(); 339 global_state.maybe_collect_garbage();
348 loop_state.in_flight_libraries -= 1; 340 loop_state.in_flight_libraries -= 1;
349 loop_state.roots_scanned += 1; 341 loop_state.roots_scanned += 1;
350 } 342 }
351 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, 343 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
352 Event::Msg(msg) => match msg { 344 Event::Msg(msg) => match msg {
353 Message::Request(req) => on_request( 345 Message::Request(req) => on_request(
354 world_state, 346 global_state,
355 &mut loop_state.pending_requests, 347 &mut loop_state.pending_requests,
356 pool, 348 pool,
357 task_sender, 349 task_sender,
@@ -360,7 +352,7 @@ fn loop_turn(
360 req, 352 req,
361 )?, 353 )?,
362 Message::Notification(not) => { 354 Message::Notification(not) => {
363 on_notification(&connection.sender, world_state, loop_state, not)?; 355 on_notification(&connection.sender, global_state, loop_state, not)?;
364 } 356 }
365 Message::Response(resp) => { 357 Message::Response(resp) => {
366 let removed = loop_state.pending_responses.remove(&resp.id); 358 let removed = loop_state.pending_responses.remove(&resp.id);
@@ -379,9 +371,9 @@ fn loop_turn(
379 } 371 }
380 (None, Some(configs)) => { 372 (None, Some(configs)) => {
381 if let Some(new_config) = configs.get(0) { 373 if let Some(new_config) = configs.get(0) {
382 let mut config = world_state.config.clone(); 374 let mut config = global_state.config.clone();
383 config.update(&new_config); 375 config.update(&new_config);
384 world_state.update_configuration(config); 376 global_state.update_configuration(config);
385 } 377 }
386 } 378 }
387 (None, None) => { 379 (None, None) => {
@@ -394,7 +386,7 @@ fn loop_turn(
394 }; 386 };
395 387
396 let mut state_changed = false; 388 let mut state_changed = false;
397 if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) { 389 if let Some(changes) = global_state.process_changes(&mut loop_state.roots_scanned) {
398 state_changed = true; 390 state_changed = true;
399 loop_state.pending_libraries.extend(changes); 391 loop_state.pending_libraries.extend(changes);
400 } 392 }
@@ -416,7 +408,7 @@ fn loop_turn(
416 } 408 }
417 409
418 let show_progress = 410 let show_progress =
419 !loop_state.workspace_loaded && world_state.config.client_caps.work_done_progress; 411 !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress;
420 412
421 if !loop_state.workspace_loaded 413 if !loop_state.workspace_loaded
422 && loop_state.roots_scanned == loop_state.roots_total 414 && loop_state.roots_scanned == loop_state.roots_total
@@ -425,7 +417,7 @@ fn loop_turn(
425 { 417 {
426 state_changed = true; 418 state_changed = true;
427 loop_state.workspace_loaded = true; 419 loop_state.workspace_loaded = true;
428 if let Some(flycheck) = &world_state.flycheck { 420 if let Some(flycheck) = &global_state.flycheck {
429 flycheck.update(); 421 flycheck.update();
430 } 422 }
431 } 423 }
@@ -437,13 +429,13 @@ fn loop_turn(
437 if state_changed && loop_state.workspace_loaded { 429 if state_changed && loop_state.workspace_loaded {
438 update_file_notifications_on_threadpool( 430 update_file_notifications_on_threadpool(
439 pool, 431 pool,
440 world_state.snapshot(), 432 global_state.snapshot(),
441 task_sender.clone(), 433 task_sender.clone(),
442 loop_state.subscriptions.subscriptions(), 434 loop_state.subscriptions.subscriptions(),
443 ); 435 );
444 pool.execute({ 436 pool.execute({
445 let subs = loop_state.subscriptions.subscriptions(); 437 let subs = loop_state.subscriptions.subscriptions();
446 let snap = world_state.snapshot(); 438 let snap = global_state.snapshot();
447 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) 439 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
448 }); 440 });
449 } 441 }
@@ -467,7 +459,7 @@ fn on_task(
467 task: Task, 459 task: Task,
468 msg_sender: &Sender<Message>, 460 msg_sender: &Sender<Message>,
469 pending_requests: &mut PendingRequests, 461 pending_requests: &mut PendingRequests,
470 state: &mut WorldState, 462 state: &mut GlobalState,
471) { 463) {
472 match task { 464 match task {
473 Task::Respond(response) => { 465 Task::Respond(response) => {
@@ -485,7 +477,7 @@ fn on_task(
485} 477}
486 478
487fn on_request( 479fn on_request(
488 world: &mut WorldState, 480 global_state: &mut GlobalState,
489 pending_requests: &mut PendingRequests, 481 pending_requests: &mut PendingRequests,
490 pool: &ThreadPool, 482 pool: &ThreadPool,
491 task_sender: &Sender<Task>, 483 task_sender: &Sender<Task>,
@@ -496,7 +488,7 @@ fn on_request(
496 let mut pool_dispatcher = PoolDispatcher { 488 let mut pool_dispatcher = PoolDispatcher {
497 req: Some(req), 489 req: Some(req),
498 pool, 490 pool,
499 world, 491 global_state,
500 task_sender, 492 task_sender,
501 msg_sender, 493 msg_sender,
502 pending_requests, 494 pending_requests,
@@ -553,7 +545,7 @@ fn on_request(
553 545
554fn on_notification( 546fn on_notification(
555 msg_sender: &Sender<Message>, 547 msg_sender: &Sender<Message>,
556 state: &mut WorldState, 548 state: &mut GlobalState,
557 loop_state: &mut LoopState, 549 loop_state: &mut LoopState,
558 not: Notification, 550 not: Notification,
559) -> Result<()> { 551) -> Result<()> {
@@ -727,7 +719,7 @@ fn apply_document_changes(
727 719
728fn on_check_task( 720fn on_check_task(
729 task: CheckTask, 721 task: CheckTask,
730 world_state: &mut WorldState, 722 global_state: &mut GlobalState,
731 task_sender: &Sender<Task>, 723 task_sender: &Sender<Task>,
732) -> Result<()> { 724) -> Result<()> {
733 match task { 725 match task {
@@ -746,7 +738,7 @@ fn on_check_task(
746 .uri 738 .uri
747 .to_file_path() 739 .to_file_path()
748 .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; 740 .map_err(|()| format!("invalid uri: {}", diag.location.uri))?;
749 let file_id = match world_state.vfs.read().path2file(&path) { 741 let file_id = match global_state.vfs.read().path2file(&path) {
750 Some(file) => FileId(file.0), 742 Some(file) => FileId(file.0),
751 None => { 743 None => {
752 log::error!( 744 log::error!(
@@ -766,7 +758,7 @@ fn on_check_task(
766 } 758 }
767 759
768 CheckTask::Status(status) => { 760 CheckTask::Status(status) => {
769 if world_state.config.client_caps.work_done_progress { 761 if global_state.config.client_caps.work_done_progress {
770 let progress = match status { 762 let progress = match status {
771 Status::Being => { 763 Status::Being => {
772 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { 764 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
@@ -805,7 +797,7 @@ fn on_check_task(
805 Ok(()) 797 Ok(())
806} 798}
807 799
808fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut WorldState) { 800fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut GlobalState) {
809 let subscriptions = state.diagnostics.handle_task(task); 801 let subscriptions = state.diagnostics.handle_task(task);
810 802
811 for file_id in subscriptions { 803 for file_id in subscriptions {
@@ -880,7 +872,7 @@ fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
880struct PoolDispatcher<'a> { 872struct PoolDispatcher<'a> {
881 req: Option<Request>, 873 req: Option<Request>,
882 pool: &'a ThreadPool, 874 pool: &'a ThreadPool,
883 world: &'a mut WorldState, 875 global_state: &'a mut GlobalState,
884 pending_requests: &'a mut PendingRequests, 876 pending_requests: &'a mut PendingRequests,
885 msg_sender: &'a Sender<Message>, 877 msg_sender: &'a Sender<Message>,
886 task_sender: &'a Sender<Task>, 878 task_sender: &'a Sender<Task>,
@@ -891,7 +883,7 @@ impl<'a> PoolDispatcher<'a> {
891 /// Dispatches the request onto the current thread 883 /// Dispatches the request onto the current thread
892 fn on_sync<R>( 884 fn on_sync<R>(
893 &mut self, 885 &mut self,
894 f: fn(&mut WorldState, R::Params) -> Result<R::Result>, 886 f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
895 ) -> Result<&mut Self> 887 ) -> Result<&mut Self>
896 where 888 where
897 R: lsp_types::request::Request + 'static, 889 R: lsp_types::request::Request + 'static,
@@ -904,18 +896,21 @@ impl<'a> PoolDispatcher<'a> {
904 return Ok(self); 896 return Ok(self);
905 } 897 }
906 }; 898 };
907 let world = panic::AssertUnwindSafe(&mut *self.world); 899 let world = panic::AssertUnwindSafe(&mut *self.global_state);
908 let task = panic::catch_unwind(move || { 900 let task = panic::catch_unwind(move || {
909 let result = f(world.0, params); 901 let result = f(world.0, params);
910 result_to_task::<R>(id, result) 902 result_to_task::<R>(id, result)
911 }) 903 })
912 .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; 904 .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?;
913 on_task(task, self.msg_sender, self.pending_requests, self.world); 905 on_task(task, self.msg_sender, self.pending_requests, self.global_state);
914 Ok(self) 906 Ok(self)
915 } 907 }
916 908
917 /// Dispatches the request onto thread pool 909 /// Dispatches the request onto thread pool
918 fn on<R>(&mut self, f: fn(WorldSnapshot, R::Params) -> Result<R::Result>) -> Result<&mut Self> 910 fn on<R>(
911 &mut self,
912 f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
913 ) -> Result<&mut Self>
919 where 914 where
920 R: lsp_types::request::Request + 'static, 915 R: lsp_types::request::Request + 'static,
921 R::Params: DeserializeOwned + Send + 'static, 916 R::Params: DeserializeOwned + Send + 'static,
@@ -929,7 +924,7 @@ impl<'a> PoolDispatcher<'a> {
929 }; 924 };
930 925
931 self.pool.execute({ 926 self.pool.execute({
932 let world = self.world.snapshot(); 927 let world = self.global_state.snapshot();
933 let sender = self.task_sender.clone(); 928 let sender = self.task_sender.clone();
934 move || { 929 move || {
935 let result = f(world, params); 930 let result = f(world, params);
@@ -1013,7 +1008,7 @@ where
1013 1008
1014fn update_file_notifications_on_threadpool( 1009fn update_file_notifications_on_threadpool(
1015 pool: &ThreadPool, 1010 pool: &ThreadPool,
1016 world: WorldSnapshot, 1011 world: GlobalStateSnapshot,
1017 task_sender: Sender<Task>, 1012 task_sender: Sender<Task>,
1018 subscriptions: Vec<FileId>, 1013 subscriptions: Vec<FileId>,
1019) { 1014) {
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index fab82ff7e..a3361d6dc 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -32,17 +32,16 @@ use crate::{
32 config::RustfmtConfig, 32 config::RustfmtConfig,
33 diagnostics::DiagnosticTask, 33 diagnostics::DiagnosticTask,
34 from_json, from_proto, 34 from_json, from_proto,
35 global_state::GlobalStateSnapshot,
35 lsp_ext::{self, InlayHint, InlayHintsParams}, 36 lsp_ext::{self, InlayHint, InlayHintsParams},
36 to_proto, 37 to_proto, LspError, Result,
37 world::WorldSnapshot,
38 LspError, Result,
39}; 38};
40 39
41pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { 40pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> {
42 let _p = profile("handle_analyzer_status"); 41 let _p = profile("handle_analyzer_status");
43 let mut buf = world.status(); 42 let mut buf = snap.status();
44 format_to!(buf, "\n\nrequests:\n"); 43 format_to!(buf, "\n\nrequests:\n");
45 let requests = world.latest_requests.read(); 44 let requests = snap.latest_requests.read();
46 for (is_last, r) in requests.iter() { 45 for (is_last, r) in requests.iter() {
47 let mark = if is_last { "*" } else { " " }; 46 let mark = if is_last { "*" } else { " " };
48 format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis()); 47 format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis());
@@ -51,37 +50,37 @@ pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
51} 50}
52 51
53pub fn handle_syntax_tree( 52pub fn handle_syntax_tree(
54 world: WorldSnapshot, 53 snap: GlobalStateSnapshot,
55 params: lsp_ext::SyntaxTreeParams, 54 params: lsp_ext::SyntaxTreeParams,
56) -> Result<String> { 55) -> Result<String> {
57 let _p = profile("handle_syntax_tree"); 56 let _p = profile("handle_syntax_tree");
58 let id = from_proto::file_id(&world, &params.text_document.uri)?; 57 let id = from_proto::file_id(&snap, &params.text_document.uri)?;
59 let line_index = world.analysis().file_line_index(id)?; 58 let line_index = snap.analysis().file_line_index(id)?;
60 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r)); 59 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
61 let res = world.analysis().syntax_tree(id, text_range)?; 60 let res = snap.analysis().syntax_tree(id, text_range)?;
62 Ok(res) 61 Ok(res)
63} 62}
64 63
65pub fn handle_expand_macro( 64pub fn handle_expand_macro(
66 world: WorldSnapshot, 65 snap: GlobalStateSnapshot,
67 params: lsp_ext::ExpandMacroParams, 66 params: lsp_ext::ExpandMacroParams,
68) -> Result<Option<lsp_ext::ExpandedMacro>> { 67) -> Result<Option<lsp_ext::ExpandedMacro>> {
69 let _p = profile("handle_expand_macro"); 68 let _p = profile("handle_expand_macro");
70 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 69 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
71 let line_index = world.analysis().file_line_index(file_id)?; 70 let line_index = snap.analysis().file_line_index(file_id)?;
72 let offset = from_proto::offset(&line_index, params.position); 71 let offset = from_proto::offset(&line_index, params.position);
73 72
74 let res = world.analysis().expand_macro(FilePosition { file_id, offset })?; 73 let res = snap.analysis().expand_macro(FilePosition { file_id, offset })?;
75 Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion })) 74 Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion }))
76} 75}
77 76
78pub fn handle_selection_range( 77pub fn handle_selection_range(
79 world: WorldSnapshot, 78 snap: GlobalStateSnapshot,
80 params: lsp_types::SelectionRangeParams, 79 params: lsp_types::SelectionRangeParams,
81) -> Result<Option<Vec<lsp_types::SelectionRange>>> { 80) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
82 let _p = profile("handle_selection_range"); 81 let _p = profile("handle_selection_range");
83 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 82 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
84 let line_index = world.analysis().file_line_index(file_id)?; 83 let line_index = snap.analysis().file_line_index(file_id)?;
85 let res: Result<Vec<lsp_types::SelectionRange>> = params 84 let res: Result<Vec<lsp_types::SelectionRange>> = params
86 .positions 85 .positions
87 .into_iter() 86 .into_iter()
@@ -93,7 +92,7 @@ pub fn handle_selection_range(
93 loop { 92 loop {
94 ranges.push(range); 93 ranges.push(range);
95 let frange = FileRange { file_id, range }; 94 let frange = FileRange { file_id, range };
96 let next = world.analysis().extend_selection(frange)?; 95 let next = snap.analysis().extend_selection(frange)?;
97 if next == range { 96 if next == range {
98 break; 97 break;
99 } else { 98 } else {
@@ -119,18 +118,18 @@ pub fn handle_selection_range(
119} 118}
120 119
121pub fn handle_matching_brace( 120pub fn handle_matching_brace(
122 world: WorldSnapshot, 121 snap: GlobalStateSnapshot,
123 params: lsp_ext::MatchingBraceParams, 122 params: lsp_ext::MatchingBraceParams,
124) -> Result<Vec<Position>> { 123) -> Result<Vec<Position>> {
125 let _p = profile("handle_matching_brace"); 124 let _p = profile("handle_matching_brace");
126 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 125 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
127 let line_index = world.analysis().file_line_index(file_id)?; 126 let line_index = snap.analysis().file_line_index(file_id)?;
128 let res = params 127 let res = params
129 .positions 128 .positions
130 .into_iter() 129 .into_iter()
131 .map(|position| { 130 .map(|position| {
132 let offset = from_proto::offset(&line_index, position); 131 let offset = from_proto::offset(&line_index, position);
133 let offset = match world.analysis().matching_brace(FilePosition { file_id, offset }) { 132 let offset = match snap.analysis().matching_brace(FilePosition { file_id, offset }) {
134 Ok(Some(matching_brace_offset)) => matching_brace_offset, 133 Ok(Some(matching_brace_offset)) => matching_brace_offset,
135 Err(_) | Ok(None) => offset, 134 Err(_) | Ok(None) => offset,
136 }; 135 };
@@ -141,17 +140,17 @@ pub fn handle_matching_brace(
141} 140}
142 141
143pub fn handle_join_lines( 142pub fn handle_join_lines(
144 world: WorldSnapshot, 143 snap: GlobalStateSnapshot,
145 params: lsp_ext::JoinLinesParams, 144 params: lsp_ext::JoinLinesParams,
146) -> Result<Vec<lsp_types::TextEdit>> { 145) -> Result<Vec<lsp_types::TextEdit>> {
147 let _p = profile("handle_join_lines"); 146 let _p = profile("handle_join_lines");
148 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 147 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
149 let line_index = world.analysis().file_line_index(file_id)?; 148 let line_index = snap.analysis().file_line_index(file_id)?;
150 let line_endings = world.file_line_endings(file_id); 149 let line_endings = snap.file_line_endings(file_id);
151 let mut res = TextEdit::default(); 150 let mut res = TextEdit::default();
152 for range in params.ranges { 151 for range in params.ranges {
153 let range = from_proto::text_range(&line_index, range); 152 let range = from_proto::text_range(&line_index, range);
154 let edit = world.analysis().join_lines(FileRange { file_id, range })?; 153 let edit = snap.analysis().join_lines(FileRange { file_id, range })?;
155 match res.union(edit) { 154 match res.union(edit) {
156 Ok(()) => (), 155 Ok(()) => (),
157 Err(_edit) => { 156 Err(_edit) => {
@@ -164,37 +163,37 @@ pub fn handle_join_lines(
164} 163}
165 164
166pub fn handle_on_enter( 165pub fn handle_on_enter(
167 world: WorldSnapshot, 166 snap: GlobalStateSnapshot,
168 params: lsp_types::TextDocumentPositionParams, 167 params: lsp_types::TextDocumentPositionParams,
169) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> { 168) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
170 let _p = profile("handle_on_enter"); 169 let _p = profile("handle_on_enter");
171 let position = from_proto::file_position(&world, params)?; 170 let position = from_proto::file_position(&snap, params)?;
172 let edit = match world.analysis().on_enter(position)? { 171 let edit = match snap.analysis().on_enter(position)? {
173 None => return Ok(None), 172 None => return Ok(None),
174 Some(it) => it, 173 Some(it) => it,
175 }; 174 };
176 let line_index = world.analysis().file_line_index(position.file_id)?; 175 let line_index = snap.analysis().file_line_index(position.file_id)?;
177 let line_endings = world.file_line_endings(position.file_id); 176 let line_endings = snap.file_line_endings(position.file_id);
178 let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit); 177 let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
179 Ok(Some(edit)) 178 Ok(Some(edit))
180} 179}
181 180
182// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. 181// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
183pub fn handle_on_type_formatting( 182pub fn handle_on_type_formatting(
184 world: WorldSnapshot, 183 snap: GlobalStateSnapshot,
185 params: lsp_types::DocumentOnTypeFormattingParams, 184 params: lsp_types::DocumentOnTypeFormattingParams,
186) -> Result<Option<Vec<lsp_types::TextEdit>>> { 185) -> Result<Option<Vec<lsp_types::TextEdit>>> {
187 let _p = profile("handle_on_type_formatting"); 186 let _p = profile("handle_on_type_formatting");
188 let mut position = from_proto::file_position(&world, params.text_document_position)?; 187 let mut position = from_proto::file_position(&snap, params.text_document_position)?;
189 let line_index = world.analysis().file_line_index(position.file_id)?; 188 let line_index = snap.analysis().file_line_index(position.file_id)?;
190 let line_endings = world.file_line_endings(position.file_id); 189 let line_endings = snap.file_line_endings(position.file_id);
191 190
192 // in `ra_ide`, the `on_type` invariant is that 191 // in `ra_ide`, the `on_type` invariant is that
193 // `text.char_at(position) == typed_char`. 192 // `text.char_at(position) == typed_char`.
194 position.offset -= TextSize::of('.'); 193 position.offset -= TextSize::of('.');
195 let char_typed = params.ch.chars().next().unwrap_or('\0'); 194 let char_typed = params.ch.chars().next().unwrap_or('\0');
196 assert!({ 195 assert!({
197 let text = world.analysis().file_text(position.file_id)?; 196 let text = snap.analysis().file_text(position.file_id)?;
198 text[usize::from(position.offset)..].starts_with(char_typed) 197 text[usize::from(position.offset)..].starts_with(char_typed)
199 }); 198 });
200 199
@@ -206,7 +205,7 @@ pub fn handle_on_type_formatting(
206 return Ok(None); 205 return Ok(None);
207 } 206 }
208 207
209 let edit = world.analysis().on_char_typed(position, char_typed)?; 208 let edit = snap.analysis().on_char_typed(position, char_typed)?;
210 let mut edit = match edit { 209 let mut edit = match edit {
211 Some(it) => it, 210 Some(it) => it,
212 None => return Ok(None), 211 None => return Ok(None),
@@ -220,16 +219,16 @@ pub fn handle_on_type_formatting(
220} 219}
221 220
222pub fn handle_document_symbol( 221pub fn handle_document_symbol(
223 world: WorldSnapshot, 222 snap: GlobalStateSnapshot,
224 params: lsp_types::DocumentSymbolParams, 223 params: lsp_types::DocumentSymbolParams,
225) -> Result<Option<lsp_types::DocumentSymbolResponse>> { 224) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
226 let _p = profile("handle_document_symbol"); 225 let _p = profile("handle_document_symbol");
227 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 226 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
228 let line_index = world.analysis().file_line_index(file_id)?; 227 let line_index = snap.analysis().file_line_index(file_id)?;
229 228
230 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); 229 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
231 230
232 for symbol in world.analysis().file_structure(file_id)? { 231 for symbol in snap.analysis().file_structure(file_id)? {
233 let doc_symbol = DocumentSymbol { 232 let doc_symbol = DocumentSymbol {
234 name: symbol.label, 233 name: symbol.label,
235 detail: symbol.detail, 234 detail: symbol.detail,
@@ -255,10 +254,10 @@ pub fn handle_document_symbol(
255 } 254 }
256 } 255 }
257 256
258 let res = if world.config.client_caps.hierarchical_symbols { 257 let res = if snap.config.client_caps.hierarchical_symbols {
259 document_symbols.into() 258 document_symbols.into()
260 } else { 259 } else {
261 let url = to_proto::url(&world, file_id)?; 260 let url = to_proto::url(&snap, file_id)?;
262 let mut symbol_information = Vec::<SymbolInformation>::new(); 261 let mut symbol_information = Vec::<SymbolInformation>::new();
263 for symbol in document_symbols { 262 for symbol in document_symbols {
264 flatten_document_symbol(&symbol, None, &url, &mut symbol_information); 263 flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
@@ -288,7 +287,7 @@ pub fn handle_document_symbol(
288} 287}
289 288
290pub fn handle_workspace_symbol( 289pub fn handle_workspace_symbol(
291 world: WorldSnapshot, 290 snap: GlobalStateSnapshot,
292 params: lsp_types::WorkspaceSymbolParams, 291 params: lsp_types::WorkspaceSymbolParams,
293) -> Result<Option<Vec<SymbolInformation>>> { 292) -> Result<Option<Vec<SymbolInformation>>> {
294 let _p = profile("handle_workspace_symbol"); 293 let _p = profile("handle_workspace_symbol");
@@ -306,22 +305,22 @@ pub fn handle_workspace_symbol(
306 q.limit(128); 305 q.limit(128);
307 q 306 q
308 }; 307 };
309 let mut res = exec_query(&world, query)?; 308 let mut res = exec_query(&snap, query)?;
310 if res.is_empty() && !all_symbols { 309 if res.is_empty() && !all_symbols {
311 let mut query = Query::new(params.query); 310 let mut query = Query::new(params.query);
312 query.limit(128); 311 query.limit(128);
313 res = exec_query(&world, query)?; 312 res = exec_query(&snap, query)?;
314 } 313 }
315 314
316 return Ok(Some(res)); 315 return Ok(Some(res));
317 316
318 fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> { 317 fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
319 let mut res = Vec::new(); 318 let mut res = Vec::new();
320 for nav in world.analysis().symbol_search(query)? { 319 for nav in snap.analysis().symbol_search(query)? {
321 let info = SymbolInformation { 320 let info = SymbolInformation {
322 name: nav.name().to_string(), 321 name: nav.name().to_string(),
323 kind: to_proto::symbol_kind(nav.kind()), 322 kind: to_proto::symbol_kind(nav.kind()),
324 location: to_proto::location(world, nav.file_range())?, 323 location: to_proto::location(snap, nav.file_range())?,
325 container_name: nav.container_name().map(|v| v.to_string()), 324 container_name: nav.container_name().map(|v| v.to_string()),
326 deprecated: None, 325 deprecated: None,
327 }; 326 };
@@ -332,73 +331,73 @@ pub fn handle_workspace_symbol(
332} 331}
333 332
334pub fn handle_goto_definition( 333pub fn handle_goto_definition(
335 world: WorldSnapshot, 334 snap: GlobalStateSnapshot,
336 params: lsp_types::GotoDefinitionParams, 335 params: lsp_types::GotoDefinitionParams,
337) -> Result<Option<lsp_types::GotoDefinitionResponse>> { 336) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
338 let _p = profile("handle_goto_definition"); 337 let _p = profile("handle_goto_definition");
339 let position = from_proto::file_position(&world, params.text_document_position_params)?; 338 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
340 let nav_info = match world.analysis().goto_definition(position)? { 339 let nav_info = match snap.analysis().goto_definition(position)? {
341 None => return Ok(None), 340 None => return Ok(None),
342 Some(it) => it, 341 Some(it) => it,
343 }; 342 };
344 let src = FileRange { file_id: position.file_id, range: nav_info.range }; 343 let src = FileRange { file_id: position.file_id, range: nav_info.range };
345 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; 344 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
346 Ok(Some(res)) 345 Ok(Some(res))
347} 346}
348 347
349pub fn handle_goto_implementation( 348pub fn handle_goto_implementation(
350 world: WorldSnapshot, 349 snap: GlobalStateSnapshot,
351 params: lsp_types::request::GotoImplementationParams, 350 params: lsp_types::request::GotoImplementationParams,
352) -> Result<Option<lsp_types::request::GotoImplementationResponse>> { 351) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
353 let _p = profile("handle_goto_implementation"); 352 let _p = profile("handle_goto_implementation");
354 let position = from_proto::file_position(&world, params.text_document_position_params)?; 353 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
355 let nav_info = match world.analysis().goto_implementation(position)? { 354 let nav_info = match snap.analysis().goto_implementation(position)? {
356 None => return Ok(None), 355 None => return Ok(None),
357 Some(it) => it, 356 Some(it) => it,
358 }; 357 };
359 let src = FileRange { file_id: position.file_id, range: nav_info.range }; 358 let src = FileRange { file_id: position.file_id, range: nav_info.range };
360 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; 359 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
361 Ok(Some(res)) 360 Ok(Some(res))
362} 361}
363 362
364pub fn handle_goto_type_definition( 363pub fn handle_goto_type_definition(
365 world: WorldSnapshot, 364 snap: GlobalStateSnapshot,
366 params: lsp_types::request::GotoTypeDefinitionParams, 365 params: lsp_types::request::GotoTypeDefinitionParams,
367) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> { 366) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
368 let _p = profile("handle_goto_type_definition"); 367 let _p = profile("handle_goto_type_definition");
369 let position = from_proto::file_position(&world, params.text_document_position_params)?; 368 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
370 let nav_info = match world.analysis().goto_type_definition(position)? { 369 let nav_info = match snap.analysis().goto_type_definition(position)? {
371 None => return Ok(None), 370 None => return Ok(None),
372 Some(it) => it, 371 Some(it) => it,
373 }; 372 };
374 let src = FileRange { file_id: position.file_id, range: nav_info.range }; 373 let src = FileRange { file_id: position.file_id, range: nav_info.range };
375 let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; 374 let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?;
376 Ok(Some(res)) 375 Ok(Some(res))
377} 376}
378 377
379pub fn handle_parent_module( 378pub fn handle_parent_module(
380 world: WorldSnapshot, 379 snap: GlobalStateSnapshot,
381 params: lsp_types::TextDocumentPositionParams, 380 params: lsp_types::TextDocumentPositionParams,
382) -> Result<Option<lsp_types::GotoDefinitionResponse>> { 381) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
383 let _p = profile("handle_parent_module"); 382 let _p = profile("handle_parent_module");
384 let position = from_proto::file_position(&world, params)?; 383 let position = from_proto::file_position(&snap, params)?;
385 let navs = world.analysis().parent_module(position)?; 384 let navs = snap.analysis().parent_module(position)?;
386 let res = to_proto::goto_definition_response(&world, None, navs)?; 385 let res = to_proto::goto_definition_response(&snap, None, navs)?;
387 Ok(Some(res)) 386 Ok(Some(res))
388} 387}
389 388
390pub fn handle_runnables( 389pub fn handle_runnables(
391 world: WorldSnapshot, 390 snap: GlobalStateSnapshot,
392 params: lsp_ext::RunnablesParams, 391 params: lsp_ext::RunnablesParams,
393) -> Result<Vec<lsp_ext::Runnable>> { 392) -> Result<Vec<lsp_ext::Runnable>> {
394 let _p = profile("handle_runnables"); 393 let _p = profile("handle_runnables");
395 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 394 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
396 let line_index = world.analysis().file_line_index(file_id)?; 395 let line_index = snap.analysis().file_line_index(file_id)?;
397 let offset = params.position.map(|it| from_proto::offset(&line_index, it)); 396 let offset = params.position.map(|it| from_proto::offset(&line_index, it));
398 let mut res = Vec::new(); 397 let mut res = Vec::new();
399 let workspace_root = world.workspace_root_for(file_id); 398 let workspace_root = snap.workspace_root_for(file_id);
400 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; 399 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
401 for runnable in world.analysis().runnables(file_id)? { 400 for runnable in snap.analysis().runnables(file_id)? {
402 if let Some(offset) = offset { 401 if let Some(offset) = offset {
403 if !runnable.nav.full_range().contains_inclusive(offset) { 402 if !runnable.nav.full_range().contains_inclusive(offset) {
404 continue; 403 continue;
@@ -413,7 +412,7 @@ pub fn handle_runnables(
413 } 412 }
414 } 413 }
415 } 414 }
416 res.push(to_proto::runnable(&world, file_id, runnable)?); 415 res.push(to_proto::runnable(&snap, file_id, runnable)?);
417 } 416 }
418 417
419 // Add `cargo check` and `cargo test` for the whole package 418 // Add `cargo check` and `cargo test` for the whole package
@@ -453,16 +452,16 @@ pub fn handle_runnables(
453} 452}
454 453
455pub fn handle_completion( 454pub fn handle_completion(
456 world: WorldSnapshot, 455 snap: GlobalStateSnapshot,
457 params: lsp_types::CompletionParams, 456 params: lsp_types::CompletionParams,
458) -> Result<Option<lsp_types::CompletionResponse>> { 457) -> Result<Option<lsp_types::CompletionResponse>> {
459 let _p = profile("handle_completion"); 458 let _p = profile("handle_completion");
460 let position = from_proto::file_position(&world, params.text_document_position)?; 459 let position = from_proto::file_position(&snap, params.text_document_position)?;
461 let completion_triggered_after_single_colon = { 460 let completion_triggered_after_single_colon = {
462 let mut res = false; 461 let mut res = false;
463 if let Some(ctx) = params.context { 462 if let Some(ctx) = params.context {
464 if ctx.trigger_character.unwrap_or_default() == ":" { 463 if ctx.trigger_character.unwrap_or_default() == ":" {
465 let source_file = world.analysis().parse(position.file_id)?; 464 let source_file = snap.analysis().parse(position.file_id)?;
466 let syntax = source_file.syntax(); 465 let syntax = source_file.syntax();
467 let text = syntax.text(); 466 let text = syntax.text();
468 if let Some(next_char) = text.char_at(position.offset) { 467 if let Some(next_char) = text.char_at(position.offset) {
@@ -480,12 +479,12 @@ pub fn handle_completion(
480 return Ok(None); 479 return Ok(None);
481 } 480 }
482 481
483 let items = match world.analysis().completions(&world.config.completion, position)? { 482 let items = match snap.analysis().completions(&snap.config.completion, position)? {
484 None => return Ok(None), 483 None => return Ok(None),
485 Some(items) => items, 484 Some(items) => items,
486 }; 485 };
487 let line_index = world.analysis().file_line_index(position.file_id)?; 486 let line_index = snap.analysis().file_line_index(position.file_id)?;
488 let line_endings = world.file_line_endings(position.file_id); 487 let line_endings = snap.file_line_endings(position.file_id);
489 let items: Vec<CompletionItem> = items 488 let items: Vec<CompletionItem> = items
490 .into_iter() 489 .into_iter()
491 .map(|item| to_proto::completion_item(&line_index, line_endings, item)) 490 .map(|item| to_proto::completion_item(&line_index, line_endings, item))
@@ -495,15 +494,15 @@ pub fn handle_completion(
495} 494}
496 495
497pub fn handle_folding_range( 496pub fn handle_folding_range(
498 world: WorldSnapshot, 497 snap: GlobalStateSnapshot,
499 params: FoldingRangeParams, 498 params: FoldingRangeParams,
500) -> Result<Option<Vec<FoldingRange>>> { 499) -> Result<Option<Vec<FoldingRange>>> {
501 let _p = profile("handle_folding_range"); 500 let _p = profile("handle_folding_range");
502 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 501 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
503 let folds = world.analysis().folding_ranges(file_id)?; 502 let folds = snap.analysis().folding_ranges(file_id)?;
504 let text = world.analysis().file_text(file_id)?; 503 let text = snap.analysis().file_text(file_id)?;
505 let line_index = world.analysis().file_line_index(file_id)?; 504 let line_index = snap.analysis().file_line_index(file_id)?;
506 let line_folding_only = world.config.client_caps.line_folding_only; 505 let line_folding_only = snap.config.client_caps.line_folding_only;
507 let res = folds 506 let res = folds
508 .into_iter() 507 .into_iter()
509 .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it)) 508 .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
@@ -512,16 +511,16 @@ pub fn handle_folding_range(
512} 511}
513 512
514pub fn handle_signature_help( 513pub fn handle_signature_help(
515 world: WorldSnapshot, 514 snap: GlobalStateSnapshot,
516 params: lsp_types::SignatureHelpParams, 515 params: lsp_types::SignatureHelpParams,
517) -> Result<Option<lsp_types::SignatureHelp>> { 516) -> Result<Option<lsp_types::SignatureHelp>> {
518 let _p = profile("handle_signature_help"); 517 let _p = profile("handle_signature_help");
519 let position = from_proto::file_position(&world, params.text_document_position_params)?; 518 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
520 let call_info = match world.analysis().call_info(position)? { 519 let call_info = match snap.analysis().call_info(position)? {
521 None => return Ok(None), 520 None => return Ok(None),
522 Some(it) => it, 521 Some(it) => it,
523 }; 522 };
524 let concise = !world.config.call_info_full; 523 let concise = !snap.config.call_info_full;
525 let mut active_parameter = call_info.active_parameter.map(|it| it as i64); 524 let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
526 if concise && call_info.signature.has_self_param { 525 if concise && call_info.signature.has_self_param {
527 active_parameter = active_parameter.map(|it| it.saturating_sub(1)); 526 active_parameter = active_parameter.map(|it| it.saturating_sub(1));
@@ -535,14 +534,17 @@ pub fn handle_signature_help(
535 })) 534 }))
536} 535}
537 536
538pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result<Option<Hover>> { 537pub fn handle_hover(
538 snap: GlobalStateSnapshot,
539 params: lsp_types::HoverParams,
540) -> Result<Option<Hover>> {
539 let _p = profile("handle_hover"); 541 let _p = profile("handle_hover");
540 let position = from_proto::file_position(&world, params.text_document_position_params)?; 542 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
541 let info = match world.analysis().hover(position)? { 543 let info = match snap.analysis().hover(position)? {
542 None => return Ok(None), 544 None => return Ok(None),
543 Some(info) => info, 545 Some(info) => info,
544 }; 546 };
545 let line_index = world.analysis.file_line_index(position.file_id)?; 547 let line_index = snap.analysis.file_line_index(position.file_id)?;
546 let range = to_proto::range(&line_index, info.range); 548 let range = to_proto::range(&line_index, info.range);
547 let res = Hover { 549 let res = Hover {
548 contents: HoverContents::Markup(MarkupContent { 550 contents: HoverContents::Markup(MarkupContent {
@@ -555,26 +557,29 @@ pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Res
555} 557}
556 558
557pub fn handle_prepare_rename( 559pub fn handle_prepare_rename(
558 world: WorldSnapshot, 560 snap: GlobalStateSnapshot,
559 params: lsp_types::TextDocumentPositionParams, 561 params: lsp_types::TextDocumentPositionParams,
560) -> Result<Option<PrepareRenameResponse>> { 562) -> Result<Option<PrepareRenameResponse>> {
561 let _p = profile("handle_prepare_rename"); 563 let _p = profile("handle_prepare_rename");
562 let position = from_proto::file_position(&world, params)?; 564 let position = from_proto::file_position(&snap, params)?;
563 565
564 let optional_change = world.analysis().rename(position, "dummy")?; 566 let optional_change = snap.analysis().rename(position, "dummy")?;
565 let range = match optional_change { 567 let range = match optional_change {
566 None => return Ok(None), 568 None => return Ok(None),
567 Some(it) => it.range, 569 Some(it) => it.range,
568 }; 570 };
569 571
570 let line_index = world.analysis().file_line_index(position.file_id)?; 572 let line_index = snap.analysis().file_line_index(position.file_id)?;
571 let range = to_proto::range(&line_index, range); 573 let range = to_proto::range(&line_index, range);
572 Ok(Some(PrepareRenameResponse::Range(range))) 574 Ok(Some(PrepareRenameResponse::Range(range)))
573} 575}
574 576
575pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> { 577pub fn handle_rename(
578 snap: GlobalStateSnapshot,
579 params: RenameParams,
580) -> Result<Option<WorkspaceEdit>> {
576 let _p = profile("handle_rename"); 581 let _p = profile("handle_rename");
577 let position = from_proto::file_position(&world, params.text_document_position)?; 582 let position = from_proto::file_position(&snap, params.text_document_position)?;
578 583
579 if params.new_name.is_empty() { 584 if params.new_name.is_empty() {
580 return Err(LspError::new( 585 return Err(LspError::new(
@@ -584,36 +589,36 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Optio
584 .into()); 589 .into());
585 } 590 }
586 591
587 let optional_change = world.analysis().rename(position, &*params.new_name)?; 592 let optional_change = snap.analysis().rename(position, &*params.new_name)?;
588 let source_change = match optional_change { 593 let source_change = match optional_change {
589 None => return Ok(None), 594 None => return Ok(None),
590 Some(it) => it.info, 595 Some(it) => it.info,
591 }; 596 };
592 let workspace_edit = to_proto::workspace_edit(&world, source_change)?; 597 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
593 Ok(Some(workspace_edit)) 598 Ok(Some(workspace_edit))
594} 599}
595 600
596pub fn handle_references( 601pub fn handle_references(
597 world: WorldSnapshot, 602 snap: GlobalStateSnapshot,
598 params: lsp_types::ReferenceParams, 603 params: lsp_types::ReferenceParams,
599) -> Result<Option<Vec<Location>>> { 604) -> Result<Option<Vec<Location>>> {
600 let _p = profile("handle_references"); 605 let _p = profile("handle_references");
601 let position = from_proto::file_position(&world, params.text_document_position)?; 606 let position = from_proto::file_position(&snap, params.text_document_position)?;
602 607
603 let refs = match world.analysis().find_all_refs(position, None)? { 608 let refs = match snap.analysis().find_all_refs(position, None)? {
604 None => return Ok(None), 609 None => return Ok(None),
605 Some(refs) => refs, 610 Some(refs) => refs,
606 }; 611 };
607 612
608 let locations = if params.context.include_declaration { 613 let locations = if params.context.include_declaration {
609 refs.into_iter() 614 refs.into_iter()
610 .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) 615 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
611 .collect() 616 .collect()
612 } else { 617 } else {
613 // Only iterate over the references if include_declaration was false 618 // Only iterate over the references if include_declaration was false
614 refs.references() 619 refs.references()
615 .iter() 620 .iter()
616 .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) 621 .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok())
617 .collect() 622 .collect()
618 }; 623 };
619 624
@@ -621,24 +626,24 @@ pub fn handle_references(
621} 626}
622 627
623pub fn handle_formatting( 628pub fn handle_formatting(
624 world: WorldSnapshot, 629 snap: GlobalStateSnapshot,
625 params: DocumentFormattingParams, 630 params: DocumentFormattingParams,
626) -> Result<Option<Vec<lsp_types::TextEdit>>> { 631) -> Result<Option<Vec<lsp_types::TextEdit>>> {
627 let _p = profile("handle_formatting"); 632 let _p = profile("handle_formatting");
628 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 633 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
629 let file = world.analysis().file_text(file_id)?; 634 let file = snap.analysis().file_text(file_id)?;
630 let crate_ids = world.analysis().crate_for(file_id)?; 635 let crate_ids = snap.analysis().crate_for(file_id)?;
631 636
632 let file_line_index = world.analysis().file_line_index(file_id)?; 637 let file_line_index = snap.analysis().file_line_index(file_id)?;
633 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); 638 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str()));
634 639
635 let mut rustfmt = match &world.config.rustfmt { 640 let mut rustfmt = match &snap.config.rustfmt {
636 RustfmtConfig::Rustfmt { extra_args } => { 641 RustfmtConfig::Rustfmt { extra_args } => {
637 let mut cmd = process::Command::new("rustfmt"); 642 let mut cmd = process::Command::new("rustfmt");
638 cmd.args(extra_args); 643 cmd.args(extra_args);
639 if let Some(&crate_id) = crate_ids.first() { 644 if let Some(&crate_id) = crate_ids.first() {
640 // Assume all crates are in the same edition 645 // Assume all crates are in the same edition
641 let edition = world.analysis().crate_edition(crate_id)?; 646 let edition = snap.analysis().crate_edition(crate_id)?;
642 cmd.arg("--edition"); 647 cmd.arg("--edition");
643 cmd.arg(edition.to_string()); 648 cmd.arg(edition.to_string());
644 } 649 }
@@ -697,15 +702,14 @@ pub fn handle_formatting(
697} 702}
698 703
699fn handle_fixes( 704fn handle_fixes(
700 world: &WorldSnapshot, 705 snap: &GlobalStateSnapshot,
701 params: &lsp_types::CodeActionParams, 706 params: &lsp_types::CodeActionParams,
702 res: &mut Vec<lsp_ext::CodeAction>, 707 res: &mut Vec<lsp_ext::CodeAction>,
703) -> Result<()> { 708) -> Result<()> {
704 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 709 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
705 let line_index = world.analysis().file_line_index(file_id)?; 710 let line_index = snap.analysis().file_line_index(file_id)?;
706 let range = from_proto::text_range(&line_index, params.range); 711 let range = from_proto::text_range(&line_index, params.range);
707 712 let diagnostics = snap.analysis().diagnostics(file_id)?;
708 let diagnostics = world.analysis().diagnostics(file_id)?;
709 713
710 let fixes_from_diagnostics = diagnostics 714 let fixes_from_diagnostics = diagnostics
711 .into_iter() 715 .into_iter()
@@ -714,18 +718,19 @@ fn handle_fixes(
714 .map(|(_range, fix)| fix); 718 .map(|(_range, fix)| fix);
715 for fix in fixes_from_diagnostics { 719 for fix in fixes_from_diagnostics {
716 let title = fix.label; 720 let title = fix.label;
717 let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?; 721 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
718 let action = lsp_ext::CodeAction { 722 let action = lsp_ext::CodeAction {
719 title, 723 title,
720 id: None, 724 id: None,
721 group: None, 725 group: None,
722 kind: None, 726 kind: Some(lsp_types::code_action_kind::QUICKFIX.into()),
723 edit: Some(edit), 727 edit: Some(edit),
724 command: None, 728 command: None,
725 }; 729 };
726 res.push(action); 730 res.push(action);
727 } 731 }
728 for fix in world.check_fixes.get(&file_id).into_iter().flatten() { 732
733 for fix in snap.check_fixes.get(&file_id).into_iter().flatten() {
729 let fix_range = from_proto::text_range(&line_index, fix.range); 734 let fix_range = from_proto::text_range(&line_index, fix.range);
730 if fix_range.intersect(range).is_none() { 735 if fix_range.intersect(range).is_none() {
731 continue; 736 continue;
@@ -736,37 +741,34 @@ fn handle_fixes(
736} 741}
737 742
738pub fn handle_code_action( 743pub fn handle_code_action(
739 world: WorldSnapshot, 744 snap: GlobalStateSnapshot,
740 params: lsp_types::CodeActionParams, 745 params: lsp_types::CodeActionParams,
741) -> Result<Option<Vec<lsp_ext::CodeAction>>> { 746) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
742 let _p = profile("handle_code_action"); 747 let _p = profile("handle_code_action");
743 // We intentionally don't support command-based actions, as those either 748 // We intentionally don't support command-based actions, as those either
744 // requires custom client-code anyway, or requires server-initiated edits. 749 // requires custom client-code anyway, or requires server-initiated edits.
745 // Server initiated edits break causality, so we avoid those as well. 750 // Server initiated edits break causality, so we avoid those as well.
746 if !world.config.client_caps.code_action_literals { 751 if !snap.config.client_caps.code_action_literals {
747 return Ok(None); 752 return Ok(None);
748 } 753 }
749 754
750 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 755 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
751 let line_index = world.analysis().file_line_index(file_id)?; 756 let line_index = snap.analysis().file_line_index(file_id)?;
752 let range = from_proto::text_range(&line_index, params.range); 757 let range = from_proto::text_range(&line_index, params.range);
753 let frange = FileRange { file_id, range }; 758 let frange = FileRange { file_id, range };
754 let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); 759 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
755 760
756 handle_fixes(&world, &params, &mut res)?; 761 handle_fixes(&snap, &params, &mut res)?;
757 762
758 if world.config.client_caps.resolve_code_action { 763 if snap.config.client_caps.resolve_code_action {
759 for (index, assist) in world 764 for (index, assist) in
760 .analysis() 765 snap.analysis().unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
761 .unresolved_assists(&world.config.assist, frange)?
762 .into_iter()
763 .enumerate()
764 { 766 {
765 res.push(to_proto::unresolved_code_action(&world, assist, index)?); 767 res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
766 } 768 }
767 } else { 769 } else {
768 for assist in world.analysis().resolved_assists(&world.config.assist, frange)?.into_iter() { 770 for assist in snap.analysis().resolved_assists(&snap.config.assist, frange)?.into_iter() {
769 res.push(to_proto::resolved_code_action(&world, assist)?); 771 res.push(to_proto::resolved_code_action(&snap, assist)?);
770 } 772 }
771 } 773 }
772 774
@@ -774,43 +776,43 @@ pub fn handle_code_action(
774} 776}
775 777
776pub fn handle_resolve_code_action( 778pub fn handle_resolve_code_action(
777 world: WorldSnapshot, 779 snap: GlobalStateSnapshot,
778 params: lsp_ext::ResolveCodeActionParams, 780 params: lsp_ext::ResolveCodeActionParams,
779) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> { 781) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
780 let _p = profile("handle_resolve_code_action"); 782 let _p = profile("handle_resolve_code_action");
781 let file_id = from_proto::file_id(&world, &params.code_action_params.text_document.uri)?; 783 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
782 let line_index = world.analysis().file_line_index(file_id)?; 784 let line_index = snap.analysis().file_line_index(file_id)?;
783 let range = from_proto::text_range(&line_index, params.code_action_params.range); 785 let range = from_proto::text_range(&line_index, params.code_action_params.range);
784 let frange = FileRange { file_id, range }; 786 let frange = FileRange { file_id, range };
785 787
786 let assists = world.analysis().resolved_assists(&world.config.assist, frange)?; 788 let assists = snap.analysis().resolved_assists(&snap.config.assist, frange)?;
787 let id_components = params.id.split(":").collect::<Vec<&str>>(); 789 let id_components = params.id.split(":").collect::<Vec<&str>>();
788 let index = id_components.last().unwrap().parse::<usize>().unwrap(); 790 let index = id_components.last().unwrap().parse::<usize>().unwrap();
789 let id_string = id_components.first().unwrap(); 791 let id_string = id_components.first().unwrap();
790 let assist = &assists[index]; 792 let assist = &assists[index];
791 assert!(assist.assist.id.0 == *id_string); 793 assert!(assist.assist.id.0 == *id_string);
792 Ok(to_proto::resolved_code_action(&world, assist.clone())?.edit) 794 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
793} 795}
794 796
795pub fn handle_code_lens( 797pub fn handle_code_lens(
796 world: WorldSnapshot, 798 snap: GlobalStateSnapshot,
797 params: lsp_types::CodeLensParams, 799 params: lsp_types::CodeLensParams,
798) -> Result<Option<Vec<CodeLens>>> { 800) -> Result<Option<Vec<CodeLens>>> {
799 let _p = profile("handle_code_lens"); 801 let _p = profile("handle_code_lens");
800 let mut lenses: Vec<CodeLens> = Default::default(); 802 let mut lenses: Vec<CodeLens> = Default::default();
801 803
802 if world.config.lens.none() { 804 if snap.config.lens.none() {
803 // early return before any db query! 805 // early return before any db query!
804 return Ok(Some(lenses)); 806 return Ok(Some(lenses));
805 } 807 }
806 808
807 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 809 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
808 let line_index = world.analysis().file_line_index(file_id)?; 810 let line_index = snap.analysis().file_line_index(file_id)?;
809 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; 811 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
810 812
811 if world.config.lens.runnable() { 813 if snap.config.lens.runnable() {
812 // Gather runnables 814 // Gather runnables
813 for runnable in world.analysis().runnables(file_id)? { 815 for runnable in snap.analysis().runnables(file_id)? {
814 let (run_title, debugee) = match &runnable.kind { 816 let (run_title, debugee) = match &runnable.kind {
815 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { 817 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
816 ("â–¶\u{fe0e} Run Test", true) 818 ("â–¶\u{fe0e} Run Test", true)
@@ -836,8 +838,8 @@ pub fn handle_code_lens(
836 }; 838 };
837 839
838 let range = to_proto::range(&line_index, runnable.nav.range()); 840 let range = to_proto::range(&line_index, runnable.nav.range());
839 let r = to_proto::runnable(&world, file_id, runnable)?; 841 let r = to_proto::runnable(&snap, file_id, runnable)?;
840 if world.config.lens.run { 842 if snap.config.lens.run {
841 let lens = CodeLens { 843 let lens = CodeLens {
842 range, 844 range,
843 command: Some(Command { 845 command: Some(Command {
@@ -850,7 +852,7 @@ pub fn handle_code_lens(
850 lenses.push(lens); 852 lenses.push(lens);
851 } 853 }
852 854
853 if debugee && world.config.lens.debug { 855 if debugee && snap.config.lens.debug {
854 let debug_lens = CodeLens { 856 let debug_lens = CodeLens {
855 range, 857 range,
856 command: Some(Command { 858 command: Some(Command {
@@ -865,11 +867,10 @@ pub fn handle_code_lens(
865 } 867 }
866 } 868 }
867 869
868 if world.config.lens.impementations { 870 if snap.config.lens.impementations {
869 // Handle impls 871 // Handle impls
870 lenses.extend( 872 lenses.extend(
871 world 873 snap.analysis()
872 .analysis()
873 .file_structure(file_id)? 874 .file_structure(file_id)?
874 .into_iter() 875 .into_iter()
875 .filter(|it| match it.kind { 876 .filter(|it| match it.kind {
@@ -904,14 +905,17 @@ enum CodeLensResolveData {
904 Impls(lsp_types::request::GotoImplementationParams), 905 Impls(lsp_types::request::GotoImplementationParams),
905} 906}
906 907
907pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> { 908pub fn handle_code_lens_resolve(
909 snap: GlobalStateSnapshot,
910 code_lens: CodeLens,
911) -> Result<CodeLens> {
908 let _p = profile("handle_code_lens_resolve"); 912 let _p = profile("handle_code_lens_resolve");
909 let data = code_lens.data.unwrap(); 913 let data = code_lens.data.unwrap();
910 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?; 914 let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
911 match resolve { 915 match resolve {
912 Some(CodeLensResolveData::Impls(lens_params)) => { 916 Some(CodeLensResolveData::Impls(lens_params)) => {
913 let locations: Vec<Location> = 917 let locations: Vec<Location> =
914 match handle_goto_implementation(world, lens_params.clone())? { 918 match handle_goto_implementation(snap, lens_params.clone())? {
915 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc], 919 Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
916 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs, 920 Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
917 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links 921 Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
@@ -950,14 +954,14 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re
950} 954}
951 955
952pub fn handle_document_highlight( 956pub fn handle_document_highlight(
953 world: WorldSnapshot, 957 snap: GlobalStateSnapshot,
954 params: lsp_types::DocumentHighlightParams, 958 params: lsp_types::DocumentHighlightParams,
955) -> Result<Option<Vec<DocumentHighlight>>> { 959) -> Result<Option<Vec<DocumentHighlight>>> {
956 let _p = profile("handle_document_highlight"); 960 let _p = profile("handle_document_highlight");
957 let position = from_proto::file_position(&world, params.text_document_position_params)?; 961 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
958 let line_index = world.analysis().file_line_index(position.file_id)?; 962 let line_index = snap.analysis().file_line_index(position.file_id)?;
959 963
960 let refs = match world 964 let refs = match snap
961 .analysis() 965 .analysis()
962 .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))? 966 .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))?
963 { 967 {
@@ -977,19 +981,19 @@ pub fn handle_document_highlight(
977} 981}
978 982
979pub fn handle_ssr( 983pub fn handle_ssr(
980 world: WorldSnapshot, 984 snap: GlobalStateSnapshot,
981 params: lsp_ext::SsrParams, 985 params: lsp_ext::SsrParams,
982) -> Result<lsp_types::WorkspaceEdit> { 986) -> Result<lsp_types::WorkspaceEdit> {
983 let _p = profile("handle_ssr"); 987 let _p = profile("handle_ssr");
984 let source_change = 988 let source_change =
985 world.analysis().structural_search_replace(&params.query, params.parse_only)??; 989 snap.analysis().structural_search_replace(&params.query, params.parse_only)??;
986 to_proto::workspace_edit(&world, source_change) 990 to_proto::workspace_edit(&snap, source_change)
987} 991}
988 992
989pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { 993pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
990 let _p = profile("publish_diagnostics"); 994 let _p = profile("publish_diagnostics");
991 let line_index = world.analysis().file_line_index(file_id)?; 995 let line_index = snap.analysis().file_line_index(file_id)?;
992 let diagnostics: Vec<Diagnostic> = world 996 let diagnostics: Vec<Diagnostic> = snap
993 .analysis() 997 .analysis()
994 .diagnostics(file_id)? 998 .diagnostics(file_id)?
995 .into_iter() 999 .into_iter()
@@ -1007,28 +1011,28 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia
1007} 1011}
1008 1012
1009pub fn handle_inlay_hints( 1013pub fn handle_inlay_hints(
1010 world: WorldSnapshot, 1014 snap: GlobalStateSnapshot,
1011 params: InlayHintsParams, 1015 params: InlayHintsParams,
1012) -> Result<Vec<InlayHint>> { 1016) -> Result<Vec<InlayHint>> {
1013 let _p = profile("handle_inlay_hints"); 1017 let _p = profile("handle_inlay_hints");
1014 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 1018 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1015 let analysis = world.analysis(); 1019 let analysis = snap.analysis();
1016 let line_index = analysis.file_line_index(file_id)?; 1020 let line_index = analysis.file_line_index(file_id)?;
1017 Ok(analysis 1021 Ok(analysis
1018 .inlay_hints(file_id, &world.config.inlay_hints)? 1022 .inlay_hints(file_id, &snap.config.inlay_hints)?
1019 .into_iter() 1023 .into_iter()
1020 .map(|it| to_proto::inlay_int(&line_index, it)) 1024 .map(|it| to_proto::inlay_int(&line_index, it))
1021 .collect()) 1025 .collect())
1022} 1026}
1023 1027
1024pub fn handle_call_hierarchy_prepare( 1028pub fn handle_call_hierarchy_prepare(
1025 world: WorldSnapshot, 1029 snap: GlobalStateSnapshot,
1026 params: CallHierarchyPrepareParams, 1030 params: CallHierarchyPrepareParams,
1027) -> Result<Option<Vec<CallHierarchyItem>>> { 1031) -> Result<Option<Vec<CallHierarchyItem>>> {
1028 let _p = profile("handle_call_hierarchy_prepare"); 1032 let _p = profile("handle_call_hierarchy_prepare");
1029 let position = from_proto::file_position(&world, params.text_document_position_params)?; 1033 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1030 1034
1031 let nav_info = match world.analysis().call_hierarchy(position)? { 1035 let nav_info = match snap.analysis().call_hierarchy(position)? {
1032 None => return Ok(None), 1036 None => return Ok(None),
1033 Some(it) => it, 1037 Some(it) => it,
1034 }; 1038 };
@@ -1037,24 +1041,24 @@ pub fn handle_call_hierarchy_prepare(
1037 let res = navs 1041 let res = navs
1038 .into_iter() 1042 .into_iter()
1039 .filter(|it| it.kind() == SyntaxKind::FN_DEF) 1043 .filter(|it| it.kind() == SyntaxKind::FN_DEF)
1040 .map(|it| to_proto::call_hierarchy_item(&world, it)) 1044 .map(|it| to_proto::call_hierarchy_item(&snap, it))
1041 .collect::<Result<Vec<_>>>()?; 1045 .collect::<Result<Vec<_>>>()?;
1042 1046
1043 Ok(Some(res)) 1047 Ok(Some(res))
1044} 1048}
1045 1049
1046pub fn handle_call_hierarchy_incoming( 1050pub fn handle_call_hierarchy_incoming(
1047 world: WorldSnapshot, 1051 snap: GlobalStateSnapshot,
1048 params: CallHierarchyIncomingCallsParams, 1052 params: CallHierarchyIncomingCallsParams,
1049) -> Result<Option<Vec<CallHierarchyIncomingCall>>> { 1053) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1050 let _p = profile("handle_call_hierarchy_incoming"); 1054 let _p = profile("handle_call_hierarchy_incoming");
1051 let item = params.item; 1055 let item = params.item;
1052 1056
1053 let doc = TextDocumentIdentifier::new(item.uri); 1057 let doc = TextDocumentIdentifier::new(item.uri);
1054 let frange = from_proto::file_range(&world, doc, item.range)?; 1058 let frange = from_proto::file_range(&snap, doc, item.range)?;
1055 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; 1059 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1056 1060
1057 let call_items = match world.analysis().incoming_calls(fpos)? { 1061 let call_items = match snap.analysis().incoming_calls(fpos)? {
1058 None => return Ok(None), 1062 None => return Ok(None),
1059 Some(it) => it, 1063 Some(it) => it,
1060 }; 1064 };
@@ -1063,8 +1067,8 @@ pub fn handle_call_hierarchy_incoming(
1063 1067
1064 for call_item in call_items.into_iter() { 1068 for call_item in call_items.into_iter() {
1065 let file_id = call_item.target.file_id(); 1069 let file_id = call_item.target.file_id();
1066 let line_index = world.analysis().file_line_index(file_id)?; 1070 let line_index = snap.analysis().file_line_index(file_id)?;
1067 let item = to_proto::call_hierarchy_item(&world, call_item.target)?; 1071 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1068 res.push(CallHierarchyIncomingCall { 1072 res.push(CallHierarchyIncomingCall {
1069 from: item, 1073 from: item,
1070 from_ranges: call_item 1074 from_ranges: call_item
@@ -1079,17 +1083,17 @@ pub fn handle_call_hierarchy_incoming(
1079} 1083}
1080 1084
1081pub fn handle_call_hierarchy_outgoing( 1085pub fn handle_call_hierarchy_outgoing(
1082 world: WorldSnapshot, 1086 snap: GlobalStateSnapshot,
1083 params: CallHierarchyOutgoingCallsParams, 1087 params: CallHierarchyOutgoingCallsParams,
1084) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> { 1088) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1085 let _p = profile("handle_call_hierarchy_outgoing"); 1089 let _p = profile("handle_call_hierarchy_outgoing");
1086 let item = params.item; 1090 let item = params.item;
1087 1091
1088 let doc = TextDocumentIdentifier::new(item.uri); 1092 let doc = TextDocumentIdentifier::new(item.uri);
1089 let frange = from_proto::file_range(&world, doc, item.range)?; 1093 let frange = from_proto::file_range(&snap, doc, item.range)?;
1090 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; 1094 let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
1091 1095
1092 let call_items = match world.analysis().outgoing_calls(fpos)? { 1096 let call_items = match snap.analysis().outgoing_calls(fpos)? {
1093 None => return Ok(None), 1097 None => return Ok(None),
1094 Some(it) => it, 1098 Some(it) => it,
1095 }; 1099 };
@@ -1098,8 +1102,8 @@ pub fn handle_call_hierarchy_outgoing(
1098 1102
1099 for call_item in call_items.into_iter() { 1103 for call_item in call_items.into_iter() {
1100 let file_id = call_item.target.file_id(); 1104 let file_id = call_item.target.file_id();
1101 let line_index = world.analysis().file_line_index(file_id)?; 1105 let line_index = snap.analysis().file_line_index(file_id)?;
1102 let item = to_proto::call_hierarchy_item(&world, call_item.target)?; 1106 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1103 res.push(CallHierarchyOutgoingCall { 1107 res.push(CallHierarchyOutgoingCall {
1104 to: item, 1108 to: item,
1105 from_ranges: call_item 1109 from_ranges: call_item
@@ -1114,31 +1118,31 @@ pub fn handle_call_hierarchy_outgoing(
1114} 1118}
1115 1119
1116pub fn handle_semantic_tokens( 1120pub fn handle_semantic_tokens(
1117 world: WorldSnapshot, 1121 snap: GlobalStateSnapshot,
1118 params: SemanticTokensParams, 1122 params: SemanticTokensParams,
1119) -> Result<Option<SemanticTokensResult>> { 1123) -> Result<Option<SemanticTokensResult>> {
1120 let _p = profile("handle_semantic_tokens"); 1124 let _p = profile("handle_semantic_tokens");
1121 1125
1122 let file_id = from_proto::file_id(&world, &params.text_document.uri)?; 1126 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1123 let text = world.analysis().file_text(file_id)?; 1127 let text = snap.analysis().file_text(file_id)?;
1124 let line_index = world.analysis().file_line_index(file_id)?; 1128 let line_index = snap.analysis().file_line_index(file_id)?;
1125 1129
1126 let highlights = world.analysis().highlight(file_id)?; 1130 let highlights = snap.analysis().highlight(file_id)?;
1127 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1131 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1128 Ok(Some(semantic_tokens.into())) 1132 Ok(Some(semantic_tokens.into()))
1129} 1133}
1130 1134
1131pub fn handle_semantic_tokens_range( 1135pub fn handle_semantic_tokens_range(
1132 world: WorldSnapshot, 1136 snap: GlobalStateSnapshot,
1133 params: SemanticTokensRangeParams, 1137 params: SemanticTokensRangeParams,
1134) -> Result<Option<SemanticTokensRangeResult>> { 1138) -> Result<Option<SemanticTokensRangeResult>> {
1135 let _p = profile("handle_semantic_tokens_range"); 1139 let _p = profile("handle_semantic_tokens_range");
1136 1140
1137 let frange = from_proto::file_range(&world, params.text_document, params.range)?; 1141 let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1138 let text = world.analysis().file_text(frange.file_id)?; 1142 let text = snap.analysis().file_text(frange.file_id)?;
1139 let line_index = world.analysis().file_line_index(frange.file_id)?; 1143 let line_index = snap.analysis().file_line_index(frange.file_id)?;
1140 1144
1141 let highlights = world.analysis().highlight_range(frange)?; 1145 let highlights = snap.analysis().highlight_range(frange)?;
1142 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1146 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1143 Ok(Some(semantic_tokens.into())) 1147 Ok(Some(semantic_tokens.into()))
1144} 1148}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index fb33bdd5f..1da4d80ec 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -10,7 +10,8 @@ use ra_syntax::{SyntaxKind, TextRange, TextSize};
10use ra_vfs::LineEndings; 10use ra_vfs::LineEndings;
11 11
12use crate::{ 12use crate::{
13 cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result, 13 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext,
14 semantic_tokens, Result,
14}; 15};
15 16
16pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { 17pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
@@ -384,41 +385,44 @@ pub(crate) fn folding_range(
384 } 385 }
385} 386}
386 387
387pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<lsp_types::Url> { 388pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> {
388 world.file_id_to_uri(file_id) 389 snap.file_id_to_uri(file_id)
389} 390}
390 391
391pub(crate) fn versioned_text_document_identifier( 392pub(crate) fn versioned_text_document_identifier(
392 world: &WorldSnapshot, 393 snap: &GlobalStateSnapshot,
393 file_id: FileId, 394 file_id: FileId,
394 version: Option<i64>, 395 version: Option<i64>,
395) -> Result<lsp_types::VersionedTextDocumentIdentifier> { 396) -> Result<lsp_types::VersionedTextDocumentIdentifier> {
396 let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version }; 397 let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version };
397 Ok(res) 398 Ok(res)
398} 399}
399 400
400pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result<lsp_types::Location> { 401pub(crate) fn location(
401 let url = url(world, frange.file_id)?; 402 snap: &GlobalStateSnapshot,
402 let line_index = world.analysis().file_line_index(frange.file_id)?; 403 frange: FileRange,
404) -> Result<lsp_types::Location> {
405 let url = url(snap, frange.file_id)?;
406 let line_index = snap.analysis().file_line_index(frange.file_id)?;
403 let range = range(&line_index, frange.range); 407 let range = range(&line_index, frange.range);
404 let loc = lsp_types::Location::new(url, range); 408 let loc = lsp_types::Location::new(url, range);
405 Ok(loc) 409 Ok(loc)
406} 410}
407 411
408pub(crate) fn location_link( 412pub(crate) fn location_link(
409 world: &WorldSnapshot, 413 snap: &GlobalStateSnapshot,
410 src: Option<FileRange>, 414 src: Option<FileRange>,
411 target: NavigationTarget, 415 target: NavigationTarget,
412) -> Result<lsp_types::LocationLink> { 416) -> Result<lsp_types::LocationLink> {
413 let origin_selection_range = match src { 417 let origin_selection_range = match src {
414 Some(src) => { 418 Some(src) => {
415 let line_index = world.analysis().file_line_index(src.file_id)?; 419 let line_index = snap.analysis().file_line_index(src.file_id)?;
416 let range = range(&line_index, src.range); 420 let range = range(&line_index, src.range);
417 Some(range) 421 Some(range)
418 } 422 }
419 None => None, 423 None => None,
420 }; 424 };
421 let (target_uri, target_range, target_selection_range) = location_info(world, target)?; 425 let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
422 let res = lsp_types::LocationLink { 426 let res = lsp_types::LocationLink {
423 origin_selection_range, 427 origin_selection_range,
424 target_uri, 428 target_uri,
@@ -429,12 +433,12 @@ pub(crate) fn location_link(
429} 433}
430 434
431fn location_info( 435fn location_info(
432 world: &WorldSnapshot, 436 snap: &GlobalStateSnapshot,
433 target: NavigationTarget, 437 target: NavigationTarget,
434) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { 438) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
435 let line_index = world.analysis().file_line_index(target.file_id())?; 439 let line_index = snap.analysis().file_line_index(target.file_id())?;
436 440
437 let target_uri = url(world, target.file_id())?; 441 let target_uri = url(snap, target.file_id())?;
438 let target_range = range(&line_index, target.full_range()); 442 let target_range = range(&line_index, target.full_range());
439 let target_selection_range = 443 let target_selection_range =
440 target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); 444 target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range);
@@ -442,14 +446,14 @@ fn location_info(
442} 446}
443 447
444pub(crate) fn goto_definition_response( 448pub(crate) fn goto_definition_response(
445 world: &WorldSnapshot, 449 snap: &GlobalStateSnapshot,
446 src: Option<FileRange>, 450 src: Option<FileRange>,
447 targets: Vec<NavigationTarget>, 451 targets: Vec<NavigationTarget>,
448) -> Result<lsp_types::GotoDefinitionResponse> { 452) -> Result<lsp_types::GotoDefinitionResponse> {
449 if world.config.client_caps.location_link { 453 if snap.config.client_caps.location_link {
450 let links = targets 454 let links = targets
451 .into_iter() 455 .into_iter()
452 .map(|nav| location_link(world, src, nav)) 456 .map(|nav| location_link(snap, src, nav))
453 .collect::<Result<Vec<_>>>()?; 457 .collect::<Result<Vec<_>>>()?;
454 Ok(links.into()) 458 Ok(links.into())
455 } else { 459 } else {
@@ -457,7 +461,7 @@ pub(crate) fn goto_definition_response(
457 .into_iter() 461 .into_iter()
458 .map(|nav| { 462 .map(|nav| {
459 location( 463 location(
460 world, 464 snap,
461 FileRange { 465 FileRange {
462 file_id: nav.file_id(), 466 file_id: nav.file_id(),
463 range: nav.focus_range().unwrap_or(nav.range()), 467 range: nav.focus_range().unwrap_or(nav.range()),
@@ -470,13 +474,13 @@ pub(crate) fn goto_definition_response(
470} 474}
471 475
472pub(crate) fn snippet_text_document_edit( 476pub(crate) fn snippet_text_document_edit(
473 world: &WorldSnapshot, 477 snap: &GlobalStateSnapshot,
474 is_snippet: bool, 478 is_snippet: bool,
475 source_file_edit: SourceFileEdit, 479 source_file_edit: SourceFileEdit,
476) -> Result<lsp_ext::SnippetTextDocumentEdit> { 480) -> Result<lsp_ext::SnippetTextDocumentEdit> {
477 let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; 481 let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?;
478 let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; 482 let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?;
479 let line_endings = world.file_line_endings(source_file_edit.file_id); 483 let line_endings = snap.file_line_endings(source_file_edit.file_id);
480 let edits = source_file_edit 484 let edits = source_file_edit
481 .edit 485 .edit
482 .into_iter() 486 .into_iter()
@@ -486,17 +490,17 @@ pub(crate) fn snippet_text_document_edit(
486} 490}
487 491
488pub(crate) fn resource_op( 492pub(crate) fn resource_op(
489 world: &WorldSnapshot, 493 snap: &GlobalStateSnapshot,
490 file_system_edit: FileSystemEdit, 494 file_system_edit: FileSystemEdit,
491) -> Result<lsp_types::ResourceOp> { 495) -> Result<lsp_types::ResourceOp> {
492 let res = match file_system_edit { 496 let res = match file_system_edit {
493 FileSystemEdit::CreateFile { source_root, path } => { 497 FileSystemEdit::CreateFile { source_root, path } => {
494 let uri = world.path_to_uri(source_root, &path)?; 498 let uri = snap.path_to_uri(source_root, &path)?;
495 lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) 499 lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None })
496 } 500 }
497 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { 501 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
498 let old_uri = world.file_id_to_uri(src)?; 502 let old_uri = snap.file_id_to_uri(src)?;
499 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; 503 let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?;
500 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) 504 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None })
501 } 505 }
502 }; 506 };
@@ -504,16 +508,16 @@ pub(crate) fn resource_op(
504} 508}
505 509
506pub(crate) fn snippet_workspace_edit( 510pub(crate) fn snippet_workspace_edit(
507 world: &WorldSnapshot, 511 snap: &GlobalStateSnapshot,
508 source_change: SourceChange, 512 source_change: SourceChange,
509) -> Result<lsp_ext::SnippetWorkspaceEdit> { 513) -> Result<lsp_ext::SnippetWorkspaceEdit> {
510 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); 514 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
511 for op in source_change.file_system_edits { 515 for op in source_change.file_system_edits {
512 let op = resource_op(&world, op)?; 516 let op = resource_op(&snap, op)?;
513 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); 517 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op));
514 } 518 }
515 for edit in source_change.source_file_edits { 519 for edit in source_change.source_file_edits {
516 let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?; 520 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?;
517 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); 521 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
518 } 522 }
519 let workspace_edit = 523 let workspace_edit =
@@ -522,11 +526,11 @@ pub(crate) fn snippet_workspace_edit(
522} 526}
523 527
524pub(crate) fn workspace_edit( 528pub(crate) fn workspace_edit(
525 world: &WorldSnapshot, 529 snap: &GlobalStateSnapshot,
526 source_change: SourceChange, 530 source_change: SourceChange,
527) -> Result<lsp_types::WorkspaceEdit> { 531) -> Result<lsp_types::WorkspaceEdit> {
528 assert!(!source_change.is_snippet); 532 assert!(!source_change.is_snippet);
529 snippet_workspace_edit(world, source_change).map(|it| it.into()) 533 snippet_workspace_edit(snap, source_change).map(|it| it.into())
530} 534}
531 535
532impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { 536impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
@@ -565,13 +569,13 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
565} 569}
566 570
567pub fn call_hierarchy_item( 571pub fn call_hierarchy_item(
568 world: &WorldSnapshot, 572 snap: &GlobalStateSnapshot,
569 target: NavigationTarget, 573 target: NavigationTarget,
570) -> Result<lsp_types::CallHierarchyItem> { 574) -> Result<lsp_types::CallHierarchyItem> {
571 let name = target.name().to_string(); 575 let name = target.name().to_string();
572 let detail = target.description().map(|it| it.to_string()); 576 let detail = target.description().map(|it| it.to_string());
573 let kind = symbol_kind(target.kind()); 577 let kind = symbol_kind(target.kind());
574 let (uri, range, selection_range) = location_info(world, target)?; 578 let (uri, range, selection_range) = location_info(snap, target)?;
575 Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) 579 Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range })
576} 580}
577 581
@@ -620,14 +624,14 @@ fn main() <fold>{
620} 624}
621 625
622pub(crate) fn unresolved_code_action( 626pub(crate) fn unresolved_code_action(
623 world: &WorldSnapshot, 627 snap: &GlobalStateSnapshot,
624 assist: Assist, 628 assist: Assist,
625 index: usize, 629 index: usize,
626) -> Result<lsp_ext::CodeAction> { 630) -> Result<lsp_ext::CodeAction> {
627 let res = lsp_ext::CodeAction { 631 let res = lsp_ext::CodeAction {
628 title: assist.label, 632 title: assist.label,
629 id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())), 633 id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())),
630 group: assist.group.filter(|_| world.config.client_caps.code_action_group).map(|gr| gr.0), 634 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
631 kind: Some(String::new()), 635 kind: Some(String::new()),
632 edit: None, 636 edit: None,
633 command: None, 637 command: None,
@@ -636,25 +640,25 @@ pub(crate) fn unresolved_code_action(
636} 640}
637 641
638pub(crate) fn resolved_code_action( 642pub(crate) fn resolved_code_action(
639 world: &WorldSnapshot, 643 snap: &GlobalStateSnapshot,
640 assist: ResolvedAssist, 644 assist: ResolvedAssist,
641) -> Result<lsp_ext::CodeAction> { 645) -> Result<lsp_ext::CodeAction> {
642 let change = assist.source_change; 646 let change = assist.source_change;
643 unresolved_code_action(world, assist.assist, 0).and_then(|it| { 647 unresolved_code_action(snap, assist.assist, 0).and_then(|it| {
644 Ok(lsp_ext::CodeAction { 648 Ok(lsp_ext::CodeAction {
645 id: None, 649 id: None,
646 edit: Some(snippet_workspace_edit(world, change)?), 650 edit: Some(snippet_workspace_edit(snap, change)?),
647 ..it 651 ..it
648 }) 652 })
649 }) 653 })
650} 654}
651 655
652pub(crate) fn runnable( 656pub(crate) fn runnable(
653 world: &WorldSnapshot, 657 snap: &GlobalStateSnapshot,
654 file_id: FileId, 658 file_id: FileId,
655 runnable: Runnable, 659 runnable: Runnable,
656) -> Result<lsp_ext::Runnable> { 660) -> Result<lsp_ext::Runnable> {
657 let spec = CargoTargetSpec::for_file(world, file_id)?; 661 let spec = CargoTargetSpec::for_file(snap, file_id)?;
658 let target = spec.as_ref().map(|s| s.target.clone()); 662 let target = spec.as_ref().map(|s| s.target.clone());
659 let (cargo_args, executable_args) = 663 let (cargo_args, executable_args) =
660 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; 664 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
@@ -667,14 +671,14 @@ pub(crate) fn runnable(
667 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) 671 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
668 } 672 }
669 }; 673 };
670 let location = location_link(world, None, runnable.nav)?; 674 let location = location_link(snap, None, runnable.nav)?;
671 675
672 Ok(lsp_ext::Runnable { 676 Ok(lsp_ext::Runnable {
673 label, 677 label,
674 location: Some(location), 678 location: Some(location),
675 kind: lsp_ext::RunnableKind::Cargo, 679 kind: lsp_ext::RunnableKind::Cargo,
676 args: lsp_ext::CargoRunnable { 680 args: lsp_ext::CargoRunnable {
677 workspace_root: world.workspace_root_for(file_id).map(|root| root.to_owned()), 681 workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()),
678 cargo_args, 682 cargo_args,
679 executable_args, 683 executable_args,
680 }, 684 },
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index e18f973b8..ad3476310 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -59,55 +59,6 @@ use std::collections::Spam;
59} 59}
60 60
61#[test] 61#[test]
62fn test_runnables_no_project() {
63 if skip_slow_tests() {
64 return;
65 }
66
67 let server = project(
68 r"
69//- lib.rs
70#[test]
71fn foo() {
72}
73",
74 );
75 server.wait_until_workspace_is_loaded();
76 server.request::<Runnables>(
77 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
78 json!([
79 {
80 "args": {
81 "cargoArgs": ["test"],
82 "executableArgs": ["foo", "--nocapture"],
83 },
84 "kind": "cargo",
85 "label": "test foo",
86 "location": {
87 "targetRange": {
88 "end": { "character": 1, "line": 2 },
89 "start": { "character": 0, "line": 0 }
90 },
91 "targetSelectionRange": {
92 "end": { "character": 6, "line": 1 },
93 "start": { "character": 3, "line": 1 }
94 },
95 "targetUri": "file:///[..]/lib.rs"
96 }
97 },
98 {
99 "args": {
100 "cargoArgs": ["check", "--workspace"],
101 "executableArgs": [],
102 },
103 "kind": "cargo",
104 "label": "cargo check --workspace"
105 }
106 ]),
107 );
108}
109
110#[test]
111fn test_runnables_project() { 62fn test_runnables_project() {
112 if skip_slow_tests() { 63 if skip_slow_tests() {
113 return; 64 return;
@@ -347,6 +298,7 @@ fn main() {}
347 } 298 }
348 ] 299 ]
349 }, 300 },
301 "kind": "quickfix",
350 "title": "Create module" 302 "title": "Create module"
351 }]), 303 }]),
352 ); 304 );
@@ -379,8 +331,7 @@ fn test_missing_module_code_action_in_json_project() {
379 "root_module": path.join("src/lib.rs"), 331 "root_module": path.join("src/lib.rs"),
380 "deps": [], 332 "deps": [],
381 "edition": "2015", 333 "edition": "2015",
382 "atom_cfgs": [], 334 "cfg": [ "cfg_atom_1", "feature=cfg_1"],
383 "key_value_cfgs": {}
384 } ] 335 } ]
385 }); 336 });
386 337
@@ -418,6 +369,7 @@ fn main() {{}}
418 } 369 }
419 ] 370 ]
420 }, 371 },
372 "kind": "quickfix",
421 "title": "Create module" 373 "title": "Create module"
422 }]), 374 }]),
423 ); 375 );
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 66a6f4d54..30d03b622 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -19,8 +19,9 @@ use serde_json::{to_string_pretty, Value};
19use tempfile::TempDir; 19use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture}; 20use test_utils::{find_mismatch, parse_fixture};
21 21
22use ra_project_model::ProjectManifest;
22use rust_analyzer::{ 23use rust_analyzer::{
23 config::{ClientCapsConfig, Config}, 24 config::{ClientCapsConfig, Config, LinkedProject},
24 main_loop, 25 main_loop,
25}; 26};
26 27
@@ -42,7 +43,7 @@ impl<'a> Project<'a> {
42 self 43 self
43 } 44 }
44 45
45 pub fn root(mut self, path: &str) -> Project<'a> { 46 pub(crate) fn root(mut self, path: &str) -> Project<'a> {
46 self.roots.push(path.into()); 47 self.roots.push(path.into());
47 self 48 self
48 } 49 }
@@ -74,7 +75,16 @@ impl<'a> Project<'a> {
74 paths.push((path, entry.text)); 75 paths.push((path, entry.text));
75 } 76 }
76 77
77 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); 78 let mut roots =
79 self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::<Vec<_>>();
80 if roots.is_empty() {
81 roots.push(tmp_dir.path().to_path_buf());
82 }
83 let linked_projects = roots
84 .into_iter()
85 .map(|it| ProjectManifest::discover_single(&it).unwrap())
86 .map(LinkedProject::from)
87 .collect::<Vec<_>>();
78 88
79 let mut config = Config { 89 let mut config = Config {
80 client_caps: ClientCapsConfig { 90 client_caps: ClientCapsConfig {
@@ -84,6 +94,7 @@ impl<'a> Project<'a> {
84 ..Default::default() 94 ..Default::default()
85 }, 95 },
86 with_sysroot: self.with_sysroot, 96 with_sysroot: self.with_sysroot,
97 linked_projects,
87 ..Config::default() 98 ..Config::default()
88 }; 99 };
89 100
@@ -91,7 +102,7 @@ impl<'a> Project<'a> {
91 f(&mut config) 102 f(&mut config)
92 } 103 }
93 104
94 Server::new(tmp_dir, config, roots, paths) 105 Server::new(tmp_dir, config, paths)
95 } 106 }
96} 107}
97 108
@@ -109,20 +120,12 @@ pub struct Server {
109} 120}
110 121
111impl Server { 122impl Server {
112 fn new( 123 fn new(dir: TempDir, config: Config, files: Vec<(PathBuf, String)>) -> Server {
113 dir: TempDir,
114 config: Config,
115 roots: Vec<PathBuf>,
116 files: Vec<(PathBuf, String)>,
117 ) -> Server {
118 let path = dir.path().to_path_buf();
119
120 let roots = if roots.is_empty() { vec![path] } else { roots };
121 let (connection, client) = Connection::memory(); 124 let (connection, client) = Connection::memory();
122 125
123 let _thread = jod_thread::Builder::new() 126 let _thread = jod_thread::Builder::new()
124 .name("test server".to_string()) 127 .name("test server".to_string())
125 .spawn(move || main_loop(roots, config, connection).unwrap()) 128 .spawn(move || main_loop(config, connection).unwrap())
126 .expect("failed to spawn a thread"); 129 .expect("failed to spawn a thread");
127 130
128 let res = 131 let res =
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 71a57fba2..c0356344c 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -124,3 +124,8 @@ pub fn replace(buf: &mut String, from: char, to: &str) {
124 // FIXME: do this in place. 124 // FIXME: do this in place.
125 *buf = buf.replace(from, to) 125 *buf = buf.replace(from, to)
126} 126}
127
128pub fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> {
129 let idx = haystack.find(delim)?;
130 Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
131}
diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml
index 4d185b01c..8840bf36a 100644
--- a/crates/test_utils/Cargo.toml
+++ b/crates/test_utils/Cargo.toml
@@ -14,4 +14,5 @@ serde_json = "1.0.48"
14relative-path = "1.0.0" 14relative-path = "1.0.0"
15rustc-hash = "1.1.0" 15rustc-hash = "1.1.0"
16 16
17ra_cfg = { path = "../ra_cfg" } \ No newline at end of file 17ra_cfg = { path = "../ra_cfg" }
18stdx = { path = "../stdx" } \ No newline at end of file
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 1bd97215c..2141bfc20 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -15,6 +15,7 @@ use std::{
15}; 15};
16 16
17pub use ra_cfg::CfgOptions; 17pub use ra_cfg::CfgOptions;
18use stdx::split1;
18 19
19pub use relative_path::{RelativePath, RelativePathBuf}; 20pub use relative_path::{RelativePath, RelativePathBuf};
20pub use rustc_hash::FxHashMap; 21pub use rustc_hash::FxHashMap;
@@ -332,11 +333,6 @@ fn parse_meta(meta: &str) -> FixtureMeta {
332 FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env }) 333 FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env })
333} 334}
334 335
335fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> {
336 let idx = haystack.find(delim)?;
337 Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
338}
339
340/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. 336/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines.
341/// This allows fixtures to start off in a different indentation, e.g. to align the first line with 337/// This allows fixtures to start off in a different indentation, e.g. to align the first line with
342/// the other lines visually: 338/// the other lines visually:
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 65cc9fc12..1de5a2aab 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -30,7 +30,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
30 30
31* [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue) 31* [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue)
32 are good issues to get into the project. 32 are good issues to get into the project.
33* [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor) 33* [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions)
34 issues have links to the code in question and tests. 34 issues have links to the code in question and tests.
35* [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), 35* [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy),
36 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), 36 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium),
@@ -117,6 +117,109 @@ Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats
117path/to/some/rust/crate` to run a batch analysis. This is primarily useful for 117path/to/some/rust/crate` to run a batch analysis. This is primarily useful for
118performance optimizations, or for bug minimization. 118performance optimizations, or for bug minimization.
119 119
120# Code Style & Review Process
121
122Our approach to "clean code" is two fold:
123
124* We generally don't block PRs on style changes.
125* At the same time, all code in rust-analyzer is constantly refactored.
126
127It is explicitly OK for reviewer to flag only some nits in the PR, and than send a follow up cleanup PR for things which are easier to explain by example, cc-ing the original author.
128Sending small cleanup PRs (like rename a single local variable) is encouraged.
129
130## Scale of Changes
131
132Everyone knows that it's better to send small & focused pull requests.
133The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
134
135The main thing too keep an eye on is the boundaries between various components.
136There are three kinds of changes:
137
1381. Internals of a single component are changed.
139 Specifically, you don't change any `pub` items.
140 A good example here would be an addition of a new assist.
141
1422. API of a component is expanded.
143 Specifically, you add a new `pub` function which wasn't there before.
144 A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups.
145
1463. A new dependency between components is introduced.
147 Specifically, you add a `pub use` reexport from another crate or you add a new line to `[dependencies]` section of `Cargo.toml`.
148 A good example here would be adding reference search capability to the assists crates.
149
150For the first group, the change is generally merged as long as:
151
152* it works for the happy case,
153* it has tests,
154* it doesn't panic for unhappy case.
155
156For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
157The new API needs to be right (or at least easy to change later).
158The actual implementation doesn't matter that much.
159It's very important to minimize the amount of changed lines of code for changes of the second kind.
160Often, you start doing change of the first kind, only to realise that you need to elevate to a change of the second kind.
161In this case, we'll probably ask you to split API changes into a separate PR.
162
163Changes of the third group should be pretty rare, so we don't specify any specific process for them.
164That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
165
166Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
167https://www.tedinski.com/2018/02/06/system-boundaries.html
168
169## Order of Imports
170
171We separate import groups with blank lines
172
173```
174mod x;
175mod y;
176
177use std::{ ... }
178
179use crate_foo::{ ... }
180use crate_bar::{ ... }
181
182use crate::{}
183
184use super::{} // but prefer `use crate::`
185```
186
187## Order of Items
188
189Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on.
190People read things from top to bottom, so place most important things first.
191
192Specifically, if all items except one are private, always put the non-private item on top.
193
194Put `struct`s and `enum`s first, functions and impls last.
195
196Do
197
198```
199// Good
200struct Foo {
201 bars: Vec<Bar>
202}
203
204struct Bar;
205```
206
207rather than
208
209```
210// Not as good
211struct Bar;
212
213struct Foo {
214 bars: Vec<Bar>
215}
216```
217
218## Documentation
219
220For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
221If the line is too long, you want to split the sentence in two :-)
222
120# Logging 223# Logging
121 224
122Logging is done by both rust-analyzer and VS Code, so it might be tricky to 225Logging is done by both rust-analyzer and VS Code, so it might be tricky to
diff --git a/docs/user/generated_assists.adoc b/docs/user/generated_assists.adoc
deleted file mode 100644
index 4d2fb31d4..000000000
--- a/docs/user/generated_assists.adoc
+++ /dev/null
@@ -1,1015 +0,0 @@
1[discrete]
2=== `add_custom_impl`
3**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_custom_impl.rs#L14[add_custom_impl.rs]
4
5Adds impl block for derived trait.
6
7.Before
8```rust
9#[derive(Deb┃ug, Display)]
10struct S;
11```
12
13.After
14```rust
15#[derive(Display)]
16struct S;
17
18impl Debug for S {
19 $0
20}
21```
22
23
24[discrete]
25=== `add_derive`
26**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_derive.rs#L9[add_derive.rs]
27
28Adds a new `#[derive()]` clause to a struct or enum.
29
30.Before
31```rust
32struct Point {
33 x: u32,
34 y: u32,┃
35}
36```
37
38.After
39```rust
40#[derive($0)]
41struct Point {
42 x: u32,
43 y: u32,
44}
45```
46
47
48[discrete]
49=== `add_explicit_type`
50**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_explicit_type.rs#L9[add_explicit_type.rs]
51
52Specify type for a let binding.
53
54.Before
55```rust
56fn main() {
57 let x┃ = 92;
58}
59```
60
61.After
62```rust
63fn main() {
64 let x: i32 = 92;
65}
66```
67
68
69[discrete]
70=== `add_from_impl_for_enum`
71**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs#L7[add_from_impl_for_enum.rs]
72
73Adds a From impl for an enum variant with one tuple field.
74
75.Before
76```rust
77enum A { ┃One(u32) }
78```
79
80.After
81```rust
82enum A { One(u32) }
83
84impl From<u32> for A {
85 fn from(v: u32) -> Self {
86 A::One(v)
87 }
88}
89```
90
91
92[discrete]
93=== `add_function`
94**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_function.rs#L19[add_function.rs]
95
96Adds a stub function with a signature matching the function under the cursor.
97
98.Before
99```rust
100struct Baz;
101fn baz() -> Baz { Baz }
102fn foo() {
103 bar┃("", baz());
104}
105
106```
107
108.After
109```rust
110struct Baz;
111fn baz() -> Baz { Baz }
112fn foo() {
113 bar("", baz());
114}
115
116fn bar(arg: &str, baz: Baz) {
117 ${0:todo!()}
118}
119
120```
121
122
123[discrete]
124=== `add_hash`
125**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L65[raw_string.rs]
126
127Adds a hash to a raw string literal.
128
129.Before
130```rust
131fn main() {
132 r#"Hello,┃ World!"#;
133}
134```
135
136.After
137```rust
138fn main() {
139 r##"Hello, World!"##;
140}
141```
142
143
144[discrete]
145=== `add_impl`
146**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_impl.rs#L6[add_impl.rs]
147
148Adds a new inherent impl for a type.
149
150.Before
151```rust
152struct Ctx<T: Clone> {
153 data: T,┃
154}
155```
156
157.After
158```rust
159struct Ctx<T: Clone> {
160 data: T,
161}
162
163impl<T: Clone> Ctx<T> {
164 $0
165}
166```
167
168
169[discrete]
170=== `add_impl_default_members`
171**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L64[add_missing_impl_members.rs]
172
173Adds scaffold for overriding default impl members.
174
175.Before
176```rust
177trait Trait {
178 Type X;
179 fn foo(&self);
180 fn bar(&self) {}
181}
182
183impl Trait for () {
184 Type X = ();
185 fn foo(&self) {}┃
186
187}
188```
189
190.After
191```rust
192trait Trait {
193 Type X;
194 fn foo(&self);
195 fn bar(&self) {}
196}
197
198impl Trait for () {
199 Type X = ();
200 fn foo(&self) {}
201 $0fn bar(&self) {}
202
203}
204```
205
206
207[discrete]
208=== `add_impl_missing_members`
209**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L24[add_missing_impl_members.rs]
210
211Adds scaffold for required impl members.
212
213.Before
214```rust
215trait Trait<T> {
216 Type X;
217 fn foo(&self) -> T;
218 fn bar(&self) {}
219}
220
221impl Trait<u32> for () {┃
222
223}
224```
225
226.After
227```rust
228trait Trait<T> {
229 Type X;
230 fn foo(&self) -> T;
231 fn bar(&self) {}
232}
233
234impl Trait<u32> for () {
235 fn foo(&self) -> u32 {
236 ${0:todo!()}
237 }
238
239}
240```
241
242
243[discrete]
244=== `add_new`
245**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_new.rs#L12[add_new.rs]
246
247Adds a new inherent impl for a type.
248
249.Before
250```rust
251struct Ctx<T: Clone> {
252 data: T,┃
253}
254```
255
256.After
257```rust
258struct Ctx<T: Clone> {
259 data: T,
260}
261
262impl<T: Clone> Ctx<T> {
263 fn $0new(data: T) -> Self { Self { data } }
264}
265
266```
267
268
269[discrete]
270=== `add_turbo_fish`
271**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_turbo_fish.rs#L10[add_turbo_fish.rs]
272
273Adds `::<_>` to a call of a generic method or function.
274
275.Before
276```rust
277fn make<T>() -> T { todo!() }
278fn main() {
279 let x = make┃();
280}
281```
282
283.After
284```rust
285fn make<T>() -> T { todo!() }
286fn main() {
287 let x = make::<${0:_}>();
288}
289```
290
291
292[discrete]
293=== `apply_demorgan`
294**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/apply_demorgan.rs#L5[apply_demorgan.rs]
295
296Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
297This transforms expressions of the form `!l || !r` into `!(l && r)`.
298This also works with `&&`. This assist can only be applied with the cursor
299on either `||` or `&&`, with both operands being a negation of some kind.
300This means something of the form `!x` or `x != y`.
301
302.Before
303```rust
304fn main() {
305 if x != 4 ||┃ !y {}
306}
307```
308
309.After
310```rust
311fn main() {
312 if !(x == 4 && y) {}
313}
314```
315
316
317[discrete]
318=== `auto_import`
319**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/auto_import.rs#L18[auto_import.rs]
320
321If the name is unresolved, provides all possible imports for it.
322
323.Before
324```rust
325fn main() {
326 let map = HashMap┃::new();
327}
328```
329
330.After
331```rust
332use std::collections::HashMap;
333
334fn main() {
335 let map = HashMap::new();
336}
337```
338
339
340[discrete]
341=== `change_return_type_to_result`
342**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_return_type_to_result.rs#L8[change_return_type_to_result.rs]
343
344Change the function's return type to Result.
345
346.Before
347```rust
348fn foo() -> i32┃ { 42i32 }
349```
350
351.After
352```rust
353fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
354```
355
356
357[discrete]
358=== `change_visibility`
359**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_visibility.rs#L14[change_visibility.rs]
360
361Adds or changes existing visibility specifier.
362
363.Before
364```rust
365┃fn frobnicate() {}
366```
367
368.After
369```rust
370pub(crate) fn frobnicate() {}
371```
372
373
374[discrete]
375=== `convert_to_guarded_return`
376**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/early_return.rs#L21[early_return.rs]
377
378Replace a large conditional with a guarded return.
379
380.Before
381```rust
382fn main() {
383 ┃if cond {
384 foo();
385 bar();
386 }
387}
388```
389
390.After
391```rust
392fn main() {
393 if !cond {
394 return;
395 }
396 foo();
397 bar();
398}
399```
400
401
402[discrete]
403=== `fill_match_arms`
404**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fill_match_arms.rs#L14[fill_match_arms.rs]
405
406Adds missing clauses to a `match` expression.
407
408.Before
409```rust
410enum Action { Move { distance: u32 }, Stop }
411
412fn handle(action: Action) {
413 match action {
414 ┃
415 }
416}
417```
418
419.After
420```rust
421enum Action { Move { distance: u32 }, Stop }
422
423fn handle(action: Action) {
424 match action {
425 $0Action::Move { distance } => {}
426 Action::Stop => {}
427 }
428}
429```
430
431
432[discrete]
433=== `fix_visibility`
434**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fix_visibility.rs#L13[fix_visibility.rs]
435
436Makes inaccessible item public.
437
438.Before
439```rust
440mod m {
441 fn frobnicate() {}
442}
443fn main() {
444 m::frobnicate┃() {}
445}
446```
447
448.After
449```rust
450mod m {
451 $0pub(crate) fn frobnicate() {}
452}
453fn main() {
454 m::frobnicate() {}
455}
456```
457
458
459[discrete]
460=== `flip_binexpr`
461**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_binexpr.rs#L5[flip_binexpr.rs]
462
463Flips operands of a binary expression.
464
465.Before
466```rust
467fn main() {
468 let _ = 90 +┃ 2;
469}
470```
471
472.After
473```rust
474fn main() {
475 let _ = 2 + 90;
476}
477```
478
479
480[discrete]
481=== `flip_comma`
482**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_comma.rs#L5[flip_comma.rs]
483
484Flips two comma-separated items.
485
486.Before
487```rust
488fn main() {
489 ((1, 2),┃ (3, 4));
490}
491```
492
493.After
494```rust
495fn main() {
496 ((3, 4), (1, 2));
497}
498```
499
500
501[discrete]
502=== `flip_trait_bound`
503**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_trait_bound.rs#L9[flip_trait_bound.rs]
504
505Flips two trait bounds.
506
507.Before
508```rust
509fn foo<T: Clone +┃ Copy>() { }
510```
511
512.After
513```rust
514fn foo<T: Copy + Clone>() { }
515```
516
517
518[discrete]
519=== `inline_local_variable`
520**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/inline_local_variable.rs#L13[inline_local_variable.rs]
521
522Inlines local variable.
523
524.Before
525```rust
526fn main() {
527 let x┃ = 1 + 2;
528 x * 4;
529}
530```
531
532.After
533```rust
534fn main() {
535 (1 + 2) * 4;
536}
537```
538
539
540[discrete]
541=== `introduce_named_lifetime`
542**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_named_lifetime.rs#L12[introduce_named_lifetime.rs]
543
544Change an anonymous lifetime to a named lifetime.
545
546.Before
547```rust
548impl Cursor<'_┃> {
549 fn node(self) -> &SyntaxNode {
550 match self {
551 Cursor::Replace(node) | Cursor::Before(node) => node,
552 }
553 }
554}
555```
556
557.After
558```rust
559impl<'a> Cursor<'a> {
560 fn node(self) -> &SyntaxNode {
561 match self {
562 Cursor::Replace(node) | Cursor::Before(node) => node,
563 }
564 }
565}
566```
567
568
569[discrete]
570=== `introduce_variable`
571**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_variable.rs#L14[introduce_variable.rs]
572
573Extracts subexpression into a variable.
574
575.Before
576```rust
577fn main() {
578 ┃(1 + 2)┃ * 4;
579}
580```
581
582.After
583```rust
584fn main() {
585 let $0var_name = (1 + 2);
586 var_name * 4;
587}
588```
589
590
591[discrete]
592=== `invert_if`
593**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/invert_if.rs#L12[invert_if.rs]
594
595Apply invert_if
596This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
597This also works with `!=`. This assist can only be applied with the cursor
598on `if`.
599
600.Before
601```rust
602fn main() {
603 if┃ !y { A } else { B }
604}
605```
606
607.After
608```rust
609fn main() {
610 if y { B } else { A }
611}
612```
613
614
615[discrete]
616=== `make_raw_string`
617**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L10[raw_string.rs]
618
619Adds `r#` to a plain string literal.
620
621.Before
622```rust
623fn main() {
624 "Hello,┃ World!";
625}
626```
627
628.After
629```rust
630fn main() {
631 r#"Hello, World!"#;
632}
633```
634
635
636[discrete]
637=== `make_usual_string`
638**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L39[raw_string.rs]
639
640Turns a raw string into a plain string.
641
642.Before
643```rust
644fn main() {
645 r#"Hello,┃ "World!""#;
646}
647```
648
649.After
650```rust
651fn main() {
652 "Hello, \"World!\"";
653}
654```
655
656
657[discrete]
658=== `merge_imports`
659**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_imports.rs#L14[merge_imports.rs]
660
661Merges two imports with a common prefix.
662
663.Before
664```rust
665use std::┃fmt::Formatter;
666use std::io;
667```
668
669.After
670```rust
671use std::{fmt::Formatter, io};
672```
673
674
675[discrete]
676=== `merge_match_arms`
677**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_match_arms.rs#L11[merge_match_arms.rs]
678
679Merges identical match arms.
680
681.Before
682```rust
683enum Action { Move { distance: u32 }, Stop }
684
685fn handle(action: Action) {
686 match action {
687 ┃Action::Move(..) => foo(),
688 Action::Stop => foo(),
689 }
690}
691```
692
693.After
694```rust
695enum Action { Move { distance: u32 }, Stop }
696
697fn handle(action: Action) {
698 match action {
699 Action::Move(..) | Action::Stop => foo(),
700 }
701}
702```
703
704
705[discrete]
706=== `move_arm_cond_to_match_guard`
707**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L56[move_guard.rs]
708
709Moves if expression from match arm body into a guard.
710
711.Before
712```rust
713enum Action { Move { distance: u32 }, Stop }
714
715fn handle(action: Action) {
716 match action {
717 Action::Move { distance } => ┃if distance > 10 { foo() },
718 _ => (),
719 }
720}
721```
722
723.After
724```rust
725enum Action { Move { distance: u32 }, Stop }
726
727fn handle(action: Action) {
728 match action {
729 Action::Move { distance } if distance > 10 => foo(),
730 _ => (),
731 }
732}
733```
734
735
736[discrete]
737=== `move_bounds_to_where_clause`
738**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_bounds.rs#L10[move_bounds.rs]
739
740Moves inline type bounds to a where clause.
741
742.Before
743```rust
744fn apply<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U {
745 f(x)
746}
747```
748
749.After
750```rust
751fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
752 f(x)
753}
754```
755
756
757[discrete]
758=== `move_guard_to_arm_body`
759**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L8[move_guard.rs]
760
761Moves match guard into match arm body.
762
763.Before
764```rust
765enum Action { Move { distance: u32 }, Stop }
766
767fn handle(action: Action) {
768 match action {
769 Action::Move { distance } ┃if distance > 10 => foo(),
770 _ => (),
771 }
772}
773```
774
775.After
776```rust
777enum Action { Move { distance: u32 }, Stop }
778
779fn handle(action: Action) {
780 match action {
781 Action::Move { distance } => if distance > 10 { foo() },
782 _ => (),
783 }
784}
785```
786
787
788[discrete]
789=== `remove_dbg`
790**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_dbg.rs#L8[remove_dbg.rs]
791
792Removes `dbg!()` macro call.
793
794.Before
795```rust
796fn main() {
797 ┃dbg!(92);
798}
799```
800
801.After
802```rust
803fn main() {
804 92;
805}
806```
807
808
809[discrete]
810=== `remove_hash`
811**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L89[raw_string.rs]
812
813Removes a hash from a raw string literal.
814
815.Before
816```rust
817fn main() {
818 r#"Hello,┃ World!"#;
819}
820```
821
822.After
823```rust
824fn main() {
825 r"Hello, World!";
826}
827```
828
829
830[discrete]
831=== `remove_mut`
832**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_mut.rs#L5[remove_mut.rs]
833
834Removes the `mut` keyword.
835
836.Before
837```rust
838impl Walrus {
839 fn feed(&mut┃ self, amount: u32) {}
840}
841```
842
843.After
844```rust
845impl Walrus {
846 fn feed(&self, amount: u32) {}
847}
848```
849
850
851[discrete]
852=== `reorder_fields`
853**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/reorder_fields.rs#L10[reorder_fields.rs]
854
855Reorder the fields of record literals and record patterns in the same order as in
856the definition.
857
858.Before
859```rust
860struct Foo {foo: i32, bar: i32};
861const test: Foo = ┃Foo {bar: 0, foo: 1}
862```
863
864.After
865```rust
866struct Foo {foo: i32, bar: i32};
867const test: Foo = Foo {foo: 1, bar: 0}
868```
869
870
871[discrete]
872=== `replace_if_let_with_match`
873**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_if_let_with_match.rs#L13[replace_if_let_with_match.rs]
874
875Replaces `if let` with an else branch with a `match` expression.
876
877.Before
878```rust
879enum Action { Move { distance: u32 }, Stop }
880
881fn handle(action: Action) {
882 ┃if let Action::Move { distance } = action {
883 foo(distance)
884 } else {
885 bar()
886 }
887}
888```
889
890.After
891```rust
892enum Action { Move { distance: u32 }, Stop }
893
894fn handle(action: Action) {
895 match action {
896 Action::Move { distance } => foo(distance),
897 _ => bar(),
898 }
899}
900```
901
902
903[discrete]
904=== `replace_let_with_if_let`
905**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_let_with_if_let.rs#L14[replace_let_with_if_let.rs]
906
907Replaces `let` with an `if-let`.
908
909.Before
910```rust
911
912fn main(action: Action) {
913 ┃let x = compute();
914}
915
916fn compute() -> Option<i32> { None }
917```
918
919.After
920```rust
921
922fn main(action: Action) {
923 if let Some(x) = compute() {
924 }
925}
926
927fn compute() -> Option<i32> { None }
928```
929
930
931[discrete]
932=== `replace_qualified_name_with_use`
933**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs#L6[replace_qualified_name_with_use.rs]
934
935Adds a use statement for a given fully-qualified name.
936
937.Before
938```rust
939fn process(map: std::collections::┃HashMap<String, String>) {}
940```
941
942.After
943```rust
944use std::collections::HashMap;
945
946fn process(map: HashMap<String, String>) {}
947```
948
949
950[discrete]
951=== `replace_unwrap_with_match`
952**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs#L17[replace_unwrap_with_match.rs]
953
954Replaces `unwrap` a `match` expression. Works for Result and Option.
955
956.Before
957```rust
958enum Result<T, E> { Ok(T), Err(E) }
959fn main() {
960 let x: Result<i32, i32> = Result::Ok(92);
961 let y = x.┃unwrap();
962}
963```
964
965.After
966```rust
967enum Result<T, E> { Ok(T), Err(E) }
968fn main() {
969 let x: Result<i32, i32> = Result::Ok(92);
970 let y = match x {
971 Ok(a) => a,
972 $0_ => unreachable!(),
973 };
974}
975```
976
977
978[discrete]
979=== `split_import`
980**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/split_import.rs#L7[split_import.rs]
981
982Wraps the tail of import into braces.
983
984.Before
985```rust
986use std::┃collections::HashMap;
987```
988
989.After
990```rust
991use std::{collections::HashMap};
992```
993
994
995[discrete]
996=== `unwrap_block`
997**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/unwrap_block.rs#L9[unwrap_block.rs]
998
999This assist removes if...else, for, while and loop control statements to just keep the body.
1000
1001.Before
1002```rust
1003fn foo() {
1004 if true {┃
1005 println!("foo");
1006 }
1007}
1008```
1009
1010.After
1011```rust
1012fn foo() {
1013 println!("foo");
1014}
1015```
diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc
deleted file mode 100644
index 12812fa0b..000000000
--- a/docs/user/generated_features.adoc
+++ /dev/null
@@ -1,298 +0,0 @@
1=== Expand Macro Recursively
2**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/expand_macro.rs#L15[expand_macro.rs]
3
4Shows the full macro expansion of the macro at current cursor.
5
6|===
7| Editor | Action Name
8
9| VS Code | **Rust Analyzer: Expand macro recursively**
10|===
11
12
13=== Extend Selection
14**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/extend_selection.rs#L15[extend_selection.rs]
15
16Extends the current selection to the encompassing syntactic construct
17(expression, statement, item, module, etc). It works with multiple cursors.
18
19|===
20| Editor | Shortcut
21
22| VS Code | kbd:[Ctrl+Shift+→]
23|===
24
25
26=== File Structure
27**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/display/structure.rs#L17[structure.rs]
28
29Provides a tree of the symbols defined in the file. Can be used to
30
31* fuzzy search symbol in a file (super useful)
32* draw breadcrumbs to describe the context around the cursor
33* draw outline of the file
34
35|===
36| Editor | Shortcut
37
38| VS Code | kbd:[Ctrl+Shift+O]
39|===
40
41
42=== Go to Definition
43**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_definition.rs#L18[goto_definition.rs]
44
45Navigates to the definition of an identifier.
46
47|===
48| Editor | Shortcut
49
50| VS Code | kbd:[F12]
51|===
52
53
54=== Go to Implementation
55**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_implementation.rs#L7[goto_implementation.rs]
56
57Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
58
59|===
60| Editor | Shortcut
61
62| VS Code | kbd:[Ctrl+F12]
63|===
64
65
66=== Go to Type Definition
67**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_type_definition.rs#L6[goto_type_definition.rs]
68
69Navigates to the type of an identifier.
70
71|===
72| Editor | Action Name
73
74| VS Code | **Go to Type Definition*
75|===
76
77
78=== Hover
79**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L63[hover.rs]
80
81Shows additional information, like type of an expression or documentation for definition when "focusing" code.
82Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
83
84
85=== Inlay Hints
86**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/inlay_hints.rs#L40[inlay_hints.rs]
87
88rust-analyzer shows additional information inline with the source code.
89Editors usually render this using read-only virtual text snippets interspersed with code.
90
91rust-analyzer shows hits for
92
93* types of local variables
94* names of function arguments
95* types of chained expressions
96
97**Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations.
98This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
99https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
100
101|===
102| Editor | Action Name
103
104| VS Code | **Rust Analyzer: Toggle inlay hints*
105|===
106
107
108=== Join Lines
109**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/join_lines.rs#L12[join_lines.rs]
110
111Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
112
113|===
114| Editor | Action Name
115
116| VS Code | **Rust Analyzer: Join lines**
117|===
118
119
120=== Magic Completions
121**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/completion.rs#L38[completion.rs]
122
123In addition to usual reference completion, rust-analyzer provides some ✨magic✨
124completions as well:
125
126Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
127is placed at the appropriate position. Even though `if` is easy to type, you
128still want to complete it, to get ` { }` for free! `return` is inserted with a
129space or `;` depending on the return type of the function.
130
131When completing a function call, `()` are automatically inserted. If a function
132takes arguments, the cursor is positioned inside the parenthesis.
133
134There are postfix completions, which can be triggered by typing something like
135`foo().if`. The word after `.` determines postfix completion. Possible variants are:
136
137- `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
138- `expr.match` -> `match expr {}`
139- `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
140- `expr.ref` -> `&expr`
141- `expr.refm` -> `&mut expr`
142- `expr.not` -> `!expr`
143- `expr.dbg` -> `dbg!(expr)`
144
145There also snippet completions:
146
147.Expressions
148- `pd` -> `println!("{:?}")`
149- `ppd` -> `println!("{:#?}")`
150
151.Items
152- `tfn` -> `#[test] fn f(){}`
153- `tmod` ->
154```rust
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_fn() {}
161}
162```
163
164
165=== Matching Brace
166**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/matching_brace.rs#L3[matching_brace.rs]
167
168If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
169moves cursor to the matching brace. It uses the actual parser to determine
170braces, so it won't confuse generics with comparisons.
171
172|===
173| Editor | Action Name
174
175| VS Code | **Rust Analyzer: Find matching brace**
176|===
177
178
179=== On Typing Assists
180**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs#L35[typing.rs]
181
182Some features trigger on typing certain characters:
183
184- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
185- Enter inside comments automatically inserts `///`
186- typing `.` in a chain method call auto-indents
187
188
189=== Parent Module
190**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/parent_module.rs#L12[parent_module.rs]
191
192Navigates to the parent module of the current module.
193
194|===
195| Editor | Action Name
196
197| VS Code | **Rust Analyzer: Locate parent module**
198|===
199
200
201=== Run
202**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs#L45[runnables.rs]
203
204Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
205location**. Super useful for repeatedly running just a single test. Do bind this
206to a shortcut!
207
208|===
209| Editor | Action Name
210
211| VS Code | **Rust Analyzer: Run**
212|===
213
214
215=== Semantic Syntax Highlighting
216**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs#L33[syntax_highlighting.rs]
217
218rust-analyzer highlights the code semantically.
219For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
220rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
221It's up to the client to map those to specific colors.
222
223The general rule is that a reference to an entity gets colored the same way as the entity itself.
224We also give special modifier for `mut` and `&mut` local variables.
225
226
227=== Show Syntax Tree
228**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_tree.rs#L9[syntax_tree.rs]
229
230Shows the parse tree of the current file. It exists mostly for debugging
231rust-analyzer itself.
232
233|===
234| Editor | Action Name
235
236| VS Code | **Rust Analyzer: Show Syntax Tree**
237|===
238
239
240=== Status
241**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/status.rs#L27[status.rs]
242
243Shows internal statistic about memory usage of rust-analyzer.
244
245|===
246| Editor | Action Name
247
248| VS Code | **Rust Analyzer: Status**
249|===
250
251
252=== Structural Seach and Replace
253**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/ssr.rs#L26[ssr.rs]
254
255Search and replace with named wildcards that will match any expression.
256The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
257A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement.
258Available via the command `rust-analyzer.ssr`.
259
260```rust
261// Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)]
262
263// BEFORE
264String::from(foo(y + 5, z))
265
266// AFTER
267String::from((y + 5).foo(z))
268```
269
270|===
271| Editor | Action Name
272
273| VS Code | **Rust Analyzer: Structural Search Replace**
274|===
275
276
277=== Workspace Symbol
278**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs#L113[symbol_index.rs]
279
280Uses fuzzy-search to find types, modules and functions by name across your
281project and dependencies. This is **the** most useful feature, which improves code
282navigation tremendously. It mostly works on top of the built-in LSP
283functionality, however `#` and `*` symbols can be used to narrow down the
284search. Specifically,
285
286- `Foo` searches for `Foo` type in the current workspace
287- `foo#` searches for `foo` function in the current workspace
288- `Foo*` searches for `Foo` type among dependencies, including `stdlib`
289- `foo#*` searches for `foo` function among dependencies
290
291That is, `#` switches from "types" to all symbols, `*` switches from the current
292workspace to dependencies.
293
294|===
295| Editor | Shortcut
296
297| VS Code | kbd:[Ctrl+T]
298|===
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 202783fd9..ea714f49a 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -269,6 +269,57 @@ Gnome Builder currently has support for RLS, and there's no way to configure the
2691. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). 2691. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
2702. Enable the Rust Builder plugin. 2702. Enable the Rust Builder plugin.
271 271
272== Non-Cargo Based Projects
273
274rust-analyzer does not require Cargo.
275However, if you use some other build system, you'll have to describe the structure of your project for rust-analyzer in the `rust-project.json` format:
276
277[source,TypeScript]
278----
279interface JsonProject {
280 /// The set of paths containing the crates for this project.
281 /// Any `Crate` must be nested inside some `root`.
282 roots: string[];
283 /// The set of crates comprising the current project.
284 /// Must include all transitive dependencies as well as sysroot crate (libstd, libcore and such).
285 crates: Crate[];
286}
287
288interface Crate {
289 /// Path to the root module of the crate.
290 root_module: string;
291 /// Edition of the crate.
292 edition: "2015" | "2018";
293 /// Dependencies
294 deps: Dep[];
295 /// The set of cfgs activated for a given crate, like `["unix", "feature=foo", "feature=bar"]`.
296 cfg: string[];
297
298 /// value of the OUT_DIR env variable.
299 out_dir?: string;
300 /// For proc-macro crates, path to compiles proc-macro (.so file).
301 proc_macro_dylib_path?: string;
302}
303
304interface Dep {
305 /// Index of a crate in the `crates` array.
306 crate: number,
307 /// Name as should appear in the (implicit) `extern crate name` declaration.
308 name: string,
309}
310----
311
312This format is provisional and subject to change.
313Specifically, the `roots` setup will be different eventually.
314
315There are tree ways to feed `rust-project.json` to rust-analyzer:
316
317* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it.
318* Specify `"rust-analyzer.linkedProjects": [ "path/to/rust-project.json" ]` in the settings (and make sure that your LSP client sends settings as a part of initialize request).
319* Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline.
320
321See https://github.com/rust-analyzer/rust-project.json-example for a small example.
322
272== Features 323== Features
273 324
274include::./generated_features.adoc[] 325include::./generated_features.adoc[]
diff --git a/editors/code/package.json b/editors/code/package.json
index d8f4287fd..30ab7ba4a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -475,6 +475,25 @@
475 "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", 475 "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
476 "type": "boolean", 476 "type": "boolean",
477 "default": true 477 "default": true
478 },
479 "rust-analyzer.linkedProjects": {
480 "markdownDescription": [
481 "Disable project auto-discovery in favor of explicitly specified set of projects.",
482 "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format"
483 ],
484 "type": "array",
485 "items": {
486 "type": [
487 "string",
488 "object"
489 ]
490 },
491 "default": null
492 },
493 "rust-analyzer.withSysroot": {
494 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
495 "type": "boolean",
496 "default": true
478 } 497 }
479 } 498 }
480 }, 499 },
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 5511c01d5..f5f4b964a 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -18,8 +18,10 @@ use std::{
18use crate::{not_bash::fs2, project_root, Result}; 18use crate::{not_bash::fs2, project_root, Result};
19 19
20pub use self::{ 20pub use self::{
21 gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs, 21 gen_assists_docs::{generate_assists_docs, generate_assists_tests},
22 gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax, 22 gen_feature_docs::generate_feature_docs,
23 gen_parser_tests::generate_parser_tests,
24 gen_syntax::generate_syntax,
23}; 25};
24 26
25const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; 27const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index 6c1be5350..526941f73 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -7,16 +7,17 @@ use crate::{
7 project_root, rust_files, Result, 7 project_root, rust_files, Result,
8}; 8};
9 9
10pub fn generate_assists_docs(mode: Mode) -> Result<()> { 10pub fn generate_assists_tests(mode: Mode) -> Result<()> {
11 let assists = Assist::collect()?; 11 let assists = Assist::collect()?;
12 generate_tests(&assists, mode)?; 12 generate_tests(&assists, mode)
13}
13 14
15pub fn generate_assists_docs(mode: Mode) -> Result<()> {
16 let assists = Assist::collect()?;
14 let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); 17 let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
15 let contents = contents.trim().to_string() + "\n"; 18 let contents = contents.trim().to_string() + "\n";
16 let dst = project_root().join("docs/user/generated_assists.adoc"); 19 let dst = project_root().join("docs/user/generated_assists.adoc");
17 codegen::update(&dst, &contents, mode)?; 20 codegen::update(&dst, &contents, mode)
18
19 Ok(())
20} 21}
21 22
22#[derive(Debug)] 23#[derive(Debug)]
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 874957885..739f49f7b 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -160,6 +160,8 @@ pub fn run_release(dry_run: bool) -> Result<()> {
160 run!("git reset --hard tags/nightly")?; 160 run!("git reset --hard tags/nightly")?;
161 run!("git push")?; 161 run!("git push")?;
162 } 162 }
163 codegen::generate_assists_docs(Mode::Overwrite)?;
164 codegen::generate_feature_docs(Mode::Overwrite)?;
163 165
164 let website_root = project_root().join("../rust-analyzer.github.io"); 166 let website_root = project_root().join("../rust-analyzer.github.io");
165 let changelog_dir = website_root.join("./thisweek/_posts"); 167 let changelog_dir = website_root.join("./thisweek/_posts");
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 9d7cdd114..81bb3a33f 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -74,6 +74,7 @@ FLAGS:
74 args.finish()?; 74 args.finish()?;
75 codegen::generate_syntax(Mode::Overwrite)?; 75 codegen::generate_syntax(Mode::Overwrite)?;
76 codegen::generate_parser_tests(Mode::Overwrite)?; 76 codegen::generate_parser_tests(Mode::Overwrite)?;
77 codegen::generate_assists_tests(Mode::Overwrite)?;
77 codegen::generate_assists_docs(Mode::Overwrite)?; 78 codegen::generate_assists_docs(Mode::Overwrite)?;
78 codegen::generate_feature_docs(Mode::Overwrite)?; 79 codegen::generate_feature_docs(Mode::Overwrite)?;
79 Ok(()) 80 Ok(())
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 4ac5d929f..d38ac7f17 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -25,19 +25,12 @@ fn generated_tests_are_fresh() {
25 25
26#[test] 26#[test]
27fn generated_assists_are_fresh() { 27fn generated_assists_are_fresh() {
28 if let Err(error) = codegen::generate_assists_docs(Mode::Verify) { 28 if let Err(error) = codegen::generate_assists_tests(Mode::Verify) {
29 panic!("{}. Please update assists by running `cargo xtask codegen`", error); 29 panic!("{}. Please update assists by running `cargo xtask codegen`", error);
30 } 30 }
31} 31}
32 32
33#[test] 33#[test]
34fn generated_features_are_fresh() {
35 if let Err(error) = codegen::generate_feature_docs(Mode::Verify) {
36 panic!("{}. Please update features by running `cargo xtask codegen`", error);
37 }
38}
39
40#[test]
41fn check_code_formatting() { 34fn check_code_formatting() {
42 if let Err(error) = run_rustfmt(Mode::Verify) { 35 if let Err(error) = run_rustfmt(Mode::Verify) {
43 panic!("{}. Please format the code by running `cargo format`", error); 36 panic!("{}. Please format the code by running `cargo format`", error);
@@ -180,13 +173,11 @@ impl TidyDocs {
180} 173}
181 174
182fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool { 175fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
183 let mut cur_path = p; 176 p.strip_prefix(project_root())
184 while let Some(path) = cur_path.parent() { 177 .unwrap()
185 if dirs_to_exclude.iter().any(|dir| path.ends_with(dir)) { 178 .components()
186 return true; 179 .rev()
187 } 180 .skip(1)
188 cur_path = path; 181 .filter_map(|it| it.as_os_str().to_str())
189 } 182 .any(|it| dirs_to_exclude.contains(&it))
190
191 false
192} 183}