From beb79ed104c686d8704eb7042318eefea78770df Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Fri, 8 May 2020 16:59:52 -0700 Subject: [PATCH 01/24] Begin transition to new fields for JsonProject crate cfgs This starts the transition to a new method of documenting the cfgs that are enabled for a given crate in the json file. This is changing from a list of atoms and a dict of key:value pairs, to a list of strings that is equivalent to that returned by `rustc --print cfg ..`, and parsed in the same manner by rust-analyzer. This is the first of two changes, which adds the new field that contains the list of strings. Next change will complete the transition and remove the previous fields. --- crates/ra_project_model/src/json_project.rs | 79 +++++++++++++++++++ crates/ra_project_model/src/lib.rs | 10 +++ .../rust-analyzer/tests/heavy_tests/main.rs | 5 +- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs index b030c8a6a18f..bd2bae15e690 100644 --- a/crates/ra_project_model/src/json_project.rs +++ b/crates/ra_project_model/src/json_project.rs @@ -20,8 +20,17 @@ pub struct Crate { pub(crate) root_module: PathBuf, pub(crate) edition: Edition, pub(crate) deps: Vec, + + // This is the preferred method of providing cfg options. + #[serde(default)] + pub(crate) cfg: FxHashSet, + + // These two are here for transition only. + #[serde(default)] pub(crate) atom_cfgs: FxHashSet, + #[serde(default)] pub(crate) key_value_cfgs: FxHashMap, + pub(crate) out_dir: Option, pub(crate) proc_macro_dylib_path: Option, } @@ -54,3 +63,73 @@ pub struct JsonProject { pub(crate) roots: Vec, pub(crate) crates: Vec, } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_crate_deserialization() { + let raw_json = json!( { + "crate_id": 2, + "root_module": "this/is/a/file/path.rs", + "deps": [ + { + "crate": 1, + "name": "some_dep_crate" + }, + ], + "edition": "2015", + "cfg": [ + "atom_1", + "atom_2", + "feature=feature_1", + "feature=feature_2", + "other=value", + ], + + }); + + let krate: Crate = serde_json::from_value(raw_json).unwrap(); + + assert!(krate.cfg.contains(&"atom_1".to_string())); + assert!(krate.cfg.contains(&"atom_2".to_string())); + assert!(krate.cfg.contains(&"feature=feature_1".to_string())); + assert!(krate.cfg.contains(&"feature=feature_2".to_string())); + assert!(krate.cfg.contains(&"other=value".to_string())); + } + + #[test] + fn test_crate_deserialization_old_json() { + let raw_json = json!( { + "crate_id": 2, + "root_module": "this/is/a/file/path.rs", + "deps": [ + { + "crate": 1, + "name": "some_dep_crate" + }, + ], + "edition": "2015", + "atom_cfgs": [ + "atom_1", + "atom_2", + ], + "key_value_cfgs": { + "feature": "feature_1", + "feature": "feature_2", + "other": "value", + }, + }); + + let krate: Crate = serde_json::from_value(raw_json).unwrap(); + + assert!(krate.atom_cfgs.contains(&"atom_1".to_string())); + assert!(krate.atom_cfgs.contains(&"atom_2".to_string())); + assert!(krate.key_value_cfgs.contains_key(&"feature".to_string())); + assert_eq!(krate.key_value_cfgs.get("feature"), Some(&"feature_2".to_string())); + assert!(krate.key_value_cfgs.contains_key(&"other".to_string())); + assert_eq!(krate.key_value_cfgs.get("other"), Some(&"value".to_string())); + } +} diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 731cbd291818..e7da683d6edf 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -273,6 +273,16 @@ impl ProjectWorkspace { }; let cfg_options = { let mut opts = default_cfg_options.clone(); + for cfg in &krate.cfg { + match cfg.find('=') { + None => opts.insert_atom(cfg.into()), + Some(pos) => { + let key = &cfg[..pos]; + let value = cfg[pos + 1..].trim_matches('"'); + opts.insert_key_value(key.into(), value.into()); + } + } + } for name in &krate.atom_cfgs { opts.insert_atom(name.into()); } diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 07b8114c6139..3d5574c7ff9a 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -384,8 +384,9 @@ fn test_missing_module_code_action_in_json_project() { "root_module": path.join("src/lib.rs"), "deps": [], "edition": "2015", - "atom_cfgs": [], - "key_value_cfgs": {} + "cfg": [ "cfg_atom_1", "feature=cfg_1"], + "atom_cfgs": ["atom_2"], + "key_value_cfgs": { "feature": "key_value_feature", "other": "value"} } ] }); From 1211a46826ee8a08683e4cfe151649efd6fd90fa Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Sun, 31 May 2020 18:58:54 -0400 Subject: [PATCH 02/24] Unsquish parameter types in tooltips for macro-generated functions --- crates/ra_ide/src/display/function_signature.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 9572debd822c..b081ecaad1cd 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -207,7 +207,18 @@ impl From<&'_ ast::FnDef> for FunctionSignature { res.push(raw_param); } - res.extend(param_list.params().map(|param| param.syntax().text().to_string())); + // macro-generated functions are missing whitespace + fn fmt_param(param: ast::Param) -> String { + let text = param.syntax().text().to_string(); + match text.find(':') { + Some(pos) if 1 + pos < text.len() => { + format!("{} {}", &text[0..1 + pos].trim(), &text[1 + pos..].trim()) + } + _ => text, + } + } + + res.extend(param_list.params().map(fmt_param)); res_types.extend(param_list.params().map(|param| { let param_text = param.syntax().text().to_string(); match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { From 8101ea0fdc9d2be2d3926b22af8167f7155ff0c7 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Sun, 31 May 2020 16:04:52 -0700 Subject: [PATCH 03/24] Update a comment for the new source organization --- crates/ra_parser/src/grammar.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs index be0cd5661bd6..293baecf6a2c 100644 --- a/crates/ra_parser/src/grammar.rs +++ b/crates/ra_parser/src/grammar.rs @@ -18,9 +18,10 @@ //! // fn foo() {} //! ``` //! -//! After adding a new inline-test, run `cargo collect-tests` to extract -//! it as a standalone text-fixture into `tests/data/parser/inline`, and -//! run `cargo test` once to create the "gold" value. +//! After adding a new inline-test, run `cargo xtask codegen` to +//! extract it as a standalone text-fixture into +//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to +//! create the "gold" value. //! //! Coding convention: rules like `where_clause` always produce either a //! node or an error, rules like `opt_where_clause` may produce nothing. From f2f3c554af2c4a698e0ceb087ceb4dfa6f95ee90 Mon Sep 17 00:00:00 2001 From: veetaha Date: Tue, 2 Jun 2020 23:15:23 +0300 Subject: [PATCH 04/24] Don't exclude dirs that appear in project root parent path --- xtask/tests/tidy.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 4ac5d929fc6b..62626064ee51 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -180,13 +180,11 @@ impl TidyDocs { } fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool { - let mut cur_path = p; - while let Some(path) = cur_path.parent() { - if dirs_to_exclude.iter().any(|dir| path.ends_with(dir)) { - return true; - } - cur_path = path; - } - - false + p.strip_prefix(project_root()) + .unwrap() + .components() + .rev() + .skip(1) + .filter_map(|it| it.as_os_str().to_str()) + .any(|it| dirs_to_exclude.contains(&it)) } From a9cb2933fbeddef4ed70bde77ded4f9bb185548e Mon Sep 17 00:00:00 2001 From: Paul Daniel Faria Date: Tue, 2 Jun 2020 18:49:09 -0400 Subject: [PATCH 05/24] Add highlight support for unsafe fn calls and raw ptr deref --- crates/ra_hir/src/code_model.rs | 8 ++++ crates/ra_hir_def/src/data.rs | 6 ++- .../src/snapshots/highlight_injection.html | 1 + .../src/snapshots/highlight_strings.html | 1 + .../src/snapshots/highlight_unsafe.html | 48 +++++++++++++++++++ crates/ra_ide/src/snapshots/highlighting.html | 1 + .../src/snapshots/rainbow_highlighting.html | 1 + crates/ra_ide/src/syntax_highlighting.rs | 25 +++++++++- crates/ra_ide/src/syntax_highlighting/html.rs | 1 + crates/ra_ide/src/syntax_highlighting/tags.rs | 8 ++-- .../ra_ide/src/syntax_highlighting/tests.rs | 31 ++++++++++++ 11 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 crates/ra_ide/src/snapshots/highlight_unsafe.html diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e40aeffbcd4f..4a06f3bcddb6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -637,6 +637,10 @@ impl Function { db.function_data(self.id).params.clone() } + pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool { + db.function_data(self.id).is_unsafe + } + pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { let _p = profile("Function::diagnostics"); let infer = db.infer(self.id.into()); @@ -1190,6 +1194,10 @@ impl Type { ) } + pub fn is_raw_ptr(&self) -> bool { + matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. })) + } + pub fn contains_unknown(&self) -> bool { return go(&self.ty.value); diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index e2130d931fdb..807195d25ad9 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -34,6 +34,7 @@ pub struct FunctionData { /// True if the first param is `self`. This is relevant to decide whether this /// can be called as a method. pub has_self_param: bool, + pub is_unsafe: bool, pub visibility: RawVisibility, } @@ -85,11 +86,14 @@ impl FunctionData { ret_type }; + let is_unsafe = src.value.unsafe_token().is_some(); + let vis_default = RawVisibility::default_for_container(loc.container); let visibility = RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); - let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; + let sig = + FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs }; Arc::new(sig) } } diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index 68fc589bc785..fcdc98201f2e 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 .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 41cddd0ff261..e97192b614a0 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 .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .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 000000000000..17ffc727cd06 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html @@ -0,0 +1,48 @@ + + +
unsafe fn unsafe_fn() {}
+
+struct HasUnsafeFn;
+
+impl HasUnsafeFn {
+    unsafe fn unsafe_method(&self) {}
+}
+
+fn main() {
+    let x = &5 as *const usize;
+    unsafe {
+        unsafe_fn();
+        HasUnsafeFn.unsafe_method();
+        let y = *x;
+        let z = -x;
+    }
+}
\ 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 352e350955fc..42c5f3e5515e 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 .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 2a0294f719f5..2dd61d20d691 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 .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 0b53ebe69563..19ecd54d6cf2 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -406,6 +406,23 @@ fn highlight_element( _ => h, } } + PREFIX_EXPR => { + let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; + match prefix_expr.op_kind() { + Some(ast::PrefixOp::Deref) => {} + _ => return None, + } + + let expr = prefix_expr.expr()?; + let ty = sema.type_of_expr(&expr)?; + if !ty.is_raw_ptr() { + return None; + } + + let mut h = Highlight::new(HighlightTag::Operator); + h |= HighlightModifier::Unsafe; + h + } k if k.is_keyword() => { let h = Highlight::new(HighlightTag::Keyword); @@ -458,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { Definition::Field(_) => HighlightTag::Field, Definition::ModuleDef(def) => match def { hir::ModuleDef::Module(_) => HighlightTag::Module, - hir::ModuleDef::Function(_) => HighlightTag::Function, + hir::ModuleDef::Function(func) => { + let mut h = HighlightTag::Function.into(); + if func.is_unsafe(db) { + h |= HighlightModifier::Unsafe; + } + return h; + } hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 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 edfe61f39a13..7d946c98dae7 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 .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.operator.unsafe { color: #E28C14; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 1514531de2e3..94f466966a3a 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 { Enum, EnumVariant, Field, + FormatSpecifier, Function, Keyword, Lifetime, Macro, Module, NumericLiteral, + Operator, SelfKeyword, SelfType, Static, @@ -41,8 +43,6 @@ pub enum HighlightTag { Union, Local, UnresolvedReference, - FormatSpecifier, - Operator, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -72,12 +72,14 @@ impl HighlightTag { HighlightTag::Enum => "enum", HighlightTag::EnumVariant => "enum_variant", HighlightTag::Field => "field", + HighlightTag::FormatSpecifier => "format_specifier", HighlightTag::Function => "function", HighlightTag::Keyword => "keyword", HighlightTag::Lifetime => "lifetime", HighlightTag::Macro => "macro", HighlightTag::Module => "module", HighlightTag::NumericLiteral => "numeric_literal", + HighlightTag::Operator => "operator", HighlightTag::SelfKeyword => "self_keyword", HighlightTag::SelfType => "self_type", HighlightTag::Static => "static", @@ -89,8 +91,6 @@ impl HighlightTag { HighlightTag::Union => "union", HighlightTag::Local => "variable", HighlightTag::UnresolvedReference => "unresolved_reference", - HighlightTag::FormatSpecifier => "format_specifier", - HighlightTag::Operator => "operator", } } } diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 7dc229cab73d..36a1aa419bc3 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() { fs::write(dst_file, &actual_html).unwrap(); assert_eq_text!(expected_html, actual_html); } + +#[test] +fn test_unsafe_highlighting() { + let (analysis, file_id) = single_file( + r#" +unsafe fn unsafe_fn() {} + +struct HasUnsafeFn; + +impl HasUnsafeFn { + unsafe fn unsafe_method(&self) {} +} + +fn main() { + let x = &5 as *const usize; + unsafe { + unsafe_fn(); + HasUnsafeFn.unsafe_method(); + let y = *x; + let z = -x; + } +} +"# + .trim(), + ); + let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); + let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); + let expected_html = &read_text(&dst_file); + fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); +} From 599c105e6fabb2b81c2d0a11b86c0c96f6ab1b88 Mon Sep 17 00:00:00 2001 From: Gabriel Valfridsson Date: Wed, 3 Jun 2020 00:51:23 +0200 Subject: [PATCH 06/24] Hide squiggly for unused and unnecessary --- ...agnostics__to_proto__tests__snap_rustc_unused_variable.snap | 2 +- crates/rust-analyzer/src/diagnostics/to_proto.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 6dd3fcb2eab8..33b516e269b8 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 }, }, severity: Some( - Warning, + Hint, ), code: Some( String( diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index a500d670a72c..6a6e7b45735a 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -183,7 +183,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( return Vec::new(); } - let severity = map_level_to_severity(rd.level); + let mut severity = map_level_to_severity(rd.level); let mut source = String::from("rustc"); let mut code = rd.code.as_ref().map(|c| c.code.clone()); @@ -225,6 +225,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } if is_unused_or_unnecessary(rd) { + severity = Some(DiagnosticSeverity::Hint); tags.push(DiagnosticTag::Unnecessary); } From 994006585be6850b250b21aac76a58f1324cad5d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 2 Jun 2020 11:16:49 +0200 Subject: [PATCH 07/24] Start documenting review process --- docs/dev/README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/docs/dev/README.md b/docs/dev/README.md index 65cc9fc12c0c..1de5a2aab1d7 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 * [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue) are good issues to get into the project. -* [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor) +* [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions) issues have links to the code in question and tests. * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), [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 path/to/some/rust/crate` to run a batch analysis. This is primarily useful for performance optimizations, or for bug minimization. +# Code Style & Review Process + +Our approach to "clean code" is two fold: + +* We generally don't block PRs on style changes. +* At the same time, all code in rust-analyzer is constantly refactored. + +It 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. +Sending small cleanup PRs (like rename a single local variable) is encouraged. + +## Scale of Changes + +Everyone knows that it's better to send small & focused pull requests. +The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. + +The main thing too keep an eye on is the boundaries between various components. +There are three kinds of changes: + +1. Internals of a single component are changed. + Specifically, you don't change any `pub` items. + A good example here would be an addition of a new assist. + +2. API of a component is expanded. + Specifically, you add a new `pub` function which wasn't there before. + A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. + +3. A new dependency between components is introduced. + Specifically, you add a `pub use` reexport from another crate or you add a new line to `[dependencies]` section of `Cargo.toml`. + A good example here would be adding reference search capability to the assists crates. + +For the first group, the change is generally merged as long as: + +* it works for the happy case, +* it has tests, +* it doesn't panic for unhappy case. + +For the second group, the change would be subjected to quite a bit of scrutiny and iteration. +The new API needs to be right (or at least easy to change later). +The actual implementation doesn't matter that much. +It's very important to minimize the amount of changed lines of code for changes of the second kind. +Often, you start doing change of the first kind, only to realise that you need to elevate to a change of the second kind. +In this case, we'll probably ask you to split API changes into a separate PR. + +Changes of the third group should be pretty rare, so we don't specify any specific process for them. +That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! + +Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate +https://www.tedinski.com/2018/02/06/system-boundaries.html + +## Order of Imports + +We separate import groups with blank lines + +``` +mod x; +mod y; + +use std::{ ... } + +use crate_foo::{ ... } +use crate_bar::{ ... } + +use crate::{} + +use super::{} // but prefer `use crate::` +``` + +## Order of Items + +Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. +People read things from top to bottom, so place most important things first. + +Specifically, if all items except one are private, always put the non-private item on top. + +Put `struct`s and `enum`s first, functions and impls last. + +Do + +``` +// Good +struct Foo { + bars: Vec +} + +struct Bar; +``` + +rather than + +``` +// Not as good +struct Bar; + +struct Foo { + bars: Vec +} +``` + +## Documentation + +For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. +If the line is too long, you want to split the sentence in two :-) + # Logging Logging is done by both rust-analyzer and VS Code, so it might be tricky to From ca80544f4baef69f3bb80e6b069291fbc89a927c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 10:33:01 +0200 Subject: [PATCH 08/24] Put important things on top --- crates/ra_project_model/src/json_project.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs index bd2bae15e690..09c06fef9359 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; use rustc_hash::{FxHashMap, FxHashSet}; use serde::Deserialize; +/// Roots and crates that compose this Rust project. +#[derive(Clone, Debug, Deserialize)] +pub struct JsonProject { + pub(crate) roots: Vec, + pub(crate) crates: Vec, +} + /// A root points to the directory which contains Rust crates. rust-analyzer watches all files in /// all roots. Roots might be nested. #[derive(Clone, Debug, Deserialize)] @@ -57,13 +64,6 @@ pub struct Dep { pub(crate) name: String, } -/// Roots and crates that compose this Rust project. -#[derive(Clone, Debug, Deserialize)] -pub struct JsonProject { - pub(crate) roots: Vec, - pub(crate) crates: Vec, -} - #[cfg(test)] mod tests { use super::*; From d4b21476a8f8dc882e2ace78e156da6fcb70a8ff Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 10:52:35 +0200 Subject: [PATCH 09/24] Derive local roots from Workspaces --- crates/rust-analyzer/src/main_loop.rs | 1 - crates/rust-analyzer/src/world.rs | 24 ++++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f1287d52cd32..2e5499485f29 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -164,7 +164,6 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) } WorldState::new( - ws_roots, workspaces, config.lru_capacity, &globs, diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs index 367272925be5..c1010e86aac5 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/world.rs @@ -58,7 +58,7 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> #[derive(Debug)] pub struct WorldState { pub config: Config, - pub roots: Vec, + pub local_roots: Vec, pub workspaces: Arc>, pub analysis_host: AnalysisHost, pub vfs: Arc>, @@ -81,7 +81,6 @@ pub struct WorldSnapshot { impl WorldState { pub fn new( - folder_roots: Vec, workspaces: Vec, lru_capacity: Option, exclude_globs: &[Glob], @@ -93,6 +92,7 @@ impl WorldState { let extern_dirs: FxHashSet<_> = workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); + let mut local_roots = Vec::new(); let roots: Vec<_> = { let create_filter = |is_member| { RustPackageFilterBuilder::default() @@ -100,12 +100,16 @@ impl WorldState { .exclude(exclude_globs.iter().cloned()) .into_vfs_filter() }; - folder_roots + workspaces .iter() - .map(|path| RootEntry::new(path.clone(), create_filter(true))) - .chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| { - RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member())) - })) + .flat_map(ProjectWorkspace::to_roots) + .map(|pkg_root| { + let path = pkg_root.path().to_owned(); + if pkg_root.is_member() { + local_roots.push(path.clone()); + } + RootEntry::new(path, create_filter(pkg_root.is_member())) + }) .chain( extern_dirs .iter() @@ -121,7 +125,7 @@ impl WorldState { let mut extern_source_roots = FxHashMap::default(); for r in vfs_roots { let vfs_root_path = vfs.root2path(r); - let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); + let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it)); change.add_root(SourceRootId(r.0), is_local); change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); @@ -178,7 +182,7 @@ impl WorldState { analysis_host.apply_change(change); WorldState { config, - roots: folder_roots, + local_roots, workspaces: Arc::new(workspaces), analysis_host, vfs: Arc::new(RwLock::new(vfs)), @@ -216,7 +220,7 @@ impl WorldState { match c { VfsChange::AddRoot { root, files } => { let root_path = self.vfs.read().root2path(root); - let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); + let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); if is_local { *roots_scanned += 1; for (file, path, text) in files { From ee181cf6833f338869f878eff11e026abe4e984e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 11:07:04 +0200 Subject: [PATCH 10/24] Drop no-project test Eventually, we should support "just open random rust file" use case, we don't really do this now, so let's avoid spending time on it until we fix it properly. --- .../rust-analyzer/tests/heavy_tests/main.rs | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index c80745945cba..69dc719c54a5 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -58,55 +58,6 @@ use std::collections::Spam; eprintln!("completion took {:?}", completion_start.elapsed()); } -#[test] -fn test_runnables_no_project() { - if skip_slow_tests() { - return; - } - - let server = project( - r" -//- lib.rs -#[test] -fn foo() { -} -", - ); - server.wait_until_workspace_is_loaded(); - server.request::( - RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, - json!([ - { - "args": { - "cargoArgs": ["test"], - "executableArgs": ["foo", "--nocapture"], - }, - "kind": "cargo", - "label": "test foo", - "location": { - "targetRange": { - "end": { "character": 1, "line": 2 }, - "start": { "character": 0, "line": 0 } - }, - "targetSelectionRange": { - "end": { "character": 6, "line": 1 }, - "start": { "character": 3, "line": 1 } - }, - "targetUri": "file:///[..]/lib.rs" - } - }, - { - "args": { - "cargoArgs": ["check", "--workspace"], - "executableArgs": [], - }, - "kind": "cargo", - "label": "cargo check --workspace" - } - ]), - ); -} - #[test] fn test_runnables_project() { if skip_slow_tests() { From a87cd8ecc66f1b644e4b11b8438fde1d5575b73c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 11:16:08 +0200 Subject: [PATCH 11/24] Rename WorldState -> GlobalState --- crates/rust-analyzer/src/cargo_target_spec.rs | 4 +- crates/rust-analyzer/src/from_proto.rs | 8 +- .../src/{world.rs => global_state.rs} | 18 +- crates/rust-analyzer/src/lib.rs | 2 +- crates/rust-analyzer/src/main_loop.rs | 88 ++--- .../rust-analyzer/src/main_loop/handlers.rs | 357 +++++++++--------- crates/rust-analyzer/src/to_proto.rs | 87 +++-- 7 files changed, 291 insertions(+), 273 deletions(-) rename crates/rust-analyzer/src/{world.rs => global_state.rs} (97%) diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 008518a089ff..44f856f6b457 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; use ra_ide::{FileId, RunnableKind, TestId}; use ra_project_model::{self, ProjectWorkspace, TargetKind}; -use crate::{world::WorldSnapshot, Result}; +use crate::{global_state::GlobalStateSnapshot, Result}; /// Abstract representation of Cargo target. /// @@ -89,7 +89,7 @@ impl CargoTargetSpec { } pub(crate) fn for_file( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, file_id: FileId, ) -> Result> { let &crate_id = match world.analysis().crate_for(file_id)?.first() { diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 4bb16a496c09..206673829c48 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}; use ra_ide::{LineCol, LineIndex}; use ra_syntax::{TextRange, TextSize}; -use crate::{world::WorldSnapshot, Result}; +use crate::{global_state::GlobalStateSnapshot, Result}; pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { 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 TextRange::new(start, end) } -pub(crate) fn file_id(world: &WorldSnapshot, url: &lsp_types::Url) -> Result { +pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result { world.uri_to_file_id(url) } pub(crate) fn file_position( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, tdpp: lsp_types::TextDocumentPositionParams, ) -> Result { let file_id = file_id(world, &tdpp.text_document.uri)?; @@ -31,7 +31,7 @@ pub(crate) fn file_position( } pub(crate) fn file_range( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, text_document_identifier: lsp_types::TextDocumentIdentifier, range: lsp_types::Range, ) -> Result { diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/global_state.rs similarity index 97% rename from crates/rust-analyzer/src/world.rs rename to crates/rust-analyzer/src/global_state.rs index c1010e86aac5..0bebb5bf615a 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -50,13 +50,13 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> }) } -/// `WorldState` is the primary mutable state of the language server +/// `GlobalState` is the primary mutable state of the language server /// /// The most interesting components are `vfs`, which stores a consistent /// snapshot of the file systems, and `analysis_host`, which stores our /// incremental salsa database. #[derive(Debug)] -pub struct WorldState { +pub struct GlobalState { pub config: Config, pub local_roots: Vec, pub workspaces: Arc>, @@ -70,7 +70,7 @@ pub struct WorldState { } /// An immutable snapshot of the world's state at a point in time. -pub struct WorldSnapshot { +pub struct GlobalStateSnapshot { pub config: Config, pub workspaces: Arc>, pub analysis: Analysis, @@ -79,14 +79,14 @@ pub struct WorldSnapshot { vfs: Arc>, } -impl WorldState { +impl GlobalState { pub fn new( workspaces: Vec, lru_capacity: Option, exclude_globs: &[Glob], watch: Watch, config: Config, - ) -> WorldState { + ) -> GlobalState { let mut change = AnalysisChange::new(); let extern_dirs: FxHashSet<_> = @@ -180,7 +180,7 @@ impl WorldState { let mut analysis_host = AnalysisHost::new(lru_capacity); analysis_host.apply_change(change); - WorldState { + GlobalState { config, local_roots, workspaces: Arc::new(workspaces), @@ -255,8 +255,8 @@ impl WorldState { self.analysis_host.apply_change(change); } - pub fn snapshot(&self) -> WorldSnapshot { - WorldSnapshot { + pub fn snapshot(&self) -> GlobalStateSnapshot { + GlobalStateSnapshot { config: self.config.clone(), workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(), @@ -279,7 +279,7 @@ impl WorldState { } } -impl WorldSnapshot { +impl GlobalStateSnapshot { pub fn analysis(&self) -> &Analysis { &self.analysis } diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 57d0e92188e3..609cb69d3bff 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -26,7 +26,7 @@ mod main_loop; mod markdown; pub mod lsp_ext; pub mod config; -mod world; +mod global_state; mod diagnostics; mod semantic_tokens; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 2e5499485f29..35f2d7001e1c 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -38,12 +38,13 @@ use threadpool::ThreadPool; use crate::{ config::{Config, FilesWatcher}, diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, - from_proto, lsp_ext, + from_proto, + global_state::{GlobalState, GlobalStateSnapshot}, + lsp_ext, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, }, - world::{WorldSnapshot, WorldState}, Result, }; @@ -92,7 +93,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) } let mut loop_state = LoopState::default(); - let mut world_state = { + let mut global_state = { let workspaces = { // FIXME: support dynamic workspace loading. let project_roots: FxHashSet<_> = ws_roots @@ -163,7 +164,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) connection.sender.send(request.into()).unwrap(); } - WorldState::new( + GlobalState::new( workspaces, config.lru_capacity, &globs, @@ -172,7 +173,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) ) }; - loop_state.roots_total = world_state.vfs.read().n_roots(); + loop_state.roots_total = global_state.vfs.read().n_roots(); let pool = ThreadPool::default(); let (task_sender, task_receiver) = unbounded::(); @@ -190,12 +191,12 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) Err(RecvError) => return Err("client exited without shutdown".into()), }, recv(task_receiver) -> task => Event::Task(task.unwrap()), - recv(world_state.task_receiver) -> task => match task { + recv(global_state.task_receiver) -> task => match task { Ok(task) => Event::Vfs(task), Err(RecvError) => return Err("vfs died".into()), }, recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), - recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { + recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { Ok(task) => Event::CheckWatcher(task), Err(RecvError) => return Err("check watcher died".into()), } @@ -210,16 +211,16 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) &task_sender, &libdata_sender, &connection, - &mut world_state, + &mut global_state, &mut loop_state, event, )?; } } - world_state.analysis_host.request_cancellation(); + global_state.analysis_host.request_cancellation(); log::info!("waiting for tasks to finish..."); task_receiver.into_iter().for_each(|task| { - on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) + on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) }); libdata_receiver.into_iter().for_each(drop); log::info!("...tasks have finished"); @@ -228,7 +229,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) drop(pool); log::info!("...threadpool has finished"); - let vfs = Arc::try_unwrap(world_state.vfs).expect("all snapshots should be dead"); + let vfs = Arc::try_unwrap(global_state.vfs).expect("all snapshots should be dead"); drop(vfs); Ok(()) @@ -319,7 +320,7 @@ fn loop_turn( task_sender: &Sender, libdata_sender: &Sender, connection: &Connection, - world_state: &mut WorldState, + global_state: &mut GlobalState, loop_state: &mut LoopState, event: Event, ) -> Result<()> { @@ -335,22 +336,22 @@ fn loop_turn( match event { Event::Task(task) => { - on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state); - world_state.maybe_collect_garbage(); + on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state); + global_state.maybe_collect_garbage(); } Event::Vfs(task) => { - world_state.vfs.write().handle_task(task); + global_state.vfs.write().handle_task(task); } Event::Lib(lib) => { - world_state.add_lib(lib); - world_state.maybe_collect_garbage(); + global_state.add_lib(lib); + global_state.maybe_collect_garbage(); loop_state.in_flight_libraries -= 1; loop_state.roots_scanned += 1; } - Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, + Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, Event::Msg(msg) => match msg { Message::Request(req) => on_request( - world_state, + global_state, &mut loop_state.pending_requests, pool, task_sender, @@ -359,7 +360,7 @@ fn loop_turn( req, )?, Message::Notification(not) => { - on_notification(&connection.sender, world_state, loop_state, not)?; + on_notification(&connection.sender, global_state, loop_state, not)?; } Message::Response(resp) => { let removed = loop_state.pending_responses.remove(&resp.id); @@ -378,9 +379,9 @@ fn loop_turn( } (None, Some(configs)) => { if let Some(new_config) = configs.get(0) { - let mut config = world_state.config.clone(); + let mut config = global_state.config.clone(); config.update(&new_config); - world_state.update_configuration(config); + global_state.update_configuration(config); } } (None, None) => { @@ -393,7 +394,7 @@ fn loop_turn( }; let mut state_changed = false; - if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) { + if let Some(changes) = global_state.process_changes(&mut loop_state.roots_scanned) { state_changed = true; loop_state.pending_libraries.extend(changes); } @@ -415,7 +416,7 @@ fn loop_turn( } let show_progress = - !loop_state.workspace_loaded && world_state.config.client_caps.work_done_progress; + !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total @@ -424,7 +425,7 @@ fn loop_turn( { state_changed = true; loop_state.workspace_loaded = true; - if let Some(flycheck) = &world_state.flycheck { + if let Some(flycheck) = &global_state.flycheck { flycheck.update(); } } @@ -436,13 +437,13 @@ fn loop_turn( if state_changed && loop_state.workspace_loaded { update_file_notifications_on_threadpool( pool, - world_state.snapshot(), + global_state.snapshot(), task_sender.clone(), loop_state.subscriptions.subscriptions(), ); pool.execute({ let subs = loop_state.subscriptions.subscriptions(); - let snap = world_state.snapshot(); + let snap = global_state.snapshot(); move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) }); } @@ -466,7 +467,7 @@ fn on_task( task: Task, msg_sender: &Sender, pending_requests: &mut PendingRequests, - state: &mut WorldState, + state: &mut GlobalState, ) { match task { Task::Respond(response) => { @@ -484,7 +485,7 @@ fn on_task( } fn on_request( - world: &mut WorldState, + global_state: &mut GlobalState, pending_requests: &mut PendingRequests, pool: &ThreadPool, task_sender: &Sender, @@ -495,7 +496,7 @@ fn on_request( let mut pool_dispatcher = PoolDispatcher { req: Some(req), pool, - world, + global_state, task_sender, msg_sender, pending_requests, @@ -551,7 +552,7 @@ fn on_request( fn on_notification( msg_sender: &Sender, - state: &mut WorldState, + state: &mut GlobalState, loop_state: &mut LoopState, not: Notification, ) -> Result<()> { @@ -725,7 +726,7 @@ fn apply_document_changes( fn on_check_task( task: CheckTask, - world_state: &mut WorldState, + global_state: &mut GlobalState, task_sender: &Sender, ) -> Result<()> { match task { @@ -744,7 +745,7 @@ fn on_check_task( .uri .to_file_path() .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; - let file_id = match world_state.vfs.read().path2file(&path) { + let file_id = match global_state.vfs.read().path2file(&path) { Some(file) => FileId(file.0), None => { log::error!( @@ -764,7 +765,7 @@ fn on_check_task( } CheckTask::Status(status) => { - if world_state.config.client_caps.work_done_progress { + if global_state.config.client_caps.work_done_progress { let progress = match status { Status::Being => { lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { @@ -803,7 +804,7 @@ fn on_check_task( Ok(()) } -fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut WorldState) { +fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut GlobalState) { let subscriptions = state.diagnostics.handle_task(task); for file_id in subscriptions { @@ -878,7 +879,7 @@ fn send_startup_progress(sender: &Sender, loop_state: &mut LoopState) { struct PoolDispatcher<'a> { req: Option, pool: &'a ThreadPool, - world: &'a mut WorldState, + global_state: &'a mut GlobalState, pending_requests: &'a mut PendingRequests, msg_sender: &'a Sender, task_sender: &'a Sender, @@ -889,7 +890,7 @@ impl<'a> PoolDispatcher<'a> { /// Dispatches the request onto the current thread fn on_sync( &mut self, - f: fn(&mut WorldState, R::Params) -> Result, + f: fn(&mut GlobalState, R::Params) -> Result, ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, @@ -902,18 +903,21 @@ impl<'a> PoolDispatcher<'a> { return Ok(self); } }; - let world = panic::AssertUnwindSafe(&mut *self.world); + let world = panic::AssertUnwindSafe(&mut *self.global_state); let task = panic::catch_unwind(move || { let result = f(world.0, params); result_to_task::(id, result) }) .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; - on_task(task, self.msg_sender, self.pending_requests, self.world); + on_task(task, self.msg_sender, self.pending_requests, self.global_state); Ok(self) } /// Dispatches the request onto thread pool - fn on(&mut self, f: fn(WorldSnapshot, R::Params) -> Result) -> Result<&mut Self> + fn on( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> Result, + ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + Send + 'static, @@ -927,7 +931,7 @@ impl<'a> PoolDispatcher<'a> { }; self.pool.execute({ - let world = self.world.snapshot(); + let world = self.global_state.snapshot(); let sender = self.task_sender.clone(); move || { let result = f(world, params); @@ -1011,7 +1015,7 @@ where fn update_file_notifications_on_threadpool( pool: &ThreadPool, - world: WorldSnapshot, + world: GlobalStateSnapshot, task_sender: Sender, subscriptions: Vec, ) { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 7fd6917649a3..1bb8e44735f5 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::{ config::RustfmtConfig, diagnostics::DiagnosticTask, from_json, from_proto, + global_state::GlobalStateSnapshot, lsp_ext::{self, InlayHint, InlayHintsParams}, - to_proto, - world::WorldSnapshot, - LspError, Result, + to_proto, LspError, Result, }; -pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { +pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); - let mut buf = world.status(); + let mut buf = snap.status(); format_to!(buf, "\n\nrequests:\n"); - let requests = world.latest_requests.read(); + let requests = snap.latest_requests.read(); for (is_last, r) in requests.iter() { let mark = if is_last { "*" } else { " " }; 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 { } pub fn handle_syntax_tree( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SyntaxTreeParams, ) -> Result { let _p = profile("handle_syntax_tree"); - let id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(id)?; + let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(id)?; let text_range = params.range.map(|r| from_proto::text_range(&line_index, r)); - let res = world.analysis().syntax_tree(id, text_range)?; + let res = snap.analysis().syntax_tree(id, text_range)?; Ok(res) } pub fn handle_expand_macro( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::ExpandMacroParams, ) -> Result> { let _p = profile("handle_expand_macro"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = from_proto::offset(&line_index, params.position); - let res = world.analysis().expand_macro(FilePosition { file_id, offset })?; + let res = snap.analysis().expand_macro(FilePosition { file_id, offset })?; Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion })) } pub fn handle_selection_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SelectionRangeParams, ) -> Result>> { let _p = profile("handle_selection_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res: Result> = params .positions .into_iter() @@ -93,7 +92,7 @@ pub fn handle_selection_range( loop { ranges.push(range); let frange = FileRange { file_id, range }; - let next = world.analysis().extend_selection(frange)?; + let next = snap.analysis().extend_selection(frange)?; if next == range { break; } else { @@ -119,18 +118,18 @@ pub fn handle_selection_range( } pub fn handle_matching_brace( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::MatchingBraceParams, ) -> Result> { let _p = profile("handle_matching_brace"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res = params .positions .into_iter() .map(|position| { let offset = from_proto::offset(&line_index, position); - let offset = match world.analysis().matching_brace(FilePosition { file_id, offset }) { + let offset = match snap.analysis().matching_brace(FilePosition { file_id, offset }) { Ok(Some(matching_brace_offset)) => matching_brace_offset, Err(_) | Ok(None) => offset, }; @@ -141,17 +140,17 @@ pub fn handle_matching_brace( } pub fn handle_join_lines( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::JoinLinesParams, ) -> Result> { let _p = profile("handle_join_lines"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_endings = world.file_line_endings(file_id); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_endings = snap.file_line_endings(file_id); let mut res = TextEdit::default(); for range in params.ranges { let range = from_proto::text_range(&line_index, range); - let edit = world.analysis().join_lines(FileRange { file_id, range })?; + let edit = snap.analysis().join_lines(FileRange { file_id, range })?; match res.union(edit) { Ok(()) => (), Err(_edit) => { @@ -164,37 +163,37 @@ pub fn handle_join_lines( } pub fn handle_on_enter( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result>> { let _p = profile("handle_on_enter"); - let position = from_proto::file_position(&world, params)?; - let edit = match world.analysis().on_enter(position)? { + let position = from_proto::file_position(&snap, params)?; + let edit = match snap.analysis().on_enter(position)? { None => return Ok(None), Some(it) => it, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit); Ok(Some(edit)) } // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. pub fn handle_on_type_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentOnTypeFormattingParams, ) -> Result>> { let _p = profile("handle_on_type_formatting"); - let mut position = from_proto::file_position(&world, params.text_document_position)?; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let mut position = from_proto::file_position(&snap, params.text_document_position)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); // in `ra_ide`, the `on_type` invariant is that // `text.char_at(position) == typed_char`. position.offset -= TextSize::of('.'); let char_typed = params.ch.chars().next().unwrap_or('\0'); assert!({ - let text = world.analysis().file_text(position.file_id)?; + let text = snap.analysis().file_text(position.file_id)?; text[usize::from(position.offset)..].starts_with(char_typed) }); @@ -206,7 +205,7 @@ pub fn handle_on_type_formatting( return Ok(None); } - let edit = world.analysis().on_char_typed(position, char_typed)?; + let edit = snap.analysis().on_char_typed(position, char_typed)?; let mut edit = match edit { Some(it) => it, None => return Ok(None), @@ -220,16 +219,16 @@ pub fn handle_on_type_formatting( } pub fn handle_document_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentSymbolParams, ) -> Result> { let _p = profile("handle_document_symbol"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); - for symbol in world.analysis().file_structure(file_id)? { + for symbol in snap.analysis().file_structure(file_id)? { let doc_symbol = DocumentSymbol { name: symbol.label, detail: symbol.detail, @@ -255,10 +254,10 @@ pub fn handle_document_symbol( } } - let res = if world.config.client_caps.hierarchical_symbols { + let res = if snap.config.client_caps.hierarchical_symbols { document_symbols.into() } else { - let url = to_proto::url(&world, file_id)?; + let url = to_proto::url(&snap, file_id)?; let mut symbol_information = Vec::::new(); for symbol in document_symbols { flatten_document_symbol(&symbol, None, &url, &mut symbol_information); @@ -288,7 +287,7 @@ pub fn handle_document_symbol( } pub fn handle_workspace_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::WorkspaceSymbolParams, ) -> Result>> { let _p = profile("handle_workspace_symbol"); @@ -306,22 +305,22 @@ pub fn handle_workspace_symbol( q.limit(128); q }; - let mut res = exec_query(&world, query)?; + let mut res = exec_query(&snap, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); - res = exec_query(&world, query)?; + res = exec_query(&snap, query)?; } return Ok(Some(res)); - fn exec_query(world: &WorldSnapshot, query: Query) -> Result> { + fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result> { let mut res = Vec::new(); - for nav in world.analysis().symbol_search(query)? { + for nav in snap.analysis().symbol_search(query)? { let info = SymbolInformation { name: nav.name().to_string(), kind: to_proto::symbol_kind(nav.kind()), - location: to_proto::location(world, nav.file_range())?, + location: to_proto::location(snap, nav.file_range())?, container_name: nav.container_name().map(|v| v.to_string()), deprecated: None, }; @@ -332,73 +331,73 @@ pub fn handle_workspace_symbol( } pub fn handle_goto_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::GotoDefinitionParams, ) -> Result> { let _p = profile("handle_goto_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_implementation( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoImplementationParams, ) -> Result> { let _p = profile("handle_goto_implementation"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_implementation(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_implementation(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_type_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoTypeDefinitionParams, ) -> Result> { let _p = profile("handle_goto_type_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_type_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_type_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_parent_module( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_parent_module"); - let position = from_proto::file_position(&world, params)?; - let navs = world.analysis().parent_module(position)?; - let res = to_proto::goto_definition_response(&world, None, navs)?; + let position = from_proto::file_position(&snap, params)?; + let navs = snap.analysis().parent_module(position)?; + let res = to_proto::goto_definition_response(&snap, None, navs)?; Ok(Some(res)) } pub fn handle_runnables( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::RunnablesParams, ) -> Result> { let _p = profile("handle_runnables"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = params.position.map(|it| from_proto::offset(&line_index, it)); let mut res = Vec::new(); - let workspace_root = world.workspace_root_for(file_id); - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; - for runnable in world.analysis().runnables(file_id)? { + let workspace_root = snap.workspace_root_for(file_id); + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; + for runnable in snap.analysis().runnables(file_id)? { if let Some(offset) = offset { if !runnable.nav.full_range().contains_inclusive(offset) { continue; @@ -413,7 +412,7 @@ pub fn handle_runnables( } } } - res.push(to_proto::runnable(&world, file_id, runnable)?); + res.push(to_proto::runnable(&snap, file_id, runnable)?); } // Add `cargo check` and `cargo test` for the whole package @@ -453,16 +452,16 @@ pub fn handle_runnables( } pub fn handle_completion( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CompletionParams, ) -> Result> { let _p = profile("handle_completion"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; let completion_triggered_after_single_colon = { let mut res = false; if let Some(ctx) = params.context { if ctx.trigger_character.unwrap_or_default() == ":" { - let source_file = world.analysis().parse(position.file_id)?; + let source_file = snap.analysis().parse(position.file_id)?; let syntax = source_file.syntax(); let text = syntax.text(); if let Some(next_char) = text.char_at(position.offset) { @@ -480,12 +479,12 @@ pub fn handle_completion( return Ok(None); } - let items = match world.analysis().completions(&world.config.completion, position)? { + let items = match snap.analysis().completions(&snap.config.completion, position)? { None => return Ok(None), Some(items) => items, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let items: Vec = items .into_iter() .map(|item| to_proto::completion_item(&line_index, line_endings, item)) @@ -495,15 +494,15 @@ pub fn handle_completion( } pub fn handle_folding_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: FoldingRangeParams, ) -> Result>> { let _p = profile("handle_folding_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let folds = world.analysis().folding_ranges(file_id)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_folding_only = world.config.client_caps.line_folding_only; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let folds = snap.analysis().folding_ranges(file_id)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_folding_only = snap.config.client_caps.line_folding_only; let res = folds .into_iter() .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it)) @@ -512,16 +511,16 @@ pub fn handle_folding_range( } pub fn handle_signature_help( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SignatureHelpParams, ) -> Result> { let _p = profile("handle_signature_help"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let call_info = match world.analysis().call_info(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let call_info = match snap.analysis().call_info(position)? { None => return Ok(None), Some(it) => it, }; - let concise = !world.config.call_info_full; + let concise = !snap.config.call_info_full; let mut active_parameter = call_info.active_parameter.map(|it| it as i64); if concise && call_info.signature.has_self_param { active_parameter = active_parameter.map(|it| it.saturating_sub(1)); @@ -535,14 +534,17 @@ pub fn handle_signature_help( })) } -pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result> { +pub fn handle_hover( + snap: GlobalStateSnapshot, + params: lsp_types::HoverParams, +) -> Result> { let _p = profile("handle_hover"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let info = match world.analysis().hover(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let info = match snap.analysis().hover(position)? { None => return Ok(None), Some(info) => info, }; - let line_index = world.analysis.file_line_index(position.file_id)?; + let line_index = snap.analysis.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); let res = Hover { contents: HoverContents::Markup(MarkupContent { @@ -555,26 +557,29 @@ pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Res } pub fn handle_prepare_rename( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_prepare_rename"); - let position = from_proto::file_position(&world, params)?; + let position = from_proto::file_position(&snap, params)?; - let optional_change = world.analysis().rename(position, "dummy")?; + let optional_change = snap.analysis().rename(position, "dummy")?; let range = match optional_change { None => return Ok(None), Some(it) => it.range, }; - let line_index = world.analysis().file_line_index(position.file_id)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; let range = to_proto::range(&line_index, range); Ok(Some(PrepareRenameResponse::Range(range))) } -pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result> { +pub fn handle_rename( + snap: GlobalStateSnapshot, + params: RenameParams, +) -> Result> { let _p = profile("handle_rename"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; if params.new_name.is_empty() { return Err(LspError::new( @@ -584,36 +589,36 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result return Ok(None), Some(it) => it.info, }; - let workspace_edit = to_proto::workspace_edit(&world, source_change)?; + let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; Ok(Some(workspace_edit)) } pub fn handle_references( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::ReferenceParams, ) -> Result>> { let _p = profile("handle_references"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; - let refs = match world.analysis().find_all_refs(position, None)? { + let refs = match snap.analysis().find_all_refs(position, None)? { None => return Ok(None), Some(refs) => refs, }; let locations = if params.context.include_declaration { refs.into_iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() } else { // Only iterate over the references if include_declaration was false refs.references() .iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() }; @@ -621,24 +626,24 @@ pub fn handle_references( } pub fn handle_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: DocumentFormattingParams, ) -> Result>> { let _p = profile("handle_formatting"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let file = world.analysis().file_text(file_id)?; - let crate_ids = world.analysis().crate_for(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let file = snap.analysis().file_text(file_id)?; + let crate_ids = snap.analysis().crate_for(file_id)?; - let file_line_index = world.analysis().file_line_index(file_id)?; + let file_line_index = snap.analysis().file_line_index(file_id)?; let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); - let mut rustfmt = match &world.config.rustfmt { + let mut rustfmt = match &snap.config.rustfmt { RustfmtConfig::Rustfmt { extra_args } => { let mut cmd = process::Command::new("rustfmt"); cmd.args(extra_args); if let Some(&crate_id) = crate_ids.first() { // Assume all crates are in the same edition - let edition = world.analysis().crate_edition(crate_id)?; + let edition = snap.analysis().crate_edition(crate_id)?; cmd.arg("--edition"); cmd.arg(edition.to_string()); } @@ -697,23 +702,23 @@ pub fn handle_formatting( } pub fn handle_code_action( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CodeActionParams, ) -> Result>> { let _p = profile("handle_code_action"); // We intentionally don't support command-based actions, as those either // requires custom client-code anyway, or requires server-initiated edits. // Server initiated edits break causality, so we avoid those as well. - if !world.config.client_caps.code_action_literals { + if !snap.config.client_caps.code_action_literals { return Ok(None); } - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.range); let frange = FileRange { file_id, range }; - let diagnostics = world.analysis().diagnostics(file_id)?; + let diagnostics = snap.analysis().diagnostics(file_id)?; let mut res: Vec = Vec::new(); let fixes_from_diagnostics = diagnostics @@ -724,13 +729,13 @@ pub fn handle_code_action( for fix in fixes_from_diagnostics { let title = fix.label; - let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?; + let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; let action = lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None }; res.push(action); } - for fix in world.check_fixes.get(&file_id).into_iter().flatten() { + for fix in snap.check_fixes.get(&file_id).into_iter().flatten() { let fix_range = from_proto::text_range(&line_index, fix.range); if fix_range.intersect(range).is_none() { continue; @@ -738,31 +743,31 @@ pub fn handle_code_action( res.push(fix.action.clone()); } - for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() { - res.push(to_proto::code_action(&world, assist)?.into()); + for assist in snap.analysis().assists(&snap.config.assist, frange)?.into_iter() { + res.push(to_proto::code_action(&snap, assist)?.into()); } Ok(Some(res)) } pub fn handle_code_lens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CodeLensParams, ) -> Result>> { let _p = profile("handle_code_lens"); let mut lenses: Vec = Default::default(); - if world.config.lens.none() { + if snap.config.lens.none() { // early return before any db query! return Ok(Some(lenses)); } - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; - if world.config.lens.runnable() { + if snap.config.lens.runnable() { // Gather runnables - for runnable in world.analysis().runnables(file_id)? { + for runnable in snap.analysis().runnables(file_id)? { let (run_title, debugee) = match &runnable.kind { RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { ("▶\u{fe0e} Run Test", true) @@ -788,8 +793,8 @@ pub fn handle_code_lens( }; let range = to_proto::range(&line_index, runnable.nav.range()); - let r = to_proto::runnable(&world, file_id, runnable)?; - if world.config.lens.run { + let r = to_proto::runnable(&snap, file_id, runnable)?; + if snap.config.lens.run { let lens = CodeLens { range, command: Some(Command { @@ -802,7 +807,7 @@ pub fn handle_code_lens( lenses.push(lens); } - if debugee && world.config.lens.debug { + if debugee && snap.config.lens.debug { let debug_lens = CodeLens { range, command: Some(Command { @@ -817,11 +822,10 @@ pub fn handle_code_lens( } } - if world.config.lens.impementations { + if snap.config.lens.impementations { // Handle impls lenses.extend( - world - .analysis() + snap.analysis() .file_structure(file_id)? .into_iter() .filter(|it| match it.kind { @@ -856,14 +860,17 @@ enum CodeLensResolveData { Impls(lsp_types::request::GotoImplementationParams), } -pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result { +pub fn handle_code_lens_resolve( + snap: GlobalStateSnapshot, + code_lens: CodeLens, +) -> Result { let _p = profile("handle_code_lens_resolve"); let data = code_lens.data.unwrap(); let resolve = from_json::>("CodeLensResolveData", data)?; match resolve { Some(CodeLensResolveData::Impls(lens_params)) => { let locations: Vec = - match handle_goto_implementation(world, lens_params.clone())? { + match handle_goto_implementation(snap, lens_params.clone())? { Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc], Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs, Some(lsp_types::GotoDefinitionResponse::Link(links)) => links @@ -902,14 +909,14 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re } pub fn handle_document_highlight( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentHighlightParams, ) -> Result>> { let _p = profile("handle_document_highlight"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let line_index = world.analysis().file_line_index(position.file_id)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; - let refs = match world + let refs = match snap .analysis() .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))? { @@ -929,19 +936,19 @@ pub fn handle_document_highlight( } pub fn handle_ssr( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SsrParams, ) -> Result { let _p = profile("handle_ssr"); let source_change = - world.analysis().structural_search_replace(¶ms.query, params.parse_only)??; - to_proto::workspace_edit(&world, source_change) + snap.analysis().structural_search_replace(¶ms.query, params.parse_only)??; + to_proto::workspace_edit(&snap, source_change) } -pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result { +pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { let _p = profile("publish_diagnostics"); - let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics: Vec = world + let line_index = snap.analysis().file_line_index(file_id)?; + let diagnostics: Vec = snap .analysis() .diagnostics(file_id)? .into_iter() @@ -959,28 +966,28 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result Result> { let _p = profile("handle_inlay_hints"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let analysis = world.analysis(); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let analysis = snap.analysis(); let line_index = analysis.file_line_index(file_id)?; Ok(analysis - .inlay_hints(file_id, &world.config.inlay_hints)? + .inlay_hints(file_id, &snap.config.inlay_hints)? .into_iter() .map(|it| to_proto::inlay_int(&line_index, it)) .collect()) } pub fn handle_call_hierarchy_prepare( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyPrepareParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_prepare"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let nav_info = match world.analysis().call_hierarchy(position)? { + let nav_info = match snap.analysis().call_hierarchy(position)? { None => return Ok(None), Some(it) => it, }; @@ -989,24 +996,24 @@ pub fn handle_call_hierarchy_prepare( let res = navs .into_iter() .filter(|it| it.kind() == SyntaxKind::FN_DEF) - .map(|it| to_proto::call_hierarchy_item(&world, it)) + .map(|it| to_proto::call_hierarchy_item(&snap, it)) .collect::>>()?; Ok(Some(res)) } pub fn handle_call_hierarchy_incoming( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyIncomingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_incoming"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().incoming_calls(fpos)? { + let call_items = match snap.analysis().incoming_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1015,8 +1022,8 @@ pub fn handle_call_hierarchy_incoming( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyIncomingCall { from: item, from_ranges: call_item @@ -1031,17 +1038,17 @@ pub fn handle_call_hierarchy_incoming( } pub fn handle_call_hierarchy_outgoing( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyOutgoingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_outgoing"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().outgoing_calls(fpos)? { + let call_items = match snap.analysis().outgoing_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1050,8 +1057,8 @@ pub fn handle_call_hierarchy_outgoing( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyOutgoingCall { to: item, from_ranges: call_item @@ -1066,31 +1073,31 @@ pub fn handle_call_hierarchy_outgoing( } pub fn handle_semantic_tokens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensParams, ) -> Result> { let _p = profile("handle_semantic_tokens"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; - let highlights = world.analysis().highlight(file_id)?; + let highlights = snap.analysis().highlight(file_id)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } pub fn handle_semantic_tokens_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensRangeParams, ) -> Result> { let _p = profile("handle_semantic_tokens_range"); - let frange = from_proto::file_range(&world, params.text_document, params.range)?; - let text = world.analysis().file_text(frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; + let frange = from_proto::file_range(&snap, params.text_document, params.range)?; + let text = snap.analysis().file_text(frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; - let highlights = world.analysis().highlight_range(frange)?; + let highlights = snap.analysis().highlight_range(frange)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 85304aa8778e..0915a7fcb786 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}; use ra_vfs::LineEndings; use crate::{ - cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result, + cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext, + semantic_tokens, Result, }; pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { @@ -384,41 +385,44 @@ pub(crate) fn folding_range( } } -pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result { - world.file_id_to_uri(file_id) +pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { + snap.file_id_to_uri(file_id) } pub(crate) fn versioned_text_document_identifier( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_id: FileId, version: Option, ) -> Result { - let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version }; + let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version }; Ok(res) } -pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result { - let url = url(world, frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; +pub(crate) fn location( + snap: &GlobalStateSnapshot, + frange: FileRange, +) -> Result { + let url = url(snap, frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; let range = range(&line_index, frange.range); let loc = lsp_types::Location::new(url, range); Ok(loc) } pub(crate) fn location_link( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, target: NavigationTarget, ) -> Result { let origin_selection_range = match src { Some(src) => { - let line_index = world.analysis().file_line_index(src.file_id)?; + let line_index = snap.analysis().file_line_index(src.file_id)?; let range = range(&line_index, src.range); Some(range) } None => None, }; - let (target_uri, target_range, target_selection_range) = location_info(world, target)?; + let (target_uri, target_range, target_selection_range) = location_info(snap, target)?; let res = lsp_types::LocationLink { origin_selection_range, target_uri, @@ -429,12 +433,12 @@ pub(crate) fn location_link( } fn location_info( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { - let line_index = world.analysis().file_line_index(target.file_id())?; + let line_index = snap.analysis().file_line_index(target.file_id())?; - let target_uri = url(world, target.file_id())?; + let target_uri = url(snap, target.file_id())?; let target_range = range(&line_index, target.full_range()); let target_selection_range = target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); @@ -442,14 +446,14 @@ fn location_info( } pub(crate) fn goto_definition_response( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, targets: Vec, ) -> Result { - if world.config.client_caps.location_link { + if snap.config.client_caps.location_link { let links = targets .into_iter() - .map(|nav| location_link(world, src, nav)) + .map(|nav| location_link(snap, src, nav)) .collect::>>()?; Ok(links.into()) } else { @@ -457,7 +461,7 @@ pub(crate) fn goto_definition_response( .into_iter() .map(|nav| { location( - world, + snap, FileRange { file_id: nav.file_id(), range: nav.focus_range().unwrap_or(nav.range()), @@ -470,13 +474,13 @@ pub(crate) fn goto_definition_response( } pub(crate) fn snippet_text_document_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, is_snippet: bool, source_file_edit: SourceFileEdit, ) -> Result { - let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; - let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; - let line_endings = world.file_line_endings(source_file_edit.file_id); + let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?; + let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; + let line_endings = snap.file_line_endings(source_file_edit.file_id); let edits = source_file_edit .edit .into_iter() @@ -486,17 +490,17 @@ pub(crate) fn snippet_text_document_edit( } pub(crate) fn resource_op( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_system_edit: FileSystemEdit, ) -> Result { let res = match file_system_edit { FileSystemEdit::CreateFile { source_root, path } => { - let uri = world.path_to_uri(source_root, &path)?; + let uri = snap.path_to_uri(source_root, &path)?; lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) } FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { - let old_uri = world.file_id_to_uri(src)?; - let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; + let old_uri = snap.file_id_to_uri(src)?; + let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?; lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) } }; @@ -504,16 +508,16 @@ pub(crate) fn resource_op( } pub(crate) fn snippet_workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { let mut document_changes: Vec = Vec::new(); for op in source_change.file_system_edits { - let op = resource_op(&world, op)?; + let op = resource_op(&snap, op)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); } for edit in source_change.source_file_edits { - let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?; + let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let workspace_edit = @@ -522,11 +526,11 @@ pub(crate) fn snippet_workspace_edit( } pub(crate) fn workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { assert!(!source_change.is_snippet); - snippet_workspace_edit(world, source_change).map(|it| it.into()) + snippet_workspace_edit(snap, source_change).map(|it| it.into()) } impl From for lsp_types::WorkspaceEdit { @@ -565,13 +569,13 @@ impl From for lsp_types::WorkspaceEdit { } pub fn call_hierarchy_item( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result { let name = target.name().to_string(); let detail = target.description().map(|it| it.to_string()); let kind = symbol_kind(target.kind()); - let (uri, range, selection_range) = location_info(world, target)?; + let (uri, range, selection_range) = location_info(snap, target)?; Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) } @@ -619,23 +623,26 @@ fn main() { } } -pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result { +pub(crate) fn code_action( + snap: &GlobalStateSnapshot, + assist: Assist, +) -> Result { let res = lsp_ext::CodeAction { title: assist.label, - group: if world.config.client_caps.code_action_group { assist.group_label } else { None }, + group: if snap.config.client_caps.code_action_group { assist.group_label } else { None }, kind: Some(String::new()), - edit: Some(snippet_workspace_edit(world, assist.source_change)?), + edit: Some(snippet_workspace_edit(snap, assist.source_change)?), command: None, }; Ok(res) } pub(crate) fn runnable( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_id: FileId, runnable: Runnable, ) -> Result { - let spec = CargoTargetSpec::for_file(world, file_id)?; + let spec = CargoTargetSpec::for_file(snap, file_id)?; let target = spec.as_ref().map(|s| s.target.clone()); let (cargo_args, executable_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; @@ -648,14 +655,14 @@ pub(crate) fn runnable( target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) } }; - let location = location_link(world, None, runnable.nav)?; + let location = location_link(snap, None, runnable.nav)?; Ok(lsp_ext::Runnable { label, location: Some(location), kind: lsp_ext::RunnableKind::Cargo, args: lsp_ext::CargoRunnable { - workspace_root: world.workspace_root_for(file_id).map(|root| root.to_owned()), + workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()), cargo_args, executable_args, }, From 0a88de809f13f3b4abe0ffa11ff87c6f845050bd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 11:44:51 +0200 Subject: [PATCH 12/24] Move project discovery --- crates/ra_project_model/src/lib.rs | 16 ++++++++++++++-- crates/rust-analyzer/src/main_loop.rs | 8 ++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 9b30bef8d308..d5f82f17a9b6 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -14,7 +14,7 @@ use std::{ use anyhow::{bail, Context, Result}; use ra_cfg::CfgOptions; use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use serde_json::from_reader; pub use crate::{ @@ -57,7 +57,7 @@ impl PackageRoot { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum ProjectRoot { ProjectJson(PathBuf), CargoToml(PathBuf), @@ -128,6 +128,18 @@ impl ProjectRoot { .collect() } } + + pub fn discover_all(paths: &[impl AsRef]) -> Vec { + let mut res = paths + .iter() + .filter_map(|it| ProjectRoot::discover(it.as_ref()).ok()) + .flatten() + .collect::>() + .into_iter() + .collect::>(); + res.sort(); + res + } } impl ProjectWorkspace { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 35f2d7001e1c..82348c6fc068 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -28,7 +28,7 @@ use lsp_types::{ use ra_flycheck::{CheckTask, Status}; use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; use ra_prof::profile; -use ra_project_model::{PackageRoot, ProjectWorkspace}; +use ra_project_model::{PackageRoot, ProjectRoot, ProjectWorkspace}; use ra_vfs::{VfsFile, VfsTask, Watch}; use relative_path::RelativePathBuf; use rustc_hash::FxHashSet; @@ -96,11 +96,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) let mut global_state = { let workspaces = { // FIXME: support dynamic workspace loading. - let project_roots: FxHashSet<_> = ws_roots - .iter() - .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) - .flatten() - .collect(); + let project_roots = ProjectRoot::discover_all(&ws_roots); if project_roots.is_empty() && config.notifications.cargo_toml_not_found { show_message( From 03a76191a18b450a8e4ae309296fd7c0614d14d2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 12:05:50 +0200 Subject: [PATCH 13/24] Rename ProjectRoot -> ProjectManifest --- crates/ra_project_model/src/lib.rs | 30 +++++++++++----------- crates/rust-analyzer/src/cli/load_cargo.rs | 4 +-- crates/rust-analyzer/src/main_loop.rs | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index d5f82f17a9b6..a9961269026a 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -58,24 +58,24 @@ impl PackageRoot { } #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub enum ProjectRoot { +pub enum ProjectManifest { ProjectJson(PathBuf), CargoToml(PathBuf), } -impl ProjectRoot { - pub fn from_manifest_file(path: PathBuf) -> Result { +impl ProjectManifest { + pub fn from_manifest_file(path: PathBuf) -> Result { if path.ends_with("rust-project.json") { - return Ok(ProjectRoot::ProjectJson(path)); + return Ok(ProjectManifest::ProjectJson(path)); } if path.ends_with("Cargo.toml") { - return Ok(ProjectRoot::CargoToml(path)); + return Ok(ProjectManifest::CargoToml(path)); } bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) } - pub fn discover_single(path: &Path) -> Result { - let mut candidates = ProjectRoot::discover(path)?; + pub fn discover_single(path: &Path) -> Result { + let mut candidates = ProjectManifest::discover(path)?; let res = match candidates.pop() { None => bail!("no projects"), Some(it) => it, @@ -87,12 +87,12 @@ impl ProjectRoot { Ok(res) } - pub fn discover(path: &Path) -> io::Result> { + pub fn discover(path: &Path) -> io::Result> { if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { - return Ok(vec![ProjectRoot::ProjectJson(project_json)]); + return Ok(vec![ProjectManifest::ProjectJson(project_json)]); } return find_cargo_toml(path) - .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); + .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); fn find_cargo_toml(path: &Path) -> io::Result> { match find_in_parent_dirs(path, "Cargo.toml") { @@ -129,10 +129,10 @@ impl ProjectRoot { } } - pub fn discover_all(paths: &[impl AsRef]) -> Vec { + pub fn discover_all(paths: &[impl AsRef]) -> Vec { let mut res = paths .iter() - .filter_map(|it| ProjectRoot::discover(it.as_ref()).ok()) + .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) .flatten() .collect::>() .into_iter() @@ -144,12 +144,12 @@ impl ProjectRoot { impl ProjectWorkspace { pub fn load( - root: ProjectRoot, + root: ProjectManifest, cargo_features: &CargoConfig, with_sysroot: bool, ) -> Result { let res = match root { - ProjectRoot::ProjectJson(project_json) => { + ProjectManifest::ProjectJson(project_json) => { let file = File::open(&project_json).with_context(|| { format!("Failed to open json file {}", project_json.display()) })?; @@ -160,7 +160,7 @@ impl ProjectWorkspace { })?, } } - ProjectRoot::CargoToml(cargo_toml) => { + ProjectManifest::CargoToml(cargo_toml) => { let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) .with_context(|| { format!( diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 8eaf75ff6133..67491b42a7a9 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver}; use ra_db::{ExternSourceId, FileId, SourceRootId}; use ra_ide::{AnalysisChange, AnalysisHost}; use ra_project_model::{ - get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, + get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace, }; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -28,7 +28,7 @@ pub fn load_cargo( with_proc_macro: bool, ) -> Result<(AnalysisHost, FxHashMap)> { let root = std::env::current_dir()?.join(root); - let root = ProjectRoot::discover_single(&root)?; + let root = ProjectManifest::discover_single(&root)?; let ws = ProjectWorkspace::load( root, &CargoConfig { load_out_dirs_from_check, ..Default::default() }, diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 82348c6fc068..ea5b4c91c80f 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -28,7 +28,7 @@ use lsp_types::{ use ra_flycheck::{CheckTask, Status}; use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; use ra_prof::profile; -use ra_project_model::{PackageRoot, ProjectRoot, ProjectWorkspace}; +use ra_project_model::{PackageRoot, ProjectManifest, ProjectWorkspace}; use ra_vfs::{VfsFile, VfsTask, Watch}; use relative_path::RelativePathBuf; use rustc_hash::FxHashSet; @@ -96,7 +96,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) let mut global_state = { let workspaces = { // FIXME: support dynamic workspace loading. - let project_roots = ProjectRoot::discover_all(&ws_roots); + let project_roots = ProjectManifest::discover_all(&ws_roots); if project_roots.is_empty() && config.notifications.cargo_toml_not_found { show_message( From 8baa4c5d07690dc908b6299471c936c0d87ad871 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 12:22:01 +0200 Subject: [PATCH 14/24] Groundwork for specifying the set of projects via config --- crates/rust-analyzer/src/bin/main.rs | 41 +++++++++++++------ crates/rust-analyzer/src/cli/load_cargo.rs | 3 +- crates/rust-analyzer/src/config.rs | 25 ++++++++++- crates/rust-analyzer/src/main_loop.rs | 29 ++++++------- .../tests/heavy_tests/support.rs | 31 +++++++------- 5 files changed, 83 insertions(+), 46 deletions(-) diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e82fd57de84d..8d071ab1ca2b 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -4,9 +4,14 @@ mod args; use lsp_server::Connection; -use rust_analyzer::{cli, config::Config, from_json, Result}; +use rust_analyzer::{ + cli, + config::{Config, LinkedProject}, + from_json, Result, +}; use crate::args::HelpPrinted; +use ra_project_model::ProjectManifest; fn main() -> Result<()> { setup_logging()?; @@ -97,17 +102,6 @@ fn run_server() -> Result<()> { log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); } - let cwd = std::env::current_dir()?; - let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); - - let workspace_roots = initialize_params - .workspace_folders - .map(|workspaces| { - workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::>() - }) - .filter(|workspaces| !workspaces.is_empty()) - .unwrap_or_else(|| vec![root]); - let config = { let mut config = Config::default(); if let Some(value) = &initialize_params.initialization_options { @@ -115,10 +109,31 @@ fn run_server() -> Result<()> { } config.update_caps(&initialize_params.capabilities); + if config.linked_projects.is_empty() { + let cwd = std::env::current_dir()?; + let root = + initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); + let workspace_roots = initialize_params + .workspace_folders + .map(|workspaces| { + workspaces + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .collect::>() + }) + .filter(|workspaces| !workspaces.is_empty()) + .unwrap_or_else(|| vec![root]); + + config.linked_projects = ProjectManifest::discover_all(&workspace_roots) + .into_iter() + .map(LinkedProject::from) + .collect(); + } + config }; - rust_analyzer::main_loop(workspace_roots, config, connection)?; + rust_analyzer::main_loop(config, connection)?; log::info!("shutting down IO..."); io_threads.join()?; diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 67491b42a7a9..c7e86fe0c48b 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}; use ra_db::{ExternSourceId, FileId, SourceRootId}; use ra_ide::{AnalysisChange, AnalysisHost}; use ra_project_model::{ - get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace, + get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, + ProjectWorkspace, }; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use rustc_hash::{FxHashMap, FxHashSet}; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 9c6e369d2e1e..761bc9c2d92a 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}; use lsp_types::ClientCapabilities; use ra_flycheck::FlycheckConfig; use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; -use ra_project_model::CargoConfig; +use ra_project_model::{CargoConfig, JsonProject, ProjectManifest}; use serde::Deserialize; #[derive(Debug, Clone)] pub struct Config { pub client_caps: ClientCapsConfig, - pub with_sysroot: bool, pub publish_diagnostics: bool, pub lru_capacity: Option, pub proc_macro_srv: Option<(PathBuf, Vec)>, @@ -35,6 +34,27 @@ pub struct Config { pub assist: AssistConfig, pub call_info_full: bool, pub lens: LensConfig, + + pub with_sysroot: bool, + pub linked_projects: Vec, +} + +#[derive(Debug, Clone)] +pub enum LinkedProject { + ProjectManifest(ProjectManifest), + JsonProject(JsonProject), +} + +impl From for LinkedProject { + fn from(v: ProjectManifest) -> Self { + LinkedProject::ProjectManifest(v) + } +} + +impl From for LinkedProject { + fn from(v: JsonProject) -> Self { + LinkedProject::JsonProject(v) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -141,6 +161,7 @@ impl Default for Config { assist: AssistConfig::default(), call_info_full: true, lens: LensConfig::default(), + linked_projects: Vec::new(), } } } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index ea5b4c91c80f..d901f21d7e7e 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -12,13 +12,11 @@ use std::{ fmt, ops::Range, panic, - path::PathBuf, sync::Arc, time::{Duration, Instant}, }; use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; -use itertools::Itertools; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, @@ -28,7 +26,7 @@ use lsp_types::{ use ra_flycheck::{CheckTask, Status}; use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; use ra_prof::profile; -use ra_project_model::{PackageRoot, ProjectManifest, ProjectWorkspace}; +use ra_project_model::{PackageRoot, ProjectWorkspace}; use ra_vfs::{VfsFile, VfsTask, Watch}; use relative_path::RelativePathBuf; use rustc_hash::FxHashSet; @@ -36,7 +34,7 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ - config::{Config, FilesWatcher}, + config::{Config, FilesWatcher, LinkedProject}, diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, from_proto, global_state::{GlobalState, GlobalStateSnapshot}, @@ -70,7 +68,7 @@ impl fmt::Display for LspError { impl Error for LspError {} -pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) -> Result<()> { +pub fn main_loop(config: Config, connection: Connection) -> Result<()> { log::info!("initial config: {:#?}", config); // Windows scheduler implements priority boosts: if thread waits for an @@ -95,25 +93,24 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) let mut loop_state = LoopState::default(); let mut global_state = { let workspaces = { - // FIXME: support dynamic workspace loading. - let project_roots = ProjectManifest::discover_all(&ws_roots); - - if project_roots.is_empty() && config.notifications.cargo_toml_not_found { + if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found { show_message( lsp_types::MessageType::Error, - format!( - "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", - ws_roots.iter().format_with(", ", |it, f| f(&it.display())) - ), + "rust-analyzer failed to discover workspace".to_string(), &connection.sender, ); }; - project_roots - .into_iter() + config + .linked_projects + .iter() + .filter_map(|project| match project { + LinkedProject::ProjectManifest(it) => Some(it), + LinkedProject::JsonProject(_) => None, + }) .filter_map(|root| { ra_project_model::ProjectWorkspace::load( - root, + root.clone(), &config.cargo, config.with_sysroot, ) diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 66a6f4d541a2..30d03b622b91 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}; use tempfile::TempDir; use test_utils::{find_mismatch, parse_fixture}; +use ra_project_model::ProjectManifest; use rust_analyzer::{ - config::{ClientCapsConfig, Config}, + config::{ClientCapsConfig, Config, LinkedProject}, main_loop, }; @@ -42,7 +43,7 @@ impl<'a> Project<'a> { self } - pub fn root(mut self, path: &str) -> Project<'a> { + pub(crate) fn root(mut self, path: &str) -> Project<'a> { self.roots.push(path.into()); self } @@ -74,7 +75,16 @@ impl<'a> Project<'a> { paths.push((path, entry.text)); } - let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); + let mut roots = + self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::>(); + if roots.is_empty() { + roots.push(tmp_dir.path().to_path_buf()); + } + let linked_projects = roots + .into_iter() + .map(|it| ProjectManifest::discover_single(&it).unwrap()) + .map(LinkedProject::from) + .collect::>(); let mut config = Config { client_caps: ClientCapsConfig { @@ -84,6 +94,7 @@ impl<'a> Project<'a> { ..Default::default() }, with_sysroot: self.with_sysroot, + linked_projects, ..Config::default() }; @@ -91,7 +102,7 @@ impl<'a> Project<'a> { f(&mut config) } - Server::new(tmp_dir, config, roots, paths) + Server::new(tmp_dir, config, paths) } } @@ -109,20 +120,12 @@ pub struct Server { } impl Server { - fn new( - dir: TempDir, - config: Config, - roots: Vec, - files: Vec<(PathBuf, String)>, - ) -> Server { - let path = dir.path().to_path_buf(); - - let roots = if roots.is_empty() { vec![path] } else { roots }; + fn new(dir: TempDir, config: Config, files: Vec<(PathBuf, String)>) -> Server { let (connection, client) = Connection::memory(); let _thread = jod_thread::Builder::new() .name("test server".to_string()) - .spawn(move || main_loop(roots, config, connection).unwrap()) + .spawn(move || main_loop(config, connection).unwrap()) .expect("failed to spawn a thread"); let res = From 2e7d12d2f3ccfd1ef9946d1212e2455e5937c521 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 12:39:11 +0200 Subject: [PATCH 15/24] Drop test for old format --- crates/rust-analyzer/tests/heavy_tests/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 69dc719c54a5..c1805787a398 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -331,8 +331,6 @@ fn test_missing_module_code_action_in_json_project() { "deps": [], "edition": "2015", "cfg": [ "cfg_atom_1", "feature=cfg_1"], - "atom_cfgs": ["atom_2"], - "key_value_cfgs": { "feature": "key_value_feature", "other": "value"} } ] }); From 4c655c01f31ceffae4f8219f9706992e0e7f188a Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Sat, 30 May 2020 14:21:06 -0400 Subject: [PATCH 16/24] Enable hover and autocomplete docs on macro generated items --- crates/ra_hir_def/src/attr.rs | 8 ++++- crates/ra_hir_def/src/docs.rs | 43 +++++++++++++++++++++-- crates/ra_hir_expand/src/name.rs | 1 + crates/ra_ide/src/hover.rs | 55 +++++++++++++++++++++++++++--- crates/ra_syntax/src/ast/traits.rs | 13 +++++-- 5 files changed, 110 insertions(+), 10 deletions(-) diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 8b6c0bedee78..2eeba0572991 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -87,12 +87,18 @@ impl Attrs { } pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { + let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( + |docs_text| Attr { + input: Some(AttrInput::Literal(SmolStr::new(docs_text))), + path: ModPath::from(hir_expand::name!(doc)), + }, + ); let mut attrs = owner.attrs().peekable(); let entries = if attrs.peek().is_none() { // Avoid heap allocation None } else { - Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) }; Attrs { entries } } diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1cece3..74b9f8199c10 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -70,6 +70,45 @@ impl Documentation { } } -pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - node.doc_comment_text().map(|it| Documentation::new(&it)) +pub(crate) fn docs_from_ast(node: &N) -> Option +where + N: ast::DocCommentsOwner + ast::AttrsOwner, +{ + let doc_comment_text = node.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(node); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + docs.map(|it| Documentation::new(&it)) +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs) + } } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index ea495cb11a2b..660bdfe3365b 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -153,6 +153,7 @@ pub mod known { str, // Special names macro_rules, + doc, // Components of known path (value or mod name) std, core, diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d96cb5596918..e25a7dacf01b 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -169,13 +169,19 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) + let doc_comment_text = src.value.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&src.value); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let doc_comment_text = it.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&it); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -183,7 +189,10 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let doc_comment_text = it.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&it); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -208,10 +217,46 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) + let doc_comment_text = src.value.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&src.value); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, src.value.short_label(), mod_path) + } +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs) } } diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index bfc05e08bf24..a8f2454fd96e 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 { CommentIter { iter: self.syntax().children_with_tokens() } } + fn doc_comment_text(&self) -> Option { + self.doc_comments().doc_comment_text() + } +} + +impl CommentIter { + pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter { + CommentIter { iter: syntax_node.children_with_tokens() } + } + /// Returns the textual content of a doc comment block as a single string. /// That is, strips leading `///` (+ optional 1 character of whitespace), /// trailing `*/`, trailing whitespace and then joins the lines. - fn doc_comment_text(&self) -> Option { + pub fn doc_comment_text(self) -> Option { let mut has_comments = false; let docs = self - .doc_comments() .filter(|comment| comment.kind().doc.is_some()) .map(|comment| { has_comments = true; From 5837acce532e0cd65a1c0cb8c03cc18a4c22f327 Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Sun, 31 May 2020 11:33:48 -0400 Subject: [PATCH 17/24] Add basic hover and completion doc tests for macro generated items --- crates/ra_hir_def/src/docs.rs | 2 +- crates/ra_ide/src/completion.rs | 78 ++++++++++++++++++++++++ crates/ra_ide/src/hover.rs | 104 +++++++++++++++++++++++++++++++- 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index 74b9f8199c10..ab43cd3d3395 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -109,6 +109,6 @@ fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { if docs.is_empty() { None } else { - Some(docs) + Some(docs.trim_end_matches("\n\n").to_owned()) } } diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index d890b69d26fa..a721e23c6934 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -125,3 +125,81 @@ pub(crate) fn completions( Some(acc) } + +#[cfg(test)] +mod tests { + use crate::completion::completion_config::CompletionConfig; + use crate::mock_analysis::analysis_and_position; + + struct DetailAndDocumentation<'a> { + detail: &'a str, + documentation: &'a str, + } + + fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { + let (analysis, position) = analysis_and_position(fixture); + let config = CompletionConfig::default(); + let completions = analysis.completions(&config, position).unwrap().unwrap(); + for item in completions { + if item.detail() == Some(expected.detail) { + let opt = item.documentation(); + let doc = opt.as_ref().map(|it| it.as_str()); + assert_eq!(doc, Some(expected.documentation)); + return; + } + } + panic!("completion detail not found: {}", expected.detail) + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, + ); + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, + ); + } +} diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index e25a7dacf01b..731fc3673535 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -256,7 +256,7 @@ fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { if docs.is_empty() { None } else { - Some(docs) + Some(docs.trim_end_matches("\n\n").to_owned()) } } @@ -996,4 +996,106 @@ fn func(foo: i32) { if true { <|>foo; }; } &["mod my"], ); } + + #[test] + fn test_hover_struct_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + #[doc = "bar docs"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr_multiple_and_mixed() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs 0 + #[doc = "bar docs 1"] + #[doc = "bar docs 2"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + ); + } } From 85c4edb0afbc7cc855c434e5e7ec69aa469e0c4b Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Wed, 3 Jun 2020 06:14:56 -0400 Subject: [PATCH 18/24] Consolidate documentation expansion and merging Removes the duplicated `expand_doc_attrs` and `merge_doc_comments_and_attrs` functions from `ra_ide` and exposes the same functionality via `ra_hir::Documentation::from_ast`. --- crates/ra_hir_def/src/docs.rs | 7 ++++ crates/ra_ide/src/hover.rs | 60 ++++------------------------------- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index ab43cd3d3395..2630b3d895e6 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -29,6 +29,13 @@ impl Documentation { Documentation(s.into()) } + pub fn from_ast(node: &N) -> Option + where + N: ast::DocCommentsOwner + ast::AttrsOwner, + { + docs_from_ast(node) + } + pub fn as_str(&self) -> &str { &*self.0 } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 731fc3673535..9636cd0d6af7 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,8 @@ use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -10,12 +10,7 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, @@ -169,18 +164,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - let doc_comment_text = src.value.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&src.value); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&src.value).map(Into::into); hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - let doc_comment_text = it.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&it); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&it).map(Into::into); hover_text(docs, it.short_label(), mod_path) } _ => None, @@ -189,9 +180,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - let doc_comment_text = it.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&it); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&it).map(Into::into); hover_text(docs, it.short_label(), mod_path) } _ => None, @@ -220,46 +209,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option, - doc_attr_text: Option, -) -> Option { - match (doc_comment_text, doc_attr_text) { - (Some(mut comment_text), Some(attr_text)) => { - comment_text.push_str("\n\n"); - comment_text.push_str(&attr_text); - Some(comment_text) - } - (Some(comment_text), None) => Some(comment_text), - (None, Some(attr_text)) => Some(attr_text), - (None, None) => None, - } -} - -fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { - let mut docs = String::new(); - for attr in owner.attrs() { - if let Some(("doc", value)) = - attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) - { - docs.push_str(value); - docs.push_str("\n\n"); - } - } - if docs.is_empty() { - None - } else { - Some(docs.trim_end_matches("\n\n").to_owned()) - } -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { From ed866892640214d315d3e9503ccaed96ca87ccc0 Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Wed, 3 Jun 2020 06:55:27 -0400 Subject: [PATCH 19/24] Update generated feature docs --- docs/user/generated_features.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc index 12812fa0be7e..4b93b759fa42 100644 --- a/docs/user/generated_features.adoc +++ b/docs/user/generated_features.adoc @@ -76,7 +76,7 @@ Navigates to the type of an identifier. === Hover -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L63[hover.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L58[hover.rs] Shows additional information, like type of an expression or documentation for definition when "focusing" code. Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. From f06b2bcd91329fb795839a4eabd8f43aa472aeb2 Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Wed, 3 Jun 2020 07:26:15 -0400 Subject: [PATCH 20/24] Use split1 when formatting function signature params --- Cargo.lock | 1 + crates/ra_ide/src/display/function_signature.rs | 8 +++----- crates/stdx/src/lib.rs | 5 +++++ crates/test_utils/Cargo.toml | 3 ++- crates/test_utils/src/lib.rs | 6 +----- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index def4ed45e04e..aca283cda34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1639,6 +1639,7 @@ dependencies = [ "relative-path", "rustc-hash", "serde_json", + "stdx", "text-size", ] diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index b081ecaad1cd..ca8a6a650994 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::{ use hir::{Docs, Documentation, HasSource, HirDisplay}; use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; -use stdx::SepBy; +use stdx::{split1, SepBy}; use crate::display::{generic_parameters, where_predicates}; @@ -210,10 +210,8 @@ impl From<&'_ ast::FnDef> for FunctionSignature { // macro-generated functions are missing whitespace fn fmt_param(param: ast::Param) -> String { let text = param.syntax().text().to_string(); - match text.find(':') { - Some(pos) if 1 + pos < text.len() => { - format!("{} {}", &text[0..1 + pos].trim(), &text[1 + pos..].trim()) - } + match split1(&text, ':') { + Some((left, right)) => format!("{}: {}", left.trim(), right.trim()), _ => text, } } diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 71a57fba230f..c0356344ca24 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) { // FIXME: do this in place. *buf = buf.replace(from, to) } + +pub fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { + let idx = haystack.find(delim)?; + Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) +} diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index 4d185b01c752..8840bf36ae32 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml @@ -14,4 +14,5 @@ serde_json = "1.0.48" relative-path = "1.0.0" rustc-hash = "1.1.0" -ra_cfg = { path = "../ra_cfg" } \ No newline at end of file +ra_cfg = { path = "../ra_cfg" } +stdx = { 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 1bd97215cb83..2141bfc20277 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -15,6 +15,7 @@ use std::{ }; pub use ra_cfg::CfgOptions; +use stdx::split1; pub use relative_path::{RelativePath, RelativePathBuf}; pub use rustc_hash::FxHashMap; @@ -332,11 +333,6 @@ fn parse_meta(meta: &str) -> FixtureMeta { FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env }) } -fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { - let idx = haystack.find(delim)?; - Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) -} - /// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. /// This allows fixtures to start off in a different indentation, e.g. to align the first line with /// the other lines visually: From fa019c8f562326a720d2ef9165626c4c5703f67b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 14:48:38 +0200 Subject: [PATCH 21/24] Document rust-project.json --- crates/ra_project_model/src/lib.rs | 10 ++++-- crates/rust-analyzer/src/config.rs | 23 ++++++++++++ crates/rust-analyzer/src/main_loop.rs | 35 +++++++++--------- docs/user/manual.adoc | 51 +++++++++++++++++++++++++++ editors/code/package.json | 19 ++++++++++ 5 files changed, 118 insertions(+), 20 deletions(-) diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index a9961269026a..7ad941279506 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -32,6 +32,12 @@ pub enum ProjectWorkspace { Json { project: JsonProject }, } +impl From for ProjectWorkspace { + fn from(project: JsonProject) -> ProjectWorkspace { + ProjectWorkspace::Json { project } + } +} + /// `PackageRoot` describes a package root folder. /// Which may be an external dependency, or a member of /// the current workspace. @@ -144,11 +150,11 @@ impl ProjectManifest { impl ProjectWorkspace { pub fn load( - root: ProjectManifest, + manifest: ProjectManifest, cargo_features: &CargoConfig, with_sysroot: bool, ) -> Result { - let res = match root { + let res = match manifest { ProjectManifest::ProjectJson(project_json) => { let file = File::open(&project_json).with_context(|| { format!("Failed to open json file {}", project_json.display()) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 761bc9c2d92a..0e5dc56fd744 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -261,6 +261,22 @@ impl Config { self.lens = LensConfig::NO_LENS; } + if let Some(linked_projects) = get::>(value, "/linkedProjects") { + if !linked_projects.is_empty() { + self.linked_projects.clear(); + for linked_project in linked_projects { + let linked_project = match linked_project { + ManifestOrJsonProject::Manifest(it) => match ProjectManifest::from_manifest_file(it) { + Ok(it) => it.into(), + Err(_) => continue, + } + ManifestOrJsonProject::JsonProject(it) => it.into(), + }; + self.linked_projects.push(linked_project); + } + } + } + log::info!("Config::update() = {:#?}", self); fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option { @@ -324,3 +340,10 @@ impl Config { } } } + +#[derive(Deserialize)] +#[serde(untagged)] +enum ManifestOrJsonProject { + Manifest(PathBuf), + JsonProject(JsonProject), +} diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index d901f21d7e7e..1f8f6b978616 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -105,24 +105,23 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { .linked_projects .iter() .filter_map(|project| match project { - LinkedProject::ProjectManifest(it) => Some(it), - LinkedProject::JsonProject(_) => None, - }) - .filter_map(|root| { - ra_project_model::ProjectWorkspace::load( - root.clone(), - &config.cargo, - config.with_sysroot, - ) - .map_err(|err| { - log::error!("failed to load workspace: {:#}", err); - show_message( - lsp_types::MessageType::Error, - format!("rust-analyzer failed to load workspace: {:#}", err), - &connection.sender, - ); - }) - .ok() + LinkedProject::ProjectManifest(manifest) => { + ra_project_model::ProjectWorkspace::load( + manifest.clone(), + &config.cargo, + config.with_sysroot, + ) + .map_err(|err| { + log::error!("failed to load workspace: {:#}", err); + show_message( + lsp_types::MessageType::Error, + format!("rust-analyzer failed to load workspace: {:#}", err), + &connection.sender, + ); + }) + .ok() + } + LinkedProject::JsonProject(it) => Some(it.clone().into()), }) .collect::>() }; diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 202783fd9538..ea714f49addf 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 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). 2. Enable the Rust Builder plugin. +== Non-Cargo Based Projects + +rust-analyzer does not require Cargo. +However, 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: + +[source,TypeScript] +---- +interface JsonProject { + /// The set of paths containing the crates for this project. + /// Any `Crate` must be nested inside some `root`. + roots: string[]; + /// The set of crates comprising the current project. + /// Must include all transitive dependencies as well as sysroot crate (libstd, libcore and such). + crates: Crate[]; +} + +interface Crate { + /// Path to the root module of the crate. + root_module: string; + /// Edition of the crate. + edition: "2015" | "2018"; + /// Dependencies + deps: Dep[]; + /// The set of cfgs activated for a given crate, like `["unix", "feature=foo", "feature=bar"]`. + cfg: string[]; + + /// value of the OUT_DIR env variable. + out_dir?: string; + /// For proc-macro crates, path to compiles proc-macro (.so file). + proc_macro_dylib_path?: string; +} + +interface Dep { + /// Index of a crate in the `crates` array. + crate: number, + /// Name as should appear in the (implicit) `extern crate name` declaration. + name: string, +} +---- + +This format is provisional and subject to change. +Specifically, the `roots` setup will be different eventually. + +There are tree ways to feed `rust-project.json` to rust-analyzer: + +* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it. +* 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). +* Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline. + +See https://github.com/rust-analyzer/rust-project.json-example for a small example. + == Features include::./generated_features.adoc[] diff --git a/editors/code/package.json b/editors/code/package.json index d8f4287fd88c..30ab7ba4a9f8 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -475,6 +475,25 @@ "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true + }, + "rust-analyzer.linkedProjects": { + "markdownDescription": [ + "Disable project auto-discovery in favor of explicitly specified set of projects.", + "Elements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format" + ], + "type": "array", + "items": { + "type": [ + "string", + "object" + ] + }, + "default": null + }, + "rust-analyzer.withSysroot": { + "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", + "type": "boolean", + "default": true } } }, From 9e71fc0314a2555eda446a64057df7e8f78fb7c9 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 17 May 2020 21:03:40 -0400 Subject: [PATCH 22/24] Mark fixes from diagnostics as quick fixes --- crates/rust-analyzer/src/main_loop/handlers.rs | 9 +++++++-- crates/rust-analyzer/tests/heavy_tests/main.rs | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 1bb8e44735f5..c2a5bf4d6b8f 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -730,8 +730,13 @@ pub fn handle_code_action( for fix in fixes_from_diagnostics { let title = fix.label; let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; - let action = - lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None }; + let action = lsp_ext::CodeAction { + title, + group: None, + kind: Some(lsp_types::code_action_kind::QUICKFIX.into()), + edit: Some(edit), + command: None, + }; res.push(action); } diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index c1805787a398..ad347631054b 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -298,6 +298,7 @@ fn main() {} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); @@ -368,6 +369,7 @@ fn main() {{}} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); From bc2edc1d1a0c1fc5404ab64275a44284f1932980 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Wed, 3 Jun 2020 09:43:27 -0400 Subject: [PATCH 23/24] Cargo update --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc63ec36ceda..5f88ad0c4db5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,9 +462,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg", ] @@ -809,9 +809,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b" +checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" dependencies = [ "paste-impl", "proc-macro-hack", @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17" +checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -871,9 +871,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] @@ -1401,9 +1401,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "salsa" @@ -1577,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" [[package]] name = "syn" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ "proc-macro2", "quote", @@ -1799,9 +1799,9 @@ dependencies = [ [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] From 5315934d888797432d62ec4a55303aeacb8cd286 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 3 Jun 2020 18:22:05 +0200 Subject: [PATCH 24/24] Don't store generated docs in the repo --- .gitignore | 2 + docs/user/generated_assists.adoc | 1015 ------------------------- docs/user/generated_features.adoc | 298 -------- xtask/src/codegen.rs | 6 +- xtask/src/codegen/gen_assists_docs.rs | 11 +- xtask/src/lib.rs | 2 + xtask/src/main.rs | 1 + xtask/tests/tidy.rs | 9 +- 8 files changed, 16 insertions(+), 1328 deletions(-) delete mode 100644 docs/user/generated_assists.adoc delete mode 100644 docs/user/generated_features.adoc diff --git a/.gitignore b/.gitignore index dab51647db70..aef0fac3397f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ crates/*/target *.iml .vscode/settings.json *.html +generated_assists.adoc +generated_features.adoc diff --git a/docs/user/generated_assists.adoc b/docs/user/generated_assists.adoc deleted file mode 100644 index 4d2fb31d4840..000000000000 --- a/docs/user/generated_assists.adoc +++ /dev/null @@ -1,1015 +0,0 @@ -[discrete] -=== `add_custom_impl` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_custom_impl.rs#L14[add_custom_impl.rs] - -Adds impl block for derived trait. - -.Before -```rust -#[derive(Deb┃ug, Display)] -struct S; -``` - -.After -```rust -#[derive(Display)] -struct S; - -impl Debug for S { - $0 -} -``` - - -[discrete] -=== `add_derive` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_derive.rs#L9[add_derive.rs] - -Adds a new `#[derive()]` clause to a struct or enum. - -.Before -```rust -struct Point { - x: u32, - y: u32,┃ -} -``` - -.After -```rust -#[derive($0)] -struct Point { - x: u32, - y: u32, -} -``` - - -[discrete] -=== `add_explicit_type` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_explicit_type.rs#L9[add_explicit_type.rs] - -Specify type for a let binding. - -.Before -```rust -fn main() { - let x┃ = 92; -} -``` - -.After -```rust -fn main() { - let x: i32 = 92; -} -``` - - -[discrete] -=== `add_from_impl_for_enum` -**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] - -Adds a From impl for an enum variant with one tuple field. - -.Before -```rust -enum A { ┃One(u32) } -``` - -.After -```rust -enum A { One(u32) } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -} -``` - - -[discrete] -=== `add_function` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_function.rs#L19[add_function.rs] - -Adds a stub function with a signature matching the function under the cursor. - -.Before -```rust -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar┃("", baz()); -} - -``` - -.After -```rust -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar("", baz()); -} - -fn bar(arg: &str, baz: Baz) { - ${0:todo!()} -} - -``` - - -[discrete] -=== `add_hash` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L65[raw_string.rs] - -Adds a hash to a raw string literal. - -.Before -```rust -fn main() { - r#"Hello,┃ World!"#; -} -``` - -.After -```rust -fn main() { - r##"Hello, World!"##; -} -``` - - -[discrete] -=== `add_impl` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_impl.rs#L6[add_impl.rs] - -Adds a new inherent impl for a type. - -.Before -```rust -struct Ctx { - data: T,┃ -} -``` - -.After -```rust -struct Ctx { - data: T, -} - -impl Ctx { - $0 -} -``` - - -[discrete] -=== `add_impl_default_members` -**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] - -Adds scaffold for overriding default impl members. - -.Before -```rust -trait Trait { - Type X; - fn foo(&self); - fn bar(&self) {} -} - -impl Trait for () { - Type X = (); - fn foo(&self) {}┃ - -} -``` - -.After -```rust -trait Trait { - Type X; - fn foo(&self); - fn bar(&self) {} -} - -impl Trait for () { - Type X = (); - fn foo(&self) {} - $0fn bar(&self) {} - -} -``` - - -[discrete] -=== `add_impl_missing_members` -**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] - -Adds scaffold for required impl members. - -.Before -```rust -trait Trait { - Type X; - fn foo(&self) -> T; - fn bar(&self) {} -} - -impl Trait for () {┃ - -} -``` - -.After -```rust -trait Trait { - Type X; - fn foo(&self) -> T; - fn bar(&self) {} -} - -impl Trait for () { - fn foo(&self) -> u32 { - ${0:todo!()} - } - -} -``` - - -[discrete] -=== `add_new` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_new.rs#L12[add_new.rs] - -Adds a new inherent impl for a type. - -.Before -```rust -struct Ctx { - data: T,┃ -} -``` - -.After -```rust -struct Ctx { - data: T, -} - -impl Ctx { - fn $0new(data: T) -> Self { Self { data } } -} - -``` - - -[discrete] -=== `add_turbo_fish` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_turbo_fish.rs#L10[add_turbo_fish.rs] - -Adds `::<_>` to a call of a generic method or function. - -.Before -```rust -fn make() -> T { todo!() } -fn main() { - let x = make┃(); -} -``` - -.After -```rust -fn make() -> T { todo!() } -fn main() { - let x = make::<${0:_}>(); -} -``` - - -[discrete] -=== `apply_demorgan` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/apply_demorgan.rs#L5[apply_demorgan.rs] - -Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). -This transforms expressions of the form `!l || !r` into `!(l && r)`. -This also works with `&&`. This assist can only be applied with the cursor -on either `||` or `&&`, with both operands being a negation of some kind. -This means something of the form `!x` or `x != y`. - -.Before -```rust -fn main() { - if x != 4 ||┃ !y {} -} -``` - -.After -```rust -fn main() { - if !(x == 4 && y) {} -} -``` - - -[discrete] -=== `auto_import` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/auto_import.rs#L18[auto_import.rs] - -If the name is unresolved, provides all possible imports for it. - -.Before -```rust -fn main() { - let map = HashMap┃::new(); -} -``` - -.After -```rust -use std::collections::HashMap; - -fn main() { - let map = HashMap::new(); -} -``` - - -[discrete] -=== `change_return_type_to_result` -**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] - -Change the function's return type to Result. - -.Before -```rust -fn foo() -> i32┃ { 42i32 } -``` - -.After -```rust -fn foo() -> Result { Ok(42i32) } -``` - - -[discrete] -=== `change_visibility` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_visibility.rs#L14[change_visibility.rs] - -Adds or changes existing visibility specifier. - -.Before -```rust -┃fn frobnicate() {} -``` - -.After -```rust -pub(crate) fn frobnicate() {} -``` - - -[discrete] -=== `convert_to_guarded_return` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/early_return.rs#L21[early_return.rs] - -Replace a large conditional with a guarded return. - -.Before -```rust -fn main() { - ┃if cond { - foo(); - bar(); - } -} -``` - -.After -```rust -fn main() { - if !cond { - return; - } - foo(); - bar(); -} -``` - - -[discrete] -=== `fill_match_arms` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fill_match_arms.rs#L14[fill_match_arms.rs] - -Adds missing clauses to a `match` expression. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - ┃ - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - $0Action::Move { distance } => {} - Action::Stop => {} - } -} -``` - - -[discrete] -=== `fix_visibility` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fix_visibility.rs#L13[fix_visibility.rs] - -Makes inaccessible item public. - -.Before -```rust -mod m { - fn frobnicate() {} -} -fn main() { - m::frobnicate┃() {} -} -``` - -.After -```rust -mod m { - $0pub(crate) fn frobnicate() {} -} -fn main() { - m::frobnicate() {} -} -``` - - -[discrete] -=== `flip_binexpr` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_binexpr.rs#L5[flip_binexpr.rs] - -Flips operands of a binary expression. - -.Before -```rust -fn main() { - let _ = 90 +┃ 2; -} -``` - -.After -```rust -fn main() { - let _ = 2 + 90; -} -``` - - -[discrete] -=== `flip_comma` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_comma.rs#L5[flip_comma.rs] - -Flips two comma-separated items. - -.Before -```rust -fn main() { - ((1, 2),┃ (3, 4)); -} -``` - -.After -```rust -fn main() { - ((3, 4), (1, 2)); -} -``` - - -[discrete] -=== `flip_trait_bound` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_trait_bound.rs#L9[flip_trait_bound.rs] - -Flips two trait bounds. - -.Before -```rust -fn foo() { } -``` - -.After -```rust -fn foo() { } -``` - - -[discrete] -=== `inline_local_variable` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/inline_local_variable.rs#L13[inline_local_variable.rs] - -Inlines local variable. - -.Before -```rust -fn main() { - let x┃ = 1 + 2; - x * 4; -} -``` - -.After -```rust -fn main() { - (1 + 2) * 4; -} -``` - - -[discrete] -=== `introduce_named_lifetime` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_named_lifetime.rs#L12[introduce_named_lifetime.rs] - -Change an anonymous lifetime to a named lifetime. - -.Before -```rust -impl Cursor<'_┃> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -``` - -.After -```rust -impl<'a> Cursor<'a> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -``` - - -[discrete] -=== `introduce_variable` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_variable.rs#L14[introduce_variable.rs] - -Extracts subexpression into a variable. - -.Before -```rust -fn main() { - ┃(1 + 2)┃ * 4; -} -``` - -.After -```rust -fn main() { - let $0var_name = (1 + 2); - var_name * 4; -} -``` - - -[discrete] -=== `invert_if` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/invert_if.rs#L12[invert_if.rs] - -Apply invert_if -This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` -This also works with `!=`. This assist can only be applied with the cursor -on `if`. - -.Before -```rust -fn main() { - if┃ !y { A } else { B } -} -``` - -.After -```rust -fn main() { - if y { B } else { A } -} -``` - - -[discrete] -=== `make_raw_string` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L10[raw_string.rs] - -Adds `r#` to a plain string literal. - -.Before -```rust -fn main() { - "Hello,┃ World!"; -} -``` - -.After -```rust -fn main() { - r#"Hello, World!"#; -} -``` - - -[discrete] -=== `make_usual_string` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L39[raw_string.rs] - -Turns a raw string into a plain string. - -.Before -```rust -fn main() { - r#"Hello,┃ "World!""#; -} -``` - -.After -```rust -fn main() { - "Hello, \"World!\""; -} -``` - - -[discrete] -=== `merge_imports` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_imports.rs#L14[merge_imports.rs] - -Merges two imports with a common prefix. - -.Before -```rust -use std::┃fmt::Formatter; -use std::io; -``` - -.After -```rust -use std::{fmt::Formatter, io}; -``` - - -[discrete] -=== `merge_match_arms` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_match_arms.rs#L11[merge_match_arms.rs] - -Merges identical match arms. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - ┃Action::Move(..) => foo(), - Action::Stop => foo(), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move(..) | Action::Stop => foo(), - } -} -``` - - -[discrete] -=== `move_arm_cond_to_match_guard` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L56[move_guard.rs] - -Moves if expression from match arm body into a guard. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => ┃if distance > 10 { foo() }, - _ => (), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } if distance > 10 => foo(), - _ => (), - } -} -``` - - -[discrete] -=== `move_bounds_to_where_clause` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_bounds.rs#L10[move_bounds.rs] - -Moves inline type bounds to a where clause. - -.Before -```rust -fn apply U>(f: F, x: T) -> U { - f(x) -} -``` - -.After -```rust -fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { - f(x) -} -``` - - -[discrete] -=== `move_guard_to_arm_body` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L8[move_guard.rs] - -Moves match guard into match arm body. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } ┃if distance > 10 => foo(), - _ => (), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => if distance > 10 { foo() }, - _ => (), - } -} -``` - - -[discrete] -=== `remove_dbg` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_dbg.rs#L8[remove_dbg.rs] - -Removes `dbg!()` macro call. - -.Before -```rust -fn main() { - ┃dbg!(92); -} -``` - -.After -```rust -fn main() { - 92; -} -``` - - -[discrete] -=== `remove_hash` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L89[raw_string.rs] - -Removes a hash from a raw string literal. - -.Before -```rust -fn main() { - r#"Hello,┃ World!"#; -} -``` - -.After -```rust -fn main() { - r"Hello, World!"; -} -``` - - -[discrete] -=== `remove_mut` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_mut.rs#L5[remove_mut.rs] - -Removes the `mut` keyword. - -.Before -```rust -impl Walrus { - fn feed(&mut┃ self, amount: u32) {} -} -``` - -.After -```rust -impl Walrus { - fn feed(&self, amount: u32) {} -} -``` - - -[discrete] -=== `reorder_fields` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/reorder_fields.rs#L10[reorder_fields.rs] - -Reorder the fields of record literals and record patterns in the same order as in -the definition. - -.Before -```rust -struct Foo {foo: i32, bar: i32}; -const test: Foo = ┃Foo {bar: 0, foo: 1} -``` - -.After -```rust -struct Foo {foo: i32, bar: i32}; -const test: Foo = Foo {foo: 1, bar: 0} -``` - - -[discrete] -=== `replace_if_let_with_match` -**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] - -Replaces `if let` with an else branch with a `match` expression. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - ┃if let Action::Move { distance } = action { - foo(distance) - } else { - bar() - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => foo(distance), - _ => bar(), - } -} -``` - - -[discrete] -=== `replace_let_with_if_let` -**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] - -Replaces `let` with an `if-let`. - -.Before -```rust - -fn main(action: Action) { - ┃let x = compute(); -} - -fn compute() -> Option { None } -``` - -.After -```rust - -fn main(action: Action) { - if let Some(x) = compute() { - } -} - -fn compute() -> Option { None } -``` - - -[discrete] -=== `replace_qualified_name_with_use` -**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] - -Adds a use statement for a given fully-qualified name. - -.Before -```rust -fn process(map: std::collections::┃HashMap) {} -``` - -.After -```rust -use std::collections::HashMap; - -fn process(map: HashMap) {} -``` - - -[discrete] -=== `replace_unwrap_with_match` -**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] - -Replaces `unwrap` a `match` expression. Works for Result and Option. - -.Before -```rust -enum Result { Ok(T), Err(E) } -fn main() { - let x: Result = Result::Ok(92); - let y = x.┃unwrap(); -} -``` - -.After -```rust -enum Result { Ok(T), Err(E) } -fn main() { - let x: Result = Result::Ok(92); - let y = match x { - Ok(a) => a, - $0_ => unreachable!(), - }; -} -``` - - -[discrete] -=== `split_import` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/split_import.rs#L7[split_import.rs] - -Wraps the tail of import into braces. - -.Before -```rust -use std::┃collections::HashMap; -``` - -.After -```rust -use std::{collections::HashMap}; -``` - - -[discrete] -=== `unwrap_block` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/unwrap_block.rs#L9[unwrap_block.rs] - -This assist removes if...else, for, while and loop control statements to just keep the body. - -.Before -```rust -fn foo() { - if true {┃ - println!("foo"); - } -} -``` - -.After -```rust -fn foo() { - println!("foo"); -} -``` diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc deleted file mode 100644 index 4b93b759fa42..000000000000 --- a/docs/user/generated_features.adoc +++ /dev/null @@ -1,298 +0,0 @@ -=== Expand Macro Recursively -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/expand_macro.rs#L15[expand_macro.rs] - -Shows the full macro expansion of the macro at current cursor. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Expand macro recursively** -|=== - - -=== Extend Selection -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/extend_selection.rs#L15[extend_selection.rs] - -Extends the current selection to the encompassing syntactic construct -(expression, statement, item, module, etc). It works with multiple cursors. - -|=== -| Editor | Shortcut - -| VS Code | kbd:[Ctrl+Shift+→] -|=== - - -=== File Structure -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/display/structure.rs#L17[structure.rs] - -Provides a tree of the symbols defined in the file. Can be used to - -* fuzzy search symbol in a file (super useful) -* draw breadcrumbs to describe the context around the cursor -* draw outline of the file - -|=== -| Editor | Shortcut - -| VS Code | kbd:[Ctrl+Shift+O] -|=== - - -=== Go to Definition -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_definition.rs#L18[goto_definition.rs] - -Navigates to the definition of an identifier. - -|=== -| Editor | Shortcut - -| VS Code | kbd:[F12] -|=== - - -=== Go to Implementation -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_implementation.rs#L7[goto_implementation.rs] - -Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. - -|=== -| Editor | Shortcut - -| VS Code | kbd:[Ctrl+F12] -|=== - - -=== Go to Type Definition -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_type_definition.rs#L6[goto_type_definition.rs] - -Navigates to the type of an identifier. - -|=== -| Editor | Action Name - -| VS Code | **Go to Type Definition* -|=== - - -=== Hover -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L58[hover.rs] - -Shows additional information, like type of an expression or documentation for definition when "focusing" code. -Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. - - -=== Inlay Hints -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/inlay_hints.rs#L40[inlay_hints.rs] - -rust-analyzer shows additional information inline with the source code. -Editors usually render this using read-only virtual text snippets interspersed with code. - -rust-analyzer shows hits for - -* types of local variables -* names of function arguments -* types of chained expressions - -**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. -This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: -https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Toggle inlay hints* -|=== - - -=== Join Lines -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/join_lines.rs#L12[join_lines.rs] - -Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Join lines** -|=== - - -=== Magic Completions -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/completion.rs#L38[completion.rs] - -In addition to usual reference completion, rust-analyzer provides some ✨magic✨ -completions as well: - -Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor -is placed at the appropriate position. Even though `if` is easy to type, you -still want to complete it, to get ` { }` for free! `return` is inserted with a -space or `;` depending on the return type of the function. - -When completing a function call, `()` are automatically inserted. If a function -takes arguments, the cursor is positioned inside the parenthesis. - -There are postfix completions, which can be triggered by typing something like -`foo().if`. The word after `.` determines postfix completion. Possible variants are: - -- `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` -- `expr.match` -> `match expr {}` -- `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` -- `expr.ref` -> `&expr` -- `expr.refm` -> `&mut expr` -- `expr.not` -> `!expr` -- `expr.dbg` -> `dbg!(expr)` - -There also snippet completions: - -.Expressions -- `pd` -> `println!("{:?}")` -- `ppd` -> `println!("{:#?}")` - -.Items -- `tfn` -> `#[test] fn f(){}` -- `tmod` -> -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_fn() {} -} -``` - - -=== Matching Brace -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/matching_brace.rs#L3[matching_brace.rs] - -If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, -moves cursor to the matching brace. It uses the actual parser to determine -braces, so it won't confuse generics with comparisons. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Find matching brace** -|=== - - -=== On Typing Assists -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs#L35[typing.rs] - -Some features trigger on typing certain characters: - -- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression -- Enter inside comments automatically inserts `///` -- typing `.` in a chain method call auto-indents - - -=== Parent Module -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/parent_module.rs#L12[parent_module.rs] - -Navigates to the parent module of the current module. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Locate parent module** -|=== - - -=== Run -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs#L45[runnables.rs] - -Shows a popup suggesting to run a test/benchmark/binary **at the current cursor -location**. Super useful for repeatedly running just a single test. Do bind this -to a shortcut! - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Run** -|=== - - -=== Semantic Syntax Highlighting -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs#L33[syntax_highlighting.rs] - -rust-analyzer highlights the code semantically. -For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. -rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. -It's up to the client to map those to specific colors. - -The general rule is that a reference to an entity gets colored the same way as the entity itself. -We also give special modifier for `mut` and `&mut` local variables. - - -=== Show Syntax Tree -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_tree.rs#L9[syntax_tree.rs] - -Shows the parse tree of the current file. It exists mostly for debugging -rust-analyzer itself. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Show Syntax Tree** -|=== - - -=== Status -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/status.rs#L27[status.rs] - -Shows internal statistic about memory usage of rust-analyzer. - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Status** -|=== - - -=== Structural Seach and Replace -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/ssr.rs#L26[ssr.rs] - -Search and replace with named wildcards that will match any expression. -The syntax for a structural search replace command is ` ==>> `. -A `$:expr` placeholder in the search pattern will match any expression and `$` will reference it in the replacement. -Available via the command `rust-analyzer.ssr`. - -```rust -// Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] - -// BEFORE -String::from(foo(y + 5, z)) - -// AFTER -String::from((y + 5).foo(z)) -``` - -|=== -| Editor | Action Name - -| VS Code | **Rust Analyzer: Structural Search Replace** -|=== - - -=== Workspace Symbol -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs#L113[symbol_index.rs] - -Uses fuzzy-search to find types, modules and functions by name across your -project and dependencies. This is **the** most useful feature, which improves code -navigation tremendously. It mostly works on top of the built-in LSP -functionality, however `#` and `*` symbols can be used to narrow down the -search. Specifically, - -- `Foo` searches for `Foo` type in the current workspace -- `foo#` searches for `foo` function in the current workspace -- `Foo*` searches for `Foo` type among dependencies, including `stdlib` -- `foo#*` searches for `foo` function among dependencies - -That is, `#` switches from "types" to all symbols, `*` switches from the current -workspace to dependencies. - -|=== -| Editor | Shortcut - -| VS Code | kbd:[Ctrl+T] -|=== diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 5511c01d5487..f5f4b964a4c1 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -18,8 +18,10 @@ use std::{ use crate::{not_bash::fs2, project_root, Result}; pub use self::{ - gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs, - gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax, + gen_assists_docs::{generate_assists_docs, generate_assists_tests}, + gen_feature_docs::generate_feature_docs, + gen_parser_tests::generate_parser_tests, + gen_syntax::generate_syntax, }; const 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 6c1be53503dc..526941f73ace 100644 --- a/xtask/src/codegen/gen_assists_docs.rs +++ b/xtask/src/codegen/gen_assists_docs.rs @@ -7,16 +7,17 @@ use crate::{ project_root, rust_files, Result, }; +pub fn generate_assists_tests(mode: Mode) -> Result<()> { + let assists = Assist::collect()?; + generate_tests(&assists, mode) +} + pub fn generate_assists_docs(mode: Mode) -> Result<()> { let assists = Assist::collect()?; - generate_tests(&assists, mode)?; - let contents = assists.into_iter().map(|it| it.to_string()).collect::>().join("\n\n"); let contents = contents.trim().to_string() + "\n"; let dst = project_root().join("docs/user/generated_assists.adoc"); - codegen::update(&dst, &contents, mode)?; - - Ok(()) + codegen::update(&dst, &contents, mode) } #[derive(Debug)] diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 874957885e84..739f49f7be81 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -160,6 +160,8 @@ pub fn run_release(dry_run: bool) -> Result<()> { run!("git reset --hard tags/nightly")?; run!("git push")?; } + codegen::generate_assists_docs(Mode::Overwrite)?; + codegen::generate_feature_docs(Mode::Overwrite)?; let website_root = project_root().join("../rust-analyzer.github.io"); let changelog_dir = website_root.join("./thisweek/_posts"); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9d7cdd1145ae..81bb3a33f29e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -74,6 +74,7 @@ FLAGS: args.finish()?; codegen::generate_syntax(Mode::Overwrite)?; codegen::generate_parser_tests(Mode::Overwrite)?; + codegen::generate_assists_tests(Mode::Overwrite)?; codegen::generate_assists_docs(Mode::Overwrite)?; codegen::generate_feature_docs(Mode::Overwrite)?; Ok(()) diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 62626064ee51..d38ac7f17e78 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -25,18 +25,11 @@ fn generated_tests_are_fresh() { #[test] fn generated_assists_are_fresh() { - if let Err(error) = codegen::generate_assists_docs(Mode::Verify) { + if let Err(error) = codegen::generate_assists_tests(Mode::Verify) { panic!("{}. Please update assists by running `cargo xtask codegen`", error); } } -#[test] -fn generated_features_are_fresh() { - if let Err(error) = codegen::generate_feature_docs(Mode::Verify) { - panic!("{}. Please update features by running `cargo xtask codegen`", error); - } -} - #[test] fn check_code_formatting() { if let Err(error) = run_rustfmt(Mode::Verify) {