Merge branch 'master' into compute-lazy-assits

# Conflicts:
#	crates/rust-analyzer/src/main_loop/handlers.rs
#	crates/rust-analyzer/src/to_proto.rs
This commit is contained in:
Mikhail Rakhmanov
2020-06-03 19:26:01 +02:00
50 changed files with 1191 additions and 1829 deletions
+2
View File
@@ -8,3 +8,5 @@ crates/*/target
*.iml
.vscode/settings.json
*.html
generated_assists.adoc
generated_features.adoc
Generated
+15 -14
View File
@@ -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",
]
+8
View File
@@ -637,6 +637,10 @@ pub fn params(self, db: &dyn HirDatabase) -> Vec<TypeRef> {
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 @@ pub fn is_fn(&self) -> bool {
)
}
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);
+7 -1
View File
@@ -87,12 +87,18 @@ pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) ->
}
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 }
}
+5 -1
View File
@@ -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 @@ pub(crate) fn fn_data_query(db: &impl DefDatabase, func: FunctionId) -> Arc<Func
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)
}
}
+48 -2
View File
@@ -29,6 +29,13 @@ fn new(s: &str) -> Documentation {
Documentation(s.into())
}
pub fn from_ast<N>(node: &N) -> Option<Documentation>
where
N: ast::DocCommentsOwner + ast::AttrsOwner,
{
docs_from_ast(node)
}
pub fn as_str(&self) -> &str {
&*self.0
}
@@ -70,6 +77,45 @@ pub(crate) fn documentation_query(
}
}
pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option<Documentation> {
node.doc_comment_text().map(|it| Documentation::new(&it))
pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
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<String>,
doc_attr_text: Option<String>,
) -> Option<String> {
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<String> {
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())
}
}
+1
View File
@@ -153,6 +153,7 @@ macro_rules! known_names {
str,
// Special names
macro_rules,
doc,
// Components of known path (value or mod name)
std,
core,
+78
View File
@@ -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" },
);
}
}
@@ -10,7 +10,7 @@
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 @@ fn param_list(node: &ast::FnDef) -> (bool, Vec<String>, Vec<String>) {
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..)) {
+114 -13
View File
@@ -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 @@
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<Strin
return match def {
Definition::Macro(it) => {
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<Strin
Definition::ModuleDef(it) => 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<Strin
fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
where
D: HasSource<Ast = A>,
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 my() {}
&["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"],
);
}
}
@@ -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; }
@@ -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; }
@@ -0,0 +1,48 @@
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.lifetime { color: #DFAF8F; font-style: italic; }
.comment { color: #7F9F7F; }
.struct, .enum { color: #7CB8BB; }
.enum_variant { color: #BDE0F3; }
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.operator.unsafe { color: #E28C14; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.builtin_type { color: #8CD0D3; }
.type_param { color: #DFAF8F; }
.attribute { color: #94BFF3; }
.numeric_literal { color: #BFEBBF; }
.bool_literal { color: #BFE6EB; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
</style>
<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
}
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
<span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
<span class="keyword unsafe">unsafe</span> {
<span class="function unsafe">unsafe_fn</span>();
<span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
<span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>;
<span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
}
}</code></pre>
@@ -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; }
@@ -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; }
+24 -1
View File
@@ -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,
@@ -69,6 +69,7 @@ fn html_escape(text: &str) -> String {
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.operator.unsafe { color: #E28C14; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
@@ -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 @@ fn as_str(self) -> &'static str {
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 @@ fn as_str(self) -> &'static str {
HighlightTag::Union => "union",
HighlightTag::Local => "variable",
HighlightTag::UnresolvedReference => "unresolved_reference",
HighlightTag::FormatSpecifier => "format_specifier",
HighlightTag::Operator => "operator",
}
}
}
@@ -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);
}
+4 -3
View File
@@ -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.
+84 -5
View File
@@ -5,6 +5,13 @@
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<Root>,
pub(crate) crates: Vec<Crate>,
}
/// 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<Dep>,
// This is the preferred method of providing cfg options.
#[serde(default)]
pub(crate) cfg: FxHashSet<String>,
// These two are here for transition only.
#[serde(default)]
pub(crate) atom_cfgs: FxHashSet<String>,
#[serde(default)]
pub(crate) key_value_cfgs: FxHashMap<String, String>,
pub(crate) out_dir: Option<PathBuf>,
pub(crate) proc_macro_dylib_path: Option<PathBuf>,
}
@@ -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<Root>,
pub(crate) crates: Vec<Crate>,
#[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()));
}
}
+44 -16
View File
@@ -14,7 +14,7 @@
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<JsonProject> 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 @@ pub fn is_member(&self) -> bool {
}
}
#[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<ProjectRoot> {
impl ProjectManifest {
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectManifest> {
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<ProjectRoot> {
let mut candidates = ProjectRoot::discover(path)?;
pub fn discover_single(path: &Path) -> Result<ProjectManifest> {
let mut candidates = ProjectManifest::discover(path)?;
let res = match candidates.pop() {
None => bail!("no projects"),
Some(it) => it,
@@ -87,12 +93,12 @@ pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
Ok(res)
}
pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> {
pub fn discover(path: &Path) -> io::Result<Vec<ProjectManifest>> {
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<Vec<PathBuf>> {
match find_in_parent_dirs(path, "Cargo.toml") {
@@ -128,16 +134,28 @@ fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
.collect()
}
}
pub fn discover_all(paths: &[impl AsRef<Path>]) -> Vec<ProjectManifest> {
let mut res = paths
.iter()
.filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
.flatten()
.collect::<FxHashSet<_>>()
.into_iter()
.collect::<Vec<_>>();
res.sort();
res
}
}
impl ProjectWorkspace {
pub fn load(
root: ProjectRoot,
manifest: ProjectManifest,
cargo_features: &CargoConfig,
with_sysroot: bool,
) -> Result<ProjectWorkspace> {
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 @@ pub fn load(
})?,
}
}
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 @@ pub fn to_crate_graph(
};
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());
}
+11 -2
View File
@@ -83,13 +83,22 @@ fn doc_comments(&self) -> CommentIter {
CommentIter { iter: self.syntax().children_with_tokens() }
}
fn doc_comment_text(&self) -> Option<String> {
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<String> {
pub fn doc_comment_text(self) -> Option<String> {
let mut has_comments = false;
let docs = self
.doc_comments()
.filter(|comment| comment.kind().doc.is_some())
.map(|comment| {
has_comments = true;
+28 -13
View File
@@ -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::<Vec<_>>()
})
.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::<Vec<_>>()
})
.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()?;
@@ -4,7 +4,7 @@
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 @@ pub(crate) fn runnable_args(
}
pub(crate) fn for_file(
world: &WorldSnapshot,
world: &GlobalStateSnapshot,
file_id: FileId,
) -> Result<Option<CargoTargetSpec>> {
let &crate_id = match world.analysis().crate_for(file_id)?.first() {
+3 -2
View File
@@ -8,7 +8,8 @@
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<SourceRootId, PackageRoot>)> {
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() },
+46 -2
View File
@@ -12,14 +12,13 @@
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<usize>,
pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
@@ -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<LinkedProject>,
}
#[derive(Debug, Clone)]
pub enum LinkedProject {
ProjectManifest(ProjectManifest),
JsonProject(JsonProject),
}
impl From<ProjectManifest> for LinkedProject {
fn from(v: ProjectManifest) -> Self {
LinkedProject::ProjectManifest(v)
}
}
impl From<JsonProject> for LinkedProject {
fn from(v: JsonProject) -> Self {
LinkedProject::JsonProject(v)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -142,6 +162,7 @@ fn default() -> Self {
assist: AssistConfig::default(),
call_info_full: true,
lens: LensConfig::default(),
linked_projects: Vec::new(),
}
}
}
@@ -241,6 +262,22 @@ pub fn update(&mut self, value: &serde_json::Value) {
self.lens = LensConfig::NO_LENS;
}
if let Some(linked_projects) = get::<Vec<ManifestOrJsonProject>>(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<T> {
@@ -308,3 +345,10 @@ pub fn update_caps(&mut self, caps: &ClientCapabilities) {
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum ManifestOrJsonProject {
Manifest(PathBuf),
JsonProject(JsonProject),
}
@@ -29,7 +29,7 @@ expression: diag
},
},
severity: Some(
Warning,
Hint,
),
code: Some(
String(
@@ -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);
}
+4 -4
View File
@@ -3,7 +3,7 @@
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<FileId> {
pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
world.uri_to_file_id(url)
}
pub(crate) fn file_position(
world: &WorldSnapshot,
world: &GlobalStateSnapshot,
tdpp: lsp_types::TextDocumentPositionParams,
) -> Result<FilePosition> {
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<FileRange> {
@@ -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<PathBuf>,
pub local_roots: Vec<PathBuf>,
pub workspaces: Arc<Vec<ProjectWorkspace>>,
pub analysis_host: AnalysisHost,
pub vfs: Arc<RwLock<Vfs>>,
@@ -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<Vec<ProjectWorkspace>>,
pub analysis: Analysis,
@@ -79,20 +79,20 @@ pub struct WorldSnapshot {
vfs: Arc<RwLock<Vfs>>,
}
impl WorldState {
impl GlobalState {
pub fn new(
folder_roots: Vec<PathBuf>,
workspaces: Vec<ProjectWorkspace>,
lru_capacity: Option<usize>,
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 @@ pub fn new(
.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 @@ pub fn new(
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 @@ pub fn new(
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 @@ pub fn process_changes(
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 @@ pub fn add_lib(&mut self, data: LibraryData) {
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 @@ pub fn complete_request(&mut self, request: CompletedRequest) {
}
}
impl WorldSnapshot {
impl GlobalStateSnapshot {
pub fn analysis(&self) -> &Analysis {
&self.analysis
}
+1 -1
View File
@@ -26,7 +26,7 @@ macro_rules! eprintln {
mod markdown;
pub mod lsp_ext;
pub mod config;
mod world;
mod global_state;
mod diagnostics;
mod semantic_tokens;
+71 -76
View File
@@ -12,13 +12,11 @@
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 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 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
impl Error for LspError {}
pub fn main_loop(ws_roots: Vec<PathBuf>, 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<PathBuf>, 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::<Vec<_>>()
};
@@ -163,8 +156,7 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, 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<PathBuf>, 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::<Task>();
@@ -191,12 +183,12 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, 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<PathBuf>, 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<PathBuf>, 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<Task>,
libdata_sender: &Sender<LibraryData>,
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<Message>,
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<Task>,
@@ -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<Message>,
state: &mut WorldState,
state: &mut GlobalState,
loop_state: &mut LoopState,
not: Notification,
) -> Result<()> {
@@ -727,7 +719,7 @@ fn covers(&self, line: u64) -> bool {
fn on_check_task(
task: CheckTask,
world_state: &mut WorldState,
global_state: &mut GlobalState,
task_sender: &Sender<Task>,
) -> 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<Message>, state: &mut WorldState) {
fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut GlobalState) {
let subscriptions = state.diagnostics.handle_task(task);
for file_id in subscriptions {
@@ -880,7 +872,7 @@ fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: Wor
struct PoolDispatcher<'a> {
req: Option<Request>,
pool: &'a ThreadPool,
world: &'a mut WorldState,
global_state: &'a mut GlobalState,
pending_requests: &'a mut PendingRequests,
msg_sender: &'a Sender<Message>,
task_sender: &'a Sender<Task>,
@@ -891,7 +883,7 @@ impl<'a> PoolDispatcher<'a> {
/// Dispatches the request onto the current thread
fn on_sync<R>(
&mut self,
f: fn(&mut WorldState, R::Params) -> Result<R::Result>,
f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
) -> Result<&mut Self>
where
R: lsp_types::request::Request + 'static,
@@ -904,18 +896,21 @@ fn on_sync<R>(
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::<R>(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<R>(&mut self, f: fn(WorldSnapshot, R::Params) -> Result<R::Result>) -> Result<&mut Self>
fn on<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
) -> Result<&mut Self>
where
R: lsp_types::request::Request + 'static,
R::Params: DeserializeOwned + Send + 'static,
@@ -929,7 +924,7 @@ fn on<R>(&mut self, f: fn(WorldSnapshot, R::Params) -> Result<R::Result>) -> Res
};
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 @@ fn result_to_task<R>(id: RequestId, result: Result<R::Result>) -> Task
fn update_file_notifications_on_threadpool(
pool: &ThreadPool,
world: WorldSnapshot,
world: GlobalStateSnapshot,
task_sender: Sender<Task>,
subscriptions: Vec<FileId>,
) {
+197 -193
View File
@@ -32,17 +32,16 @@
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<String> {
pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> {
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<String> {
}
pub fn handle_syntax_tree(
world: WorldSnapshot,
snap: GlobalStateSnapshot,
params: lsp_ext::SyntaxTreeParams,
) -> Result<String> {
let _p = profile("handle_syntax_tree");
let id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(id)?;
let id = from_proto::file_id(&snap, &params.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<Option<lsp_ext::ExpandedMacro>> {
let _p = profile("handle_expand_macro");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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<Option<Vec<lsp_types::SelectionRange>>> {
let _p = profile("handle_selection_range");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.analysis().file_line_index(file_id)?;
let res: Result<Vec<lsp_types::SelectionRange>> = 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<Vec<Position>> {
let _p = profile("handle_matching_brace");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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<Vec<lsp_types::TextEdit>> {
let _p = profile("handle_join_lines");
let file_id = from_proto::file_id(&world, &params.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, &params.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<Option<Vec<lsp_ext::SnippetTextEdit>>> {
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<Option<Vec<lsp_types::TextEdit>>> {
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<Option<lsp_types::DocumentSymbolResponse>> {
let _p = profile("handle_document_symbol");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.analysis().file_line_index(file_id)?;
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = 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::<SymbolInformation>::new();
for symbol in document_symbols {
flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
@@ -288,7 +287,7 @@ fn flatten_document_symbol(
}
pub fn handle_workspace_symbol(
world: WorldSnapshot,
snap: GlobalStateSnapshot,
params: lsp_types::WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
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<Vec<SymbolInformation>> {
fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
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 @@ fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformati
}
pub fn handle_goto_definition(
world: WorldSnapshot,
snap: GlobalStateSnapshot,
params: lsp_types::GotoDefinitionParams,
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
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<Option<lsp_types::request::GotoImplementationResponse>> {
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<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
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<Option<lsp_types::GotoDefinitionResponse>> {
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<Vec<lsp_ext::Runnable>> {
let _p = profile("handle_runnables");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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<Option<lsp_types::CompletionResponse>> {
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<CompletionItem> = 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<Option<Vec<FoldingRange>>> {
let _p = profile("handle_folding_range");
let file_id = from_proto::file_id(&world, &params.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, &params.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<Option<lsp_types::SignatureHelp>> {
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<Option<Hover>> {
pub fn handle_hover(
snap: GlobalStateSnapshot,
params: lsp_types::HoverParams,
) -> Result<Option<Hover>> {
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<Option<PrepareRenameResponse>> {
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<Option<WorkspaceEdit>> {
pub fn handle_rename(
snap: GlobalStateSnapshot,
params: RenameParams,
) -> Result<Option<WorkspaceEdit>> {
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<Optio
.into());
}
let optional_change = world.analysis().rename(position, &*params.new_name)?;
let optional_change = snap.analysis().rename(position, &*params.new_name)?;
let source_change = match optional_change {
None => 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<Option<Vec<Location>>> {
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<Option<Vec<lsp_types::TextEdit>>> {
let _p = profile("handle_formatting");
let file_id = from_proto::file_id(&world, &params.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, &params.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<lsp_ext::CodeAction>,
) -> Result<()> {
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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<Option<Vec<lsp_ext::CodeAction>>> {
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, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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<lsp_ext::CodeAction> = Vec::new();
handle_fixes(&world, &params, &mut res)?;
handle_fixes(&snap, &params, &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<Option<lsp_ext::SnippetWorkspaceEdit>> {
let _p = profile("handle_resolve_code_action");
let file_id = from_proto::file_id(&world, &params.code_action_params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let file_id = from_proto::file_id(&snap, &params.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::<Vec<&str>>();
let index = id_components.last().unwrap().parse::<usize>().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<Option<Vec<CodeLens>>> {
let _p = profile("handle_code_lens");
let mut lenses: Vec<CodeLens> = 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, &params.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, &params.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<CodeLens> {
pub fn handle_code_lens_resolve(
snap: GlobalStateSnapshot,
code_lens: CodeLens,
) -> Result<CodeLens> {
let _p = profile("handle_code_lens_resolve");
let data = code_lens.data.unwrap();
let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
match resolve {
Some(CodeLensResolveData::Impls(lens_params)) => {
let locations: Vec<Location> =
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<Option<Vec<DocumentHighlight>>> {
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<lsp_types::WorkspaceEdit> {
let _p = profile("handle_ssr");
let source_change =
world.analysis().structural_search_replace(&params.query, params.parse_only)??;
to_proto::workspace_edit(&world, source_change)
snap.analysis().structural_search_replace(&params.query, params.parse_only)??;
to_proto::workspace_edit(&snap, source_change)
}
pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
let _p = profile("publish_diagnostics");
let line_index = world.analysis().file_line_index(file_id)?;
let diagnostics: Vec<Diagnostic> = world
let line_index = snap.analysis().file_line_index(file_id)?;
let diagnostics: Vec<Diagnostic> = snap
.analysis()
.diagnostics(file_id)?
.into_iter()
@@ -1007,28 +1011,28 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia
}
pub fn handle_inlay_hints(
world: WorldSnapshot,
snap: GlobalStateSnapshot,
params: InlayHintsParams,
) -> Result<Vec<InlayHint>> {
let _p = profile("handle_inlay_hints");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let analysis = world.analysis();
let file_id = from_proto::file_id(&snap, &params.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<Option<Vec<CallHierarchyItem>>> {
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::<Result<Vec<_>>>()?;
Ok(Some(res))
}
pub fn handle_call_hierarchy_incoming(
world: WorldSnapshot,
snap: GlobalStateSnapshot,
params: CallHierarchyIncomingCallsParams,
) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
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<Option<Vec<CallHierarchyOutgoingCall>>> {
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<Option<SemanticTokensResult>> {
let _p = profile("handle_semantic_tokens");
let file_id = from_proto::file_id(&world, &params.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, &params.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<Option<SemanticTokensRangeResult>> {
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()))
}
+46 -42
View File
@@ -10,7 +10,8 @@
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<lsp_types::Url> {
world.file_id_to_uri(file_id)
pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> {
snap.file_id_to_uri(file_id)
}
pub(crate) fn versioned_text_document_identifier(
world: &WorldSnapshot,
snap: &GlobalStateSnapshot,
file_id: FileId,
version: Option<i64>,
) -> Result<lsp_types::VersionedTextDocumentIdentifier> {
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<lsp_types::Location> {
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<lsp_types::Location> {
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<FileRange>,
target: NavigationTarget,
) -> Result<lsp_types::LocationLink> {
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<FileRange>,
targets: Vec<NavigationTarget>,
) -> Result<lsp_types::GotoDefinitionResponse> {
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::<Result<Vec<_>>>()?;
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<lsp_ext::SnippetTextDocumentEdit> {
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<lsp_types::ResourceOp> {
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<lsp_ext::SnippetWorkspaceEdit> {
let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = 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<lsp_types::WorkspaceEdit> {
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<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
@@ -565,13 +569,13 @@ fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::Wor
}
pub fn call_hierarchy_item(
world: &WorldSnapshot,
snap: &GlobalStateSnapshot,
target: NavigationTarget,
) -> Result<lsp_types::CallHierarchyItem> {
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() <fold>{
}
pub(crate) fn unresolved_code_action(
world: &WorldSnapshot,
snap: &GlobalStateSnapshot,
assist: Assist,
index: usize,
) -> Result<lsp_ext::CodeAction> {
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<lsp_ext::CodeAction> {
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<lsp_ext::Runnable> {
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,
},
+3 -51
View File
@@ -58,55 +58,6 @@ fn completes_items_from_standard_library() {
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::<Runnables>(
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"
}]),
);
@@ -19,8 +19,9 @@
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 @@ pub fn tmp_dir(mut self, tmp_dir: TempDir) -> 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 @@ pub fn server(self) -> Server {
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::<Vec<_>>();
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::<Vec<_>>();
let mut config = Config {
client_caps: ClientCapsConfig {
@@ -84,6 +94,7 @@ pub fn server(self) -> Server {
..Default::default()
},
with_sysroot: self.with_sysroot,
linked_projects,
..Config::default()
};
@@ -91,7 +102,7 @@ pub fn server(self) -> Server {
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<PathBuf>,
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 =
+5
View File
@@ -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()..]))
}
+2 -1
View File
@@ -14,4 +14,5 @@ serde_json = "1.0.48"
relative-path = "1.0.0"
rustc-hash = "1.1.0"
ra_cfg = { path = "../ra_cfg" }
ra_cfg = { path = "../ra_cfg" }
stdx = { path = "../stdx" }
+1 -5
View File
@@ -15,6 +15,7 @@
};
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:
+104 -1
View File
@@ -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<Bar>
}
struct Bar;
```
rather than
```
// Not as good
struct Bar;
struct Foo {
bars: Vec<Bar>
}
```
## 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
-1015
View File
@@ -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<u32> 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<T: Clone> {
data: T,┃
}
```
.After
```rust
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
$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<T> {
Type X;
fn foo(&self) -> T;
fn bar(&self) {}
}
impl Trait<u32> for () {┃
}
```
.After
```rust
trait Trait<T> {
Type X;
fn foo(&self) -> T;
fn bar(&self) {}
}
impl Trait<u32> 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<T: Clone> {
data: T,┃
}
```
.After
```rust
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
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>() -> T { todo!() }
fn main() {
let x = make┃();
}
```
.After
```rust
fn make<T>() -> 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<i32, ${0:_}> { 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<T: Clone +┃ Copy>() { }
```
.After
```rust
fn foo<T: Copy + Clone>() { }
```
[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<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U {
f(x)
}
```
.After
```rust
fn apply<T, U, F>(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<i32> { None }
```
.After
```rust
fn main(action: Action) {
if let Some(x) = compute() {
}
}
fn compute() -> Option<i32> { 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<String, String>) {}
```
.After
```rust
use std::collections::HashMap;
fn process(map: HashMap<String, String>) {}
```
[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<T, E> { Ok(T), Err(E) }
fn main() {
let x: Result<i32, i32> = Result::Ok(92);
let y = x.┃unwrap();
}
```
.After
```rust
enum Result<T, E> { Ok(T), Err(E) }
fn main() {
let x: Result<i32, i32> = 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");
}
```
-298
View File
@@ -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 `<search_pattern> ==>> <replace_pattern>`.
A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` 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]
|===
+51
View File
@@ -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[]
+19
View File
@@ -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
}
}
},
+4 -2
View File
@@ -18,8 +18,10 @@
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";
+6 -5
View File
@@ -7,16 +7,17 @@
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::<Vec<_>>().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)]
+2
View File
@@ -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");
+1
View File
@@ -74,6 +74,7 @@ fn main() -> Result<()> {
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(())
+8 -17
View File
@@ -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 @@ fn finish(self) {
}
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))
}