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
commit 6a0083a519
50 changed files with 1191 additions and 1829 deletions

2
.gitignore vendored
View File

@ -8,3 +8,5 @@ crates/*/target
*.iml
.vscode/settings.json
*.html
generated_assists.adoc
generated_features.adoc

29
Cargo.lock generated
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",
]

View File

@ -637,6 +637,10 @@ impl Function {
db.function_data(self.id).params.clone()
}
pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool {
db.function_data(self.id).is_unsafe
}
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
let _p = profile("Function::diagnostics");
let infer = db.infer(self.id.into());
@ -1190,6 +1194,10 @@ impl Type {
)
}
pub fn is_raw_ptr(&self) -> bool {
matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }))
}
pub fn contains_unknown(&self) -> bool {
return go(&self.ty.value);

View File

@ -87,12 +87,18 @@ impl Attrs {
}
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
|docs_text| Attr {
input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
path: ModPath::from(hir_expand::name!(doc)),
},
);
let mut attrs = owner.attrs().peekable();
let entries = if attrs.peek().is_none() {
// Avoid heap allocation
None
} else {
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect())
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
};
Attrs { entries }
}

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 @@ impl FunctionData {
ret_type
};
let is_unsafe = src.value.unsafe_token().is_some();
let vis_default = RawVisibility::default_for_container(loc.container);
let visibility =
RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility()));
let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs };
let sig =
FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs };
Arc::new(sig)
}
}

View File

@ -29,6 +29,13 @@ impl 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 @@ impl Documentation {
}
}
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())
}
}

View File

@ -153,6 +153,7 @@ pub mod known {
str,
// Special names
macro_rules,
doc,
// Components of known path (value or mod name)
std,
core,

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" },
);
}
}

View File

@ -10,7 +10,7 @@ use std::{
use hir::{Docs, Documentation, HasSource, HirDisplay};
use ra_ide_db::RootDatabase;
use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
use stdx::SepBy;
use stdx::{split1, SepBy};
use crate::display::{generic_parameters, where_predicates};
@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
res.push(raw_param);
}
res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
// macro-generated functions are missing whitespace
fn fmt_param(param: ast::Param) -> String {
let text = param.syntax().text().to_string();
match split1(&text, ':') {
Some((left, right)) => format!("{}: {}", left.trim(), right.trim()),
_ => text,
}
}
res.extend(param_list.params().map(fmt_param));
res_types.extend(param_list.params().map(|param| {
let param_text = param.syntax().text().to_string();
match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {

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 @@ use ra_ide_db::{
defs::{classify_name, classify_name_ref, Definition},
RootDatabase,
};
use ra_syntax::{
ast::{self, DocCommentsOwner},
match_ast, AstNode,
SyntaxKind::*,
SyntaxToken, TokenAtOffset,
};
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
use crate::{
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
@ -169,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<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 func(foo: i32) { if true { <|>foo; }; }
&["mod my"],
);
}
#[test]
fn test_hover_struct_doc_comment() {
check_hover_result(
r#"
//- /lib.rs
/// bar docs
struct Bar;
fn foo() {
let bar = Ba<|>r;
}
"#,
&["struct Bar\n```\n___\n\nbar docs"],
);
}
#[test]
fn test_hover_struct_doc_attr() {
check_hover_result(
r#"
//- /lib.rs
#[doc = "bar docs"]
struct Bar;
fn foo() {
let bar = Ba<|>r;
}
"#,
&["struct Bar\n```\n___\n\nbar docs"],
);
}
#[test]
fn test_hover_struct_doc_attr_multiple_and_mixed() {
check_hover_result(
r#"
//- /lib.rs
/// bar docs 0
#[doc = "bar docs 1"]
#[doc = "bar docs 2"]
struct Bar;
fn foo() {
let bar = Ba<|>r;
}
"#,
&["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"],
);
}
#[test]
fn test_hover_macro_generated_struct_fn_doc_comment() {
check_hover_result(
r#"
//- /lib.rs
macro_rules! bar {
() => {
struct Bar;
impl Bar {
/// Do the foo
fn foo(&self) {}
}
}
}
bar!();
fn foo() {
let bar = Bar;
bar.fo<|>o();
}
"#,
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"],
);
}
#[test]
fn test_hover_macro_generated_struct_fn_doc_attr() {
check_hover_result(
r#"
//- /lib.rs
macro_rules! bar {
() => {
struct Bar;
impl Bar {
#[doc = "Do the foo"]
fn foo(&self) {}
}
}
}
bar!();
fn foo() {
let bar = Bar;
bar.fo<|>o();
}
"#,
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
);
}
}

View File

@ -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; }

View File

@ -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; }

View File

@ -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>

View File

@ -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; }

View File

@ -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; }

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,

View File

@ -69,6 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.operator.unsafe { color: #E28C14; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }

View File

@ -24,12 +24,14 @@ pub enum HighlightTag {
Enum,
EnumVariant,
Field,
FormatSpecifier,
Function,
Keyword,
Lifetime,
Macro,
Module,
NumericLiteral,
Operator,
SelfKeyword,
SelfType,
Static,
@ -41,8 +43,6 @@ pub enum HighlightTag {
Union,
Local,
UnresolvedReference,
FormatSpecifier,
Operator,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -72,12 +72,14 @@ impl HighlightTag {
HighlightTag::Enum => "enum",
HighlightTag::EnumVariant => "enum_variant",
HighlightTag::Field => "field",
HighlightTag::FormatSpecifier => "format_specifier",
HighlightTag::Function => "function",
HighlightTag::Keyword => "keyword",
HighlightTag::Lifetime => "lifetime",
HighlightTag::Macro => "macro",
HighlightTag::Module => "module",
HighlightTag::NumericLiteral => "numeric_literal",
HighlightTag::Operator => "operator",
HighlightTag::SelfKeyword => "self_keyword",
HighlightTag::SelfType => "self_type",
HighlightTag::Static => "static",
@ -89,8 +91,6 @@ impl HighlightTag {
HighlightTag::Union => "union",
HighlightTag::Local => "variable",
HighlightTag::UnresolvedReference => "unresolved_reference",
HighlightTag::FormatSpecifier => "format_specifier",
HighlightTag::Operator => "operator",
}
}
}

View File

@ -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);
}

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.

View File

@ -5,6 +5,13 @@ use std::path::PathBuf;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize;
/// Roots and crates that compose this Rust project.
#[derive(Clone, Debug, Deserialize)]
pub struct JsonProject {
pub(crate) roots: Vec<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()));
}
}

View File

@ -14,7 +14,7 @@ use std::{
use anyhow::{bail, Context, Result};
use ra_cfg::CfgOptions;
use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use serde_json::from_reader;
pub use crate::{
@ -32,6 +32,12 @@ pub enum ProjectWorkspace {
Json { project: JsonProject },
}
impl From<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 @@ impl PackageRoot {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ProjectRoot {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ProjectManifest {
ProjectJson(PathBuf),
CargoToml(PathBuf),
}
impl ProjectRoot {
pub fn from_manifest_file(path: PathBuf) -> Result<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 @@ impl 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 @@ impl ProjectRoot {
.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 @@ impl ProjectWorkspace {
})?,
}
}
ProjectRoot::CargoToml(cargo_toml) => {
ProjectManifest::CargoToml(cargo_toml) => {
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
.with_context(|| {
format!(
@ -252,6 +270,16 @@ impl ProjectWorkspace {
};
let cfg_options = {
let mut opts = default_cfg_options.clone();
for cfg in &krate.cfg {
match cfg.find('=') {
None => opts.insert_atom(cfg.into()),
Some(pos) => {
let key = &cfg[..pos];
let value = cfg[pos + 1..].trim_matches('"');
opts.insert_key_value(key.into(), value.into());
}
}
}
for name in &krate.atom_cfgs {
opts.insert_atom(name.into());
}

View File

@ -83,13 +83,22 @@ pub trait DocCommentsOwner: AstNode {
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;

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()?;

View File

@ -4,7 +4,7 @@ use ra_cfg::CfgExpr;
use ra_ide::{FileId, RunnableKind, TestId};
use ra_project_model::{self, ProjectWorkspace, TargetKind};
use crate::{world::WorldSnapshot, Result};
use crate::{global_state::GlobalStateSnapshot, Result};
/// Abstract representation of Cargo target.
///
@ -89,7 +89,7 @@ impl CargoTargetSpec {
}
pub(crate) fn for_file(
world: &WorldSnapshot,
world: &GlobalStateSnapshot,
file_id: FileId,
) -> Result<Option<CargoTargetSpec>> {
let &crate_id = match world.analysis().crate_for(file_id)?.first() {

View File

@ -8,7 +8,8 @@ use crossbeam_channel::{unbounded, Receiver};
use ra_db::{ExternSourceId, FileId, SourceRootId};
use ra_ide::{AnalysisChange, AnalysisHost};
use ra_project_model::{
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest,
ProjectWorkspace,
};
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
use rustc_hash::{FxHashMap, FxHashSet};
@ -28,7 +29,7 @@ pub fn load_cargo(
with_proc_macro: bool,
) -> Result<(AnalysisHost, FxHashMap<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() },

View File

@ -12,14 +12,13 @@ use std::{ffi::OsString, path::PathBuf};
use lsp_types::ClientCapabilities;
use ra_flycheck::FlycheckConfig;
use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
use ra_project_model::CargoConfig;
use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
use serde::Deserialize;
#[derive(Debug, Clone)]
pub struct Config {
pub client_caps: ClientCapsConfig,
pub with_sysroot: bool,
pub publish_diagnostics: bool,
pub lru_capacity: Option<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 @@ impl Default for Config {
assist: AssistConfig::default(),
call_info_full: true,
lens: LensConfig::default(),
linked_projects: Vec::new(),
}
}
}
@ -241,6 +262,22 @@ impl Config {
self.lens = LensConfig::NO_LENS;
}
if let Some(linked_projects) = get::<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 @@ impl Config {
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum ManifestOrJsonProject {
Manifest(PathBuf),
JsonProject(JsonProject),
}

View File

@ -29,7 +29,7 @@ expression: diag
},
},
severity: Some(
Warning,
Hint,
),
code: Some(
String(

View File

@ -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);
}

View File

@ -3,7 +3,7 @@ use ra_db::{FileId, FilePosition, FileRange};
use ra_ide::{LineCol, LineIndex};
use ra_syntax::{TextRange, TextSize};
use crate::{world::WorldSnapshot, Result};
use crate::{global_state::GlobalStateSnapshot, Result};
pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize {
let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 };
@ -16,12 +16,12 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex
TextRange::new(start, end)
}
pub(crate) fn file_id(world: &WorldSnapshot, url: &lsp_types::Url) -> Result<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> {

View File

@ -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 @@ impl WorldState {
.exclude(exclude_globs.iter().cloned())
.into_vfs_filter()
};
folder_roots
workspaces
.iter()
.map(|path| RootEntry::new(path.clone(), create_filter(true)))
.chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| {
RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member()))
}))
.flat_map(ProjectWorkspace::to_roots)
.map(|pkg_root| {
let path = pkg_root.path().to_owned();
if pkg_root.is_member() {
local_roots.push(path.clone());
}
RootEntry::new(path, create_filter(pkg_root.is_member()))
})
.chain(
extern_dirs
.iter()
@ -121,7 +125,7 @@ impl WorldState {
let mut extern_source_roots = FxHashMap::default();
for r in vfs_roots {
let vfs_root_path = vfs.root2path(r);
let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it));
change.add_root(SourceRootId(r.0), is_local);
change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string());
@ -176,9 +180,9 @@ impl WorldState {
let mut analysis_host = AnalysisHost::new(lru_capacity);
analysis_host.apply_change(change);
WorldState {
GlobalState {
config,
roots: folder_roots,
local_roots,
workspaces: Arc::new(workspaces),
analysis_host,
vfs: Arc::new(RwLock::new(vfs)),
@ -216,7 +220,7 @@ impl WorldState {
match c {
VfsChange::AddRoot { root, files } => {
let root_path = self.vfs.read().root2path(root);
let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r));
if is_local {
*roots_scanned += 1;
for (file, path, text) in files {
@ -251,8 +255,8 @@ impl WorldState {
self.analysis_host.apply_change(change);
}
pub fn snapshot(&self) -> WorldSnapshot {
WorldSnapshot {
pub fn snapshot(&self) -> GlobalStateSnapshot {
GlobalStateSnapshot {
config: self.config.clone(),
workspaces: Arc::clone(&self.workspaces),
analysis: self.analysis_host.analysis(),
@ -275,7 +279,7 @@ impl WorldState {
}
}
impl WorldSnapshot {
impl GlobalStateSnapshot {
pub fn analysis(&self) -> &Analysis {
&self.analysis
}

View File

@ -26,7 +26,7 @@ mod main_loop;
mod markdown;
pub mod lsp_ext;
pub mod config;
mod world;
mod global_state;
mod diagnostics;
mod semantic_tokens;

View File

@ -12,13 +12,11 @@ use std::{
fmt,
ops::Range,
panic,
path::PathBuf,
sync::Arc,
time::{Duration, Instant},
};
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
use itertools::Itertools;
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
use lsp_types::{
DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
@ -36,14 +34,15 @@ use serde::{de::DeserializeOwned, Serialize};
use threadpool::ThreadPool;
use crate::{
config::{Config, FilesWatcher},
config::{Config, FilesWatcher, LinkedProject},
diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask},
from_proto, lsp_ext,
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
lsp_ext,
main_loop::{
pending_requests::{PendingRequest, PendingRequests},
subscriptions::Subscriptions,
},
world::{WorldSnapshot, WorldState},
Result,
};
@ -69,7 +68,7 @@ impl fmt::Display for LspError {
impl Error for LspError {}
pub fn main_loop(ws_roots: Vec<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 apply_document_changes(
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(sender: &Sender<Message>, loop_state: &mut LoopState) {
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 @@ impl<'a> PoolDispatcher<'a> {
return Ok(self);
}
};
let world = panic::AssertUnwindSafe(&mut *self.world);
let world = panic::AssertUnwindSafe(&mut *self.global_state);
let task = panic::catch_unwind(move || {
let result = f(world.0, params);
result_to_task::<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 @@ impl<'a> PoolDispatcher<'a> {
};
self.pool.execute({
let world = self.world.snapshot();
let world = self.global_state.snapshot();
let sender = self.task_sender.clone();
move || {
let result = f(world, params);
@ -1013,7 +1008,7 @@ where
fn update_file_notifications_on_threadpool(
pool: &ThreadPool,
world: WorldSnapshot,
world: GlobalStateSnapshot,
task_sender: Sender<Task>,
subscriptions: Vec<FileId>,
) {

View File

@ -32,17 +32,16 @@ use crate::{
config::RustfmtConfig,
diagnostics::DiagnosticTask,
from_json, from_proto,
global_state::GlobalStateSnapshot,
lsp_ext::{self, InlayHint, InlayHintsParams},
to_proto,
world::WorldSnapshot,
LspError, Result,
to_proto, LspError, Result,
};
pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<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 @@ pub fn handle_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 @@ pub fn handle_workspace_symbol(
}
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()))
}

View File

@ -10,7 +10,8 @@ use ra_syntax::{SyntaxKind, TextRange, TextSize};
use ra_vfs::LineEndings;
use crate::{
cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result,
cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext,
semantic_tokens, Result,
};
pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
@ -384,41 +385,44 @@ pub(crate) fn folding_range(
}
}
pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<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 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
}
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,
},

View File

@ -58,55 +58,6 @@ use std::collections::Spam;
eprintln!("completion took {:?}", completion_start.elapsed());
}
#[test]
fn test_runnables_no_project() {
if skip_slow_tests() {
return;
}
let server = project(
r"
//- lib.rs
#[test]
fn foo() {
}
",
);
server.wait_until_workspace_is_loaded();
server.request::<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"
}]),
);

View File

@ -19,8 +19,9 @@ use serde_json::{to_string_pretty, Value};
use tempfile::TempDir;
use test_utils::{find_mismatch, parse_fixture};
use ra_project_model::ProjectManifest;
use rust_analyzer::{
config::{ClientCapsConfig, Config},
config::{ClientCapsConfig, Config, LinkedProject},
main_loop,
};
@ -42,7 +43,7 @@ impl<'a> Project<'a> {
self
}
pub fn root(mut self, path: &str) -> Project<'a> {
pub(crate) fn root(mut self, path: &str) -> Project<'a> {
self.roots.push(path.into());
self
}
@ -74,7 +75,16 @@ impl<'a> Project<'a> {
paths.push((path, entry.text));
}
let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
let mut roots =
self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::<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 @@ impl<'a> Project<'a> {
..Default::default()
},
with_sysroot: self.with_sysroot,
linked_projects,
..Config::default()
};
@ -91,7 +102,7 @@ impl<'a> Project<'a> {
f(&mut config)
}
Server::new(tmp_dir, config, roots, paths)
Server::new(tmp_dir, config, paths)
}
}
@ -109,20 +120,12 @@ pub struct Server {
}
impl Server {
fn new(
dir: TempDir,
config: Config,
roots: Vec<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 =

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()..]))
}

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" }

View File

@ -15,6 +15,7 @@ use std::{
};
pub use ra_cfg::CfgOptions;
use stdx::split1;
pub use relative_path::{RelativePath, RelativePathBuf};
pub use rustc_hash::FxHashMap;
@ -332,11 +333,6 @@ fn parse_meta(meta: &str) -> FixtureMeta {
FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env })
}
fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> {
let idx = haystack.find(delim)?;
Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
}
/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines.
/// This allows fixtures to start off in a different indentation, e.g. to align the first line with
/// the other lines visually:

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

File diff suppressed because it is too large Load Diff

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]
|===

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[]

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
}
}
},

View File

@ -18,8 +18,10 @@ use std::{
use crate::{not_bash::fs2, project_root, Result};
pub use self::{
gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs,
gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax,
gen_assists_docs::{generate_assists_docs, generate_assists_tests},
gen_feature_docs::generate_feature_docs,
gen_parser_tests::generate_parser_tests,
gen_syntax::generate_syntax,
};
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";

View File

@ -7,16 +7,17 @@ use crate::{
project_root, rust_files, Result,
};
pub fn generate_assists_tests(mode: Mode) -> Result<()> {
let assists = Assist::collect()?;
generate_tests(&assists, mode)
}
pub fn generate_assists_docs(mode: Mode) -> Result<()> {
let assists = Assist::collect()?;
generate_tests(&assists, mode)?;
let contents = assists.into_iter().map(|it| it.to_string()).collect::<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)]

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");

View File

@ -74,6 +74,7 @@ FLAGS:
args.finish()?;
codegen::generate_syntax(Mode::Overwrite)?;
codegen::generate_parser_tests(Mode::Overwrite)?;
codegen::generate_assists_tests(Mode::Overwrite)?;
codegen::generate_assists_docs(Mode::Overwrite)?;
codegen::generate_feature_docs(Mode::Overwrite)?;
Ok(())

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 @@ impl TidyDocs {
}
fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
let mut cur_path = p;
while let Some(path) = cur_path.parent() {
if dirs_to_exclude.iter().any(|dir| path.ends_with(dir)) {
return true;
}
cur_path = path;
}
false
p.strip_prefix(project_root())
.unwrap()
.components()
.rev()
.skip(1)
.filter_map(|it| it.as_os_str().to_str())
.any(|it| dirs_to_exclude.contains(&it))
}