diff --git a/.gitignore b/.gitignore index dab51647db..aef0fac339 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/Cargo.lock b/Cargo.lock index af27bfc85e..5f88ad0c4d 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", @@ -1640,6 +1640,7 @@ dependencies = [ "relative-path", "rustc-hash", "serde_json", + "stdx", "text-size", ] @@ -1798,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", ] diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e40aeffbcd..4a06f3bcdd 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/attr.rs b/crates/ra_hir_def/src/attr.rs index 8b6c0bedee..2eeba05729 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/data.rs b/crates/ra_hir_def/src/data.rs index e2130d931f..807195d25a 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_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1cec..2630b3d895 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 } @@ -70,6 +77,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.trim_end_matches("\n\n").to_owned()) + } } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index ea495cb11a..660bdfe336 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/completion.rs b/crates/ra_ide/src/completion.rs index d890b69d26..a721e23c69 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/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 9572debd82..ca8a6a6509 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}; @@ -207,7 +207,16 @@ 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 split1(&text, ':') { + Some((left, right)) => format!("{}: {}", left.trim(), right.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..)) { diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d96cb55969..9636cd0d6a 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,13 +164,15 @@ 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 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) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -183,7 +180,8 @@ 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 docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -208,10 +206,11 @@ 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 docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, src.value.short_label(), mod_path) } } @@ -951,4 +950,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"], + ); + } } diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index 68fc589bc7..fcdc98201f 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 41cddd0ff2..e97192b614 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 0000000000..17ffc727cd --- /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 352e350955..42c5f3e551 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 2a0294f719..2dd61d20d6 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 0b53ebe695..19ecd54d6c 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 edfe61f39a..7d946c98da 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 1514531de2..94f466966a 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 7dc229cab7..36a1aa419b 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); +} diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs index be0cd5661b..293baecf6a 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. diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs index b030c8a6a1..09c06fef93 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)] @@ -20,8 +27,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, } @@ -48,9 +64,72 @@ 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::*; + 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 a2e9f65eff..7ad9412795 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::{ @@ -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. @@ -57,25 +63,25 @@ impl PackageRoot { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ProjectRoot { +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +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 +93,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") { @@ -128,16 +134,28 @@ impl ProjectRoot { .collect() } } + + pub fn discover_all(paths: &[impl AsRef]) -> Vec { + let mut res = paths + .iter() + .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) + .flatten() + .collect::>() + .into_iter() + .collect::>(); + res.sort(); + res + } } impl ProjectWorkspace { pub fn load( - root: ProjectRoot, + manifest: ProjectManifest, cargo_features: &CargoConfig, with_sysroot: bool, ) -> Result { - let res = match root { - ProjectRoot::ProjectJson(project_json) => { + 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()) })?; @@ -148,7 +166,7 @@ impl ProjectWorkspace { })?, } } - ProjectRoot::CargoToml(cargo_toml) => { + ProjectManifest::CargoToml(cargo_toml) => { let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) .with_context(|| { format!( @@ -252,6 +270,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/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index bfc05e08bf..a8f2454fd9 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; diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e82fd57de8..8d071ab1ca 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/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 008518a089..44f856f6b4 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/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 8eaf75ff61..c7e86fe0c4 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, 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 +29,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/config.rs b/crates/rust-analyzer/src/config.rs index 3337078ace..23168c3ae9 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)] @@ -142,6 +162,7 @@ impl Default for Config { assist: AssistConfig::default(), call_info_full: true, lens: LensConfig::default(), + linked_projects: Vec::new(), } } } @@ -241,6 +262,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 { @@ -308,3 +345,10 @@ impl Config { } } } + +#[derive(Deserialize)] +#[serde(untagged)] +enum ManifestOrJsonProject { + Manifest(PathBuf), + JsonProject(JsonProject), +} 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 9a7972ff54..f0273315e9 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 257910e094..04e286780c 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -184,7 +184,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( 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()); @@ -226,6 +226,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } if is_unused_or_unnecessary(rd) { + severity = Some(DiagnosticSeverity::Hint); tags.push(DiagnosticTag::Unnecessary); } diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 4bb16a496c..206673829c 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 92% rename from crates/rust-analyzer/src/world.rs rename to crates/rust-analyzer/src/global_state.rs index 367272925b..0bebb5bf61 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -50,15 +50,15 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> }) } -/// `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 roots: Vec, + pub local_roots: Vec, pub workspaces: Arc>, pub analysis_host: AnalysisHost, pub vfs: 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,20 +79,20 @@ pub struct WorldSnapshot { vfs: Arc>, } -impl WorldState { +impl GlobalState { pub fn new( - folder_roots: Vec, workspaces: Vec, lru_capacity: Option, exclude_globs: &[Glob], watch: Watch, config: Config, - ) -> WorldState { + ) -> GlobalState { let mut change = AnalysisChange::new(); 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()); @@ -176,9 +180,9 @@ impl WorldState { let mut analysis_host = AnalysisHost::new(lru_capacity); analysis_host.apply_change(change); - WorldState { + GlobalState { 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 { @@ -251,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(), @@ -275,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 57d0e92188..609cb69d3b 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 ad9dd4c596..e60337b8e8 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, @@ -36,14 +34,15 @@ 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, lsp_ext, + from_proto, + global_state::{GlobalState, GlobalStateSnapshot}, + lsp_ext, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, }, - world::{WorldSnapshot, WorldState}, Result, }; @@ -69,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 @@ -92,43 +91,37 @@ 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 - .iter() - .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) - .flatten() - .collect(); - - 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() - .filter_map(|root| { - ra_project_model::ProjectWorkspace::load( - root, - &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() + config + .linked_projects + .iter() + .filter_map(|project| match project { + 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::>() }; @@ -163,8 +156,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) connection.sender.send(request.into()).unwrap(); } - WorldState::new( - ws_roots, + GlobalState::new( workspaces, config.lru_capacity, &globs, @@ -173,7 +165,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::(); @@ -191,12 +183,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()), } @@ -211,16 +203,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"); @@ -229,7 +221,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(()) @@ -320,7 +312,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<()> { @@ -336,22 +328,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, @@ -360,7 +352,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); @@ -379,9 +371,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) => { @@ -394,7 +386,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); } @@ -416,7 +408,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 @@ -425,7 +417,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(); } } @@ -437,13 +429,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| ()) }); } @@ -467,7 +459,7 @@ fn on_task( task: Task, msg_sender: &Sender, pending_requests: &mut PendingRequests, - state: &mut WorldState, + state: &mut GlobalState, ) { match task { Task::Respond(response) => { @@ -485,7 +477,7 @@ fn on_task( } fn on_request( - world: &mut WorldState, + global_state: &mut GlobalState, pending_requests: &mut PendingRequests, pool: &ThreadPool, task_sender: &Sender, @@ -496,7 +488,7 @@ fn on_request( let mut pool_dispatcher = PoolDispatcher { req: Some(req), pool, - world, + global_state, task_sender, msg_sender, pending_requests, @@ -553,7 +545,7 @@ fn on_request( fn on_notification( msg_sender: &Sender, - state: &mut WorldState, + state: &mut GlobalState, loop_state: &mut LoopState, not: Notification, ) -> Result<()> { @@ -727,7 +719,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 { @@ -746,7 +738,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!( @@ -766,7 +758,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 { @@ -805,7 +797,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 { @@ -880,7 +872,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, @@ -891,7 +883,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, @@ -904,18 +896,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, @@ -929,7 +924,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); @@ -1013,7 +1008,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 fab82ff7ea..a3361d6dc5 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,15 +702,14 @@ pub fn handle_formatting( } fn handle_fixes( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, params: &lsp_types::CodeActionParams, res: &mut Vec, ) -> Result<()> { - 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 diagnostics = world.analysis().diagnostics(file_id)?; + let diagnostics = snap.analysis().diagnostics(file_id)?; let fixes_from_diagnostics = diagnostics .into_iter() @@ -714,18 +718,19 @@ fn handle_fixes( .map(|(_range, fix)| fix); 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, id: None, group: None, - kind: None, + kind: Some(lsp_types::code_action_kind::QUICKFIX.into()), 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; @@ -736,37 +741,34 @@ fn handle_fixes( } 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 mut res: Vec = Vec::new(); - handle_fixes(&world, ¶ms, &mut res)?; + handle_fixes(&snap, ¶ms, &mut res)?; - if world.config.client_caps.resolve_code_action { - for (index, assist) in world - .analysis() - .unresolved_assists(&world.config.assist, frange)? - .into_iter() - .enumerate() + if snap.config.client_caps.resolve_code_action { + for (index, assist) in + snap.analysis().unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() { - res.push(to_proto::unresolved_code_action(&world, assist, index)?); + res.push(to_proto::unresolved_code_action(&snap, assist, index)?); } } else { - for assist in world.analysis().resolved_assists(&world.config.assist, frange)?.into_iter() { - res.push(to_proto::resolved_code_action(&world, assist)?); + for assist in snap.analysis().resolved_assists(&snap.config.assist, frange)?.into_iter() { + res.push(to_proto::resolved_code_action(&snap, assist)?); } } @@ -774,43 +776,43 @@ pub fn handle_code_action( } pub fn handle_resolve_code_action( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::ResolveCodeActionParams, ) -> Result> { let _p = profile("handle_resolve_code_action"); - let file_id = from_proto::file_id(&world, ¶ms.code_action_params.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.code_action_params.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.code_action_params.range); let frange = FileRange { file_id, range }; - let assists = world.analysis().resolved_assists(&world.config.assist, frange)?; + let assists = snap.analysis().resolved_assists(&snap.config.assist, frange)?; let id_components = params.id.split(":").collect::>(); let index = id_components.last().unwrap().parse::().unwrap(); let id_string = id_components.first().unwrap(); let assist = &assists[index]; assert!(assist.assist.id.0 == *id_string); - Ok(to_proto::resolved_code_action(&world, assist.clone())?.edit) + Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit) } 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) @@ -836,8 +838,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 { @@ -850,7 +852,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 { @@ -865,11 +867,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 { @@ -904,14 +905,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 @@ -950,14 +954,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)))? { @@ -977,19 +981,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() @@ -1007,28 +1011,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, }; @@ -1037,24 +1041,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, }; @@ -1063,8 +1067,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 @@ -1079,17 +1083,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, }; @@ -1098,8 +1102,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 @@ -1114,31 +1118,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 fb33bdd5f6..1da4d80ece 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 }) } @@ -620,14 +624,14 @@ fn main() { } pub(crate) fn unresolved_code_action( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, assist: Assist, index: usize, ) -> Result { let res = lsp_ext::CodeAction { title: assist.label, id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())), - group: assist.group.filter(|_| world.config.client_caps.code_action_group).map(|gr| gr.0), + group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), kind: Some(String::new()), edit: None, command: None, @@ -636,25 +640,25 @@ pub(crate) fn unresolved_code_action( } pub(crate) fn resolved_code_action( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, assist: ResolvedAssist, ) -> Result { let change = assist.source_change; - unresolved_code_action(world, assist.assist, 0).and_then(|it| { + unresolved_code_action(snap, assist.assist, 0).and_then(|it| { Ok(lsp_ext::CodeAction { id: None, - edit: Some(snippet_workspace_edit(world, change)?), + edit: Some(snippet_workspace_edit(snap, change)?), ..it }) }) } 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)?; @@ -667,14 +671,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, }, diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index e18f973b82..ad34763105 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() { @@ -347,6 +298,7 @@ fn main() {} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); @@ -379,8 +331,7 @@ 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"], } ] }); @@ -418,6 +369,7 @@ fn main() {{}} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 66a6f4d541..30d03b622b 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 = diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 71a57fba23..c0356344ca 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 4d185b01c7..8840bf36ae 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 1bd97215cb..2141bfc202 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: diff --git a/docs/dev/README.md b/docs/dev/README.md index 65cc9fc12c..1de5a2aab1 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 diff --git a/docs/user/generated_assists.adoc b/docs/user/generated_assists.adoc deleted file mode 100644 index 4d2fb31d48..0000000000 --- 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 12812fa0be..0000000000 --- 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#L63[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/docs/user/manual.adoc b/docs/user/manual.adoc index 202783fd95..ea714f49ad 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 d8f4287fd8..30ab7ba4a9 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 } } }, diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 5511c01d54..f5f4b964a4 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 6c1be53503..526941f73a 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 874957885e..739f49f7be 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 9d7cdd1145..81bb3a33f2 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 4ac5d929fc..d38ac7f17e 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) { @@ -180,13 +173,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)) }